Pythonの状態遷移パッケージ(transitions)を理解する【状態編2】

Pythonで状態遷移を実装したり動作確認をしたい方に、Pythonの状態遷移パッケージ「[transitions](https://github.com/pytransitions/transitions)」の使い方を説明していきたいと思います。
transitionsはPythonで状態遷移を実現するためのパッケージで、状態遷移そのものは組込みとか制御などでよく使われるものですが、それをPythonで実現したい場合にこのパッケージが有用かと思います。
その他、transitionsの概要やインストール方法、当記事で作成している状態遷移図といったグラフ表示機能の導入(GraphMachine)や設定については[準備編](https://qiita.com/noca/items/53c01ac5afb687a98db8)の記事を参照頂けたらと思います。

# 今回の話
「[状態編1](https://qiita.com/noca/items/dcdd149c6189098afa39)」ではtransitionsの「状態」に関するクラスやメソッドを紹介しました。
今回はtransitionsの拡張機能(transiitions.extensions)を使って、状態に関するより複雑な機能を実現する方法を紹介します。主に以下の内容になりますが、基本的に全て状態に設定される内容になります。

* 各状態へのタグ付与 (タグ機能/Tagsクラス)
* 不許可の終端状態における例外 (終端状態例外/Errorクラス)

状態に関する応用的な機能になるので、初めての方はまずtransitionsの基本的な使い方である[チュートリアル](https://qiita.com/noca/items/f7ffd4acc641a809ac67)や準備編、[遷移編1](https://qiita.com/noca/items/a132bb5e1ad4bf4eff6a)をご参照頂けたらと思います。

# 各状態へのタグ付与 (タグ機能/Tagsクラス)
各状態(state)を定義する際、その状態名の他にタグを付けることができます。
タグを付けた状態は「is_タグ名」で確認することができます。
以下、タグを付けた状態に関するステートマシンの定義例になります。

```Python:タグ機能をつけたステートマシンの定義例
from transitions import Machine
from transitions.extensions.states import add_state_features, Tags

# タグを使う場合はadd_state_featuresをつけて引数にTagsを指定し新たにCustomMachineクラスを定義
@add_state_features(Tags)
class CustomMachine(Machine):
    pass

# 状態の定義(タグ機能付き)
states = [{'name': 'A', 'tags': ['TagA', 'Init']},     # 状態AにTagAとInitというタグを付ける
          {'name': 'B', 'tags': 'TagB'},               # 状態BにTagBというタグを付ける
          {'name': 'C'}]  

class Model(object):
	pass

model = Model()
machine = CustomMachine(model=model, states=states, initial=states[0]['name'], 
                        auto_transitions=False, ordered_transitions=True,)	
```

これをGraphMachineで図示すると以下の通りになります。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/291448/a7221595-f83e-684a-1a66-1f7f7f8adb0b.png)
Machineでインスタンス作成時に ordered_transitions=Trueを設定していますので[順序遷移](https://qiita.com/noca/items/1d887ad21ef0c66a4c58#%E9%A0%86%E5%BA%8F%E9%81%B7%E7%A7%BB)が有効になっている点に注意下さい。
このステートマシンに各状態でのタグ有無を確認してみます。

```Python:タグ機能を付けたステートマシンのタグ確認（状態A）
>>> model.state          # まずは現在の状態を確認
'A'
>>> machine.get_state(model.state).is_Init   # Initタグの確認
True
>>> machine.get_state(model.state).is_TagA   # TagAタグの確認
True
>>> machine.get_state(model.state).is_TagB   # TagBタグの確認
False
```

次に状態Bに遷移して各タグを確認したいと思います。

```Python:タグ機能を付けたステートマシンのタグ確認（状態B）
>>> model.next_state()   # 次の状態に遷移
True
>>> model.state          # 現在の状態を確認
'B'
>>> machine.get_state(model.state).is_Init   # Initタグの確認
False
>>> machine.get_state(model.state).is_TagA   # TagAタグの確認
False
>>> machine.get_state(model.state).is_TagB   # TagBタグの確認
True
```
次に状態Cに遷移して各タグを確認したいと思います。

```Python:タグ機能を付けたステートマシンのタグ確認（状態C）
>>> model.next_state()   # 次の状態に遷移
True
>>> model.state          # 現在の状態を確認
'C'
>>> machine.get_state(model.state).is_Init   # Initタグの確認
False
>>> machine.get_state(model.state).is_TagA   # TagAタグの確認
False
>>> machine.get_state(model.state).is_TagB   # TagBタグの確認
False
```

これで大体分かったと思いますが、is_タグ名でタグの付けた状態にいることを判定できます。
タグのついていない状態の場合、is_タグ名でエラーにはならずFalseが返ります。

なお、TagsクラスはStateクラスの派生クラスなので以下のように宣言することが可能で、辞書形式の宣言と混在させることが可能です。

```Python
states = [Tags(name='A', tags=['TagA', 'Init']),	# Tagsクラスでタグを宣言
		  {'name':'B', 'tags':'TagB'},				# 辞書でタグを宣言
		  Tags(name='C')]  							# Tagsクラスでタグの無い状態を宣言
````

## タグの操作
繰り返しになりますがTagsクラスはStateクラスの派生クラスなので、Stateクラスと同様な操作や設定が可能です。（詳細は[状態編1](https://qiita.com/noca/items/dcdd149c6189098afa39#%E7%8A%B6%E6%85%8B%E3%81%AB%E9%96%A2%E3%81%99%E3%82%8B%E8%A8%AD%E5%AE%9A)へ）
それに加え、ある状態が持つタグの確認やタグの追加/変更も可能です。

### タグの確認
状態に付与されたタグは各状態のtagsメンバ変数で確認可能です。
冒頭の「タグ機能をつけたステートマシンの定義例」について付与されたタグを確認してみます。

```Python:状態Aが持つタグの確認
>>> model.state
'A'
>>> machine.get_state(model.state).tags		#状態Aが持つタグの確認
['TagA', 'Init']
```

前述のとおり複数タグを付与することは可能ですが、その場合はリストでtagsに指定することになります。
一方でタグを一つしか持たない場合はstr型になる点にご注意ください。

```Python:状態Bが持つタグの確認
model.state
'B'
machine.get_state(model.state).tags		#状態Bが持つタグの確認
'TagB'
```
もちろんリスト形式でタグを1つだけ付与することも可能です。後述するタグの追加を考えると状態宣言時、タグが1つだけの場合であってもリスト形式の方が安全かもしれません。

### タグの追加/変更
tagsメンバ変数で各状態に現在付与されているタグを確認することができますが、このメンバ変数はゲッターではないのでそのまま追加することも可能です。
ただし元のタグがリスト形式で付与されているのか、str形式で付与されているのか注意が必要です。

```Python:状態Aへのタグの追加
>>> machine.get_state(model.state).tags.append('TagNew')	#タグの追加
>>> machine.get_state(model.state).tags
['TagA', 'Init', 'TagNew']
```

もちろん追加したタグはis_タグ名で確認できます。

```Python:状態Aに追加したタグの確認
>>> machine.get_state(model.state).is_TagNew
True
```

一方で状態Bは状態宣言時にタグをstr型で付与しており、このままでは直接追加できません。
そこで、元のタグも含めリスト形式に変更して新たなタグを付与することでタグの追加を実現できます。

```Python:状態Bへのタグ追加
>>> machine.get_state(model.state).tags
'TagB'
>>> machine.get_state(model.state).tags = (['TagB', 'TagNew'])	#リスト形式に変更して追加
>>> machine.get_state(model.state).tags
['TagB', 'TagNew']
```

前述の通り、状態Aと同様にappendで追加できるようにするならば、以下の通り状態定義時にTagBをリスト形式で宣言しておけ良いかと思います

```Python
 {'name':'B', 'tags':['TagB']}
```

## タグの応用（複数の状態へのタグ付け）
タグは一つの状態にだけでなく、複数の状態に対して付けることができるので「この状態とこの状態にいる時は別の処理をする」という事が容易にできるようになります。
またModelクラス外部ではmodel.get_stateメソッドを使ってStateクラスを取得しなければなりませんでしたが、Modelクラス内部であればEventDataの機能を使うともう少し簡単にStateクラスを取得できます。（EventDataについては[コールバック編2](https://qiita.com/noca/items/2db1ac853ee285d04c0b#%E3%82%B3%E3%83%BC%E3%83%AB%E3%83%90%E3%83%83%E3%82%AF%E5%86%85%E3%81%A7%E3%81%AE%E3%82%B9%E3%83%86%E3%83%BC%E3%83%88%E3%83%9E%E3%82%B7%E3%83%B3%E5%90%84%E7%A8%AE%E6%83%85%E5%A0%B1%E3%81%AE%E5%8F%96%E5%BE%97%E6%96%B9%E6%B3%95)をご参照ください）

```Python:タグの応用定義例（複数の状態へのタグ付け）
from transitions import Machine
from transitions.extensions.states import add_state_features, Tags

@add_state_features(Tags)
class CustomMachine(Machine):
    pass

tag_name = 'hoge'
states = ['A', 'B', {'name': 'C', 'tags':tag_name }, {'name': 'D', 'tags':tag_name }]  

class Model(object):
	def action_finalize(self, event):
		print('state {}'.format(self.state))			# self.stateはstr型
		if getattr(event.state, 'is_' + tag_name):		# event.stateはTags(State継承クラス)型, タグの付いた状態のみ処理を実施
			print('特別なhogehoge')

model = Model()
machine = CustomMachine(model=model, states=states, initial=states[0], 
                        auto_transitions=False, ordered_transitions=True, 
						send_event=True, finalize_event='action_finalize')	
```

GraphMachineで生成されたステートマシンにタグを記すと以下の通りです。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/291448/bfb9ff46-467c-ff3d-4cb2-269e73570ce5.png)
実際に動作させてみて動きをみてみます。

```Python:タグの応用動作例（複数の状態へのタグ付け）
>>> model.state             # 現在の状態を確認
'A'
>>> model.next_state()      # 次の状態に遷移
state B
True
>>> model.next_state()      # 次の状態に遷移
state C
特別なhogehoge
True
>>> model.next_state()      # 次の状態に遷移
state D
特別なhogehoge
True
>>> model.next_state()      # 次の状態に遷移
state A
True
```

このようにタグをつけた状態CとDのみで処理を分けることができました。
もちろんaction_event内で「if (self.state == 'C') and (self.state == 'D')」と記載しても同じ動作を実現可能ですが、タグを使った方が状態名を変えた時や新たに条件分岐に状態を加えたい時に保守性が多少あがります。
なお、タグを使わなくても以下の通りタグによる条件分岐の処理を別のコールバックにして、状態Cと状態Dに割り当てると上記と同じような動作をさせることができます。

```Python:タグを使わずコールバックで応用例を実施
from transitions import Machine

tag_name = 'hoge'
states = ['A', 'B', {'name': 'C', 'on_exit':'action_hoge'}, {'name': 'D', 'on_exit':'action_hoge'}]  

class Model(object):
	def action_before(self):
		print('state {}'.format(self.state))

	def action_hoge(self):
		print('特別なhogehoge')

model = Model()
machine = Machine(model=model, states=states, initial=states[0], 
                  auto_transitions=False, ordered_transitions=True, 
				  before_state_change='action_before')	
```

ただし[コールバック編1](https://qiita.com/noca/items/6a809bd1527b8e667da0#%E3%82%B3%E3%83%BC%E3%83%AB%E3%83%90%E3%83%83%E3%82%AF%E3%81%AE%E5%84%AA%E5%85%88%E9%A0%86%E4%BD%8D)にもあるとおり、コールバックには優先順位や実施順位があり、複数のコールバックが登録されている場合では実施順序や実施タイミングが変わるので注意が必要です。
今回の例であればあまり問題ありませんが、例えば[ガード判定](https://qiita.com/noca/items/6a809bd1527b8e667da0#%E3%82%AC%E3%83%BC%E3%83%89%E5%88%A4%E5%AE%9A%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)などを入れた場合はon_exitコールバックは実施されない可能性もあり、タグで実装したものをコールバックのみで実現するよう実装を変更した際は注意が必要です。
また、タグの効果としては「**Modelクラス外部で(タグ付けした)特定の状態であることを確認できる**」ことなので、その辺りも加味して使われると良いかもしれません。

# 不許可の終端状態における例外 (終端状態例外/Errorクラス)
まず**終端状態**の定義について説明します。
例えば以下のようなステートマシンがあったとします。この場合A/B状態のときendトリガーイベントが起こると状態ENDにいきますが、このEND状態のように**遷移するべきトリガーイベントの定義されていない状態を終端状態**と呼ぶことにします。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/291448/4f778e02-bbc3-a442-e9f6-30b78c5615b4.png)

当然のことながら、通常のステートマシンであればこの終端状態であるEND状態へは何のおとがめもなく遷移することが可能です。

```Python:終端状態(END)への遷移（Errorクラス未使用）
>>> model.state
'A'
>>> model.end()				# 終端状態へ遷移（例外発生無し）
True
>>> model.state
'END'
```

一方で状態遷移の実装において、特定の終端状態に遷移した際例外を発生させたい場合があります。ここで役立つのがこのErrorクラス(終端状態例外)になります。

## 終端状態例外の簡単な例
それではまずは簡単な終端状態に対する例外発生について試してみます。
以下のコードは冒頭の「終端状態ENDを持つステートマシン」をErrorクラスを用いて実現した例になります。

```Python:Errorクラスを使った終端状態例外の定義例
from transitions import Machine
from transitions.extensions.states import add_state_features, Error

# Errorを使う場合はadd_state_featuresをつけて引数にErrorを指定し新たにCustomMachineクラスを定義
@add_state_features(Error)
class CustomMachine(Machine):
    pass

# 状態の定義(終端状態例外機能付き)
states = ['A', 'B', 'END']

# 遷移の定義
transitions = [	
	{'trigger':'next',   'source':'A', 'dest': 'B'},
	{'trigger':'next',   'source':'B', 'dest': 'A'},
	{'trigger':'end',    'source':['A', 'B'], 'dest': 'END'},]

class Model(object):
	pass

model = Model()
machine = CustomMachine(model=model, transitions=transitions, states=states, initial=states[0], 
                        auto_transitions=False, ordered_transitions=False)	
```

こちらを冒頭の例同様に終端状態へ遷移してみたいと思います。

```Python:終端状態(END)への遷移（Errorクラス使用）
>>> model.state
'A'
>>> model.end()				# 終端状態への遷移（例外が発生）
Traceback (most recent call last):
～略～
transitions.core.MachineError: "Error state 'END' reached!"
>>> model.state				# 終端状態へは遷移している
'END'
```

このようにErrorクラスを用いて状態を定義すると**終端状態であるENDに遷移した際にMachineError例外が発生**します。
例外理由についても"Error state 'END' reached!"とあり、Error状態(不許可の終端状態)のENDに到達したことを知らせています。

なお、当然のことながら終端状態ではない状態Aや状態Bへは例外発生なく遷移できます。

```Python:終端状態ではない状態への遷移
>>> model.state
'A'
>>> model.next()   # 次の状態(終端遷移ではない)に遷移
True
>>> model.state
'B'
>>> model.next()   # 次の状態(終端遷移ではない)に遷移
True
>>> model.state
'A'
```

## 終端状態例外(Errorクラス)使用時の注意点
終端状態例外(Errorクラス)を使う上での注意点は以下になります。

* Machine定義時、[全状態遷移](https://qiita.com/noca/items/1d887ad21ef0c66a4c58#%E5%85%A8%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB)無効時(auto_transitions=False)のみ有効
* add_state_features宣言時にErrorとTagsと同時宣言しないこと

使用時には上記には留意ください。

## 終端状態例外の除外とタグ
ここでは終端状態例外に実施できる設定を説明します。
まず終端状態例外の除外についてになります。冒頭の例では終端状態に到達した場合必ずMachineError例外が発生してしまいますので、複数の終端状態があった場合に全ての終端状態で例外が発生してしまいます。これでは使い勝手が悪いので終端状態によるMachineError例外を発生させたくない終端状態をacceptedキーを使って除外することができます。
またタグについてですが、ErrorクラスはTagsクラスの継承クラスであり、同時にタグを付けることができます。

上記の終端状態例外の除外とタグを付与した例を以下に示します。

```Python:終端状態例外の除外とタグ定義例
from transitions import Machine
from transitions.extensions.states import add_state_features, Error

@add_state_features(Error)
class CustomMachine(Machine):
    pass

# 状態の定義(終端状態例外、タグ機能付き)
states = ['start', 'processing', 								# (遷移のある状態なので例外発生なし)
		  'error',												# 終端状態例外あり
		  {'name':'warn', 'accepted':True},						# 終端状態例外なし
          {'name':'exit', 'tags':['accepted', 'Compleat']}]		# 終端状態例外なし+Compleatタグ

# 遷移の定義
transitions = [	
	{'trigger':'run',       'source':'start',      'dest': 'processing'},
	{'trigger':'cancel',    'source':'processing', 'dest': 'warn'},
	{'trigger':'success',   'source':'processing', 'dest': 'exit'},
	{'trigger':'trouble',   'source':'processing', 'dest': 'error'},]

class Model(object):
	def action_finalize(self, event):
		if event.state.is_Compleat:
			print('Compleat!')

model = Model()
machine = CustomMachine(model=model, transitions=transitions, states=states, initial=states[0], 
                        auto_transitions=False, ordered_transitions=False, send_event=True,
						finalize_event='action_finalize')	
```

こちらをGraphMachineで画像出力し、各終端状態例外の設定状況、タグについて記載したものが以下になります。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/291448/2e5a80f3-4c6d-ff43-3ba1-b2b351e08e05.png)
はじめにproessing状態に遷移し各動作の確認をしていきたいと思います。

```Python
>>> model.run()
True
```

まずは終端状態例外のあるerrorに遷移してみます。

```Python:終端状態例外の除外とタグ定義例（終端状態例外の確認）
>>> model.state
'processing'
>>> model.trouble()		 # error状態へ遷移するトリガーイベント
Traceback (most recent call last):
～略～
transitions.core.MachineError: "Error state 'error' reached!"
>>> model.state
'error'
```

きちんと終端状態例外の発生を確認しました。
次に同様にprocessing状態から終端状態例外およびタグ設定のないwarn状態への遷移をしてみたいと思います。

```Python:終端状態例外の除外とタグ定義例（終端状態例外の除外確認）
>>> model.state
'processing'
>>> model.cancel()		 # warn状態へ遷移するトリガーイベント
True
>>> model.state
'warn'
```

このようにwarn状態ではacceptedキーにTrueを設定しているのでMachineError例外発生せずに終端状態へ遷移することができました。
次にCompleatタグの付与されたexit状態への遷移を確認します。

```Python:終端状態例外の除外とタグ定義例（終端状態例外の除外+タグ確認）
>>> model.state
'processing'
>>> model.success()		 # exit状態へ遷移するトリガーイベント
Compleat!
True
>>> model.state
'exit'
```

exit状態の定義においては、tagsキーでacceptedを付与することで終端状態例外の除外を行っています。
加えてtagsキーにリストでCompleatタグを付与しており、Modelクラスのaction_finalizeメソッド内において、タグ確認(is_Compleat)がTrueになっていることも確認できました。
ちなみにErrorクラスにおいて、acceptedキーにTrueを指定した状態はtags内にacceptedタグが割り振られます。つまりErrorクラスにおいて**acceptedタグが付いていると終端状態例外が除外される**という事になります。

```Python
>>> machine.get_state('warn').tags
['accepted']
>>> machine.get_state('exit').tags
['accepted', 'Compleat']
```

なおこの終端状態例外を実現するErrorクラスは以下のように定義することができ、辞書形式での定義とも混在させることが可能です。

```Python:Errorクラスを使った定義
states = ['start', 'processing', 								# (遷移のある状態なので例外発生なし)
		  Error(name='error'),									# 終端状態例外あり
		  Error(name='warn', accepted=True),					# 終端状態例外なし
          Error(name='exit', tags=['accepted', 'Compleat'])]	# 終端状態例外なし+Compleatタグ
```

## 終端状態例外(Errorクラス)の補足
ここから先は補足的な話になるので、実装する際に参考にされると良いレベルの話を記載しておきます。
ここではステートマシンに特に言及がない場合、「終端状態例外の除外とタグ定義例」のコードを実行済みという前提で話を進めます。

### TagsクラスおよびStateクラスとの混在定義
前述の通り状態定義(states)において終端状態例外はErrorクラスによる宣言でも実現できる事を示しました。
一方で「終端状態例外の除外」をTagsやStateクラスで実現することができます。
※もちろんadd_state_features(Error)をCustomMachineに付与していることが前提です。

```Python:TagsクラスおよびStateクラスによる状態定義例外の除外定義
states = ['start', 'processing', 					# (遷移のある状態なので例外発生なし)
		  Error(name='error'),						# 終端状態例外あり
		  Tags(name='warn')							# 終端状態例外なし
          Tags(name='exit', tags=['Compleat'])]		# 終端状態例外なし+Compleatタグ
```

このように終端状態例外を実現したステートマシンに対しTagsクラスを使う事でaccepted=Trueにしたことと同様の効果が得られます。
また、もしもコード内でis_タグ名による確認を使わなければtransitions.Stateクラスで実現することも可能です。
ただ前述の通りErrorクラスにおいて終端状態例外を除外したものにはacceptedタブが付くのですが、Tabsクラスを混在させた場合問題が生じます。

例えば以下のような状態を持つステートマシンがあったとします。（CustomMachineにはErrorクラスを付与したMahineカスタムクラスとします）

```Python:
machine = CustomMachine(states=[Error('A'), Tags('B')], auto_transitions=False) 
```

このステートマシンのもつ状態クラスとタグを確認してみます。

```Python:
>>> machine.get_state('A')
<Error('A')@2316392704936>
>>> machine.get_state('A').tags
[]
>>> machine.get_state('B')
<Tags('B')@2316392705104>
>>> machine.get_state('B').tags
[]
```

これはつまり状態AはErrorクラスでtagsは空、状態BはTabクラスでtagsは空となります。
これの何が問題かというと、状態Aと状態Bが共に終端状態であった場合、それぞれ挙動が異なることを意味します。
つまり「状態AはErrorクラスなので、タグが空＝acceptedタグがない＝終端状態例外が発生。一方で状態Bはタグが空だがTabsクラスなので状態遷移例外は発生しない」ということを意味します。
従って、**ErrorクラスとTabs/Stateクラスを混在させた場合、acceptedタグ有無だけでは終端状態例外の有効/無効を判定できない**ということを意味します。
このような状況も含め終端状態例外の有無を確認する方法について次で説明します。

### 終端状態と終端状態例外の確認
終端状態例外の機能において、「その状態が終端状態例外が発生するか否か」というのを確認するのはメソッドが用意されておらず少々手間をかける必要があります。

まずは終端状態の判定をしてみましょう。
終端状態は特定状態で「別の状態への遷移が無い」ことを確認すれば良いので、「遷移」を持っているか否かで確認可能です。

```Python:終端状態の確認コード
for state_name in machine.states:
	is_end = (not machine.get_triggers(state_name))
	print('state: {:<10}\t 終端状態?: {}'.format(state_name, is_end))
```

これを実行すると以下が得られます。

```Python:終端状態の確認結果
state: start     	 終端状態?: False
state: processing	 終端状態?: False
state: error     	 終端状態?: True
state: warn      	 終端状態?: True
state: exit      	 終端状態?: True
```

これを元に、次に終端状態例外が発生するかを判定してみます。
終端状態例外が発生する条件としては「Errorクラスであること」「遷移が無いこと」「acceptedタグが設定されていないこと」になります。

```Python:終端状態例外の確認コード
for state_name in machine.states:
	state = machine.get_state(state_name)
	is_err = (isinstance(state, Error)) and \
			 (not machine.get_triggers(state_name)) and \
			 (not state.is_accepted)
	print('state: {:<10}\t 終端状態例外あり?: {}'.format(state_name, is_err))
```

これを実行すると以下が得られます。
どうでしょう。状態定義の意図一致していることを確認できたかと思います。

```Python:終端状態例外の確認結果
state: start     	 終端状態例外あり?: False
state: processing	 終端状態例外あり?: False
state: error     	 終端状態例外あり?: True
state: warn      	 終端状態例外あり?: False
state: exit      	 終端状態例外あり?: False
```

こちらはもちろんTagsクラスやStateクラスが混在した状態定義(states)に対しても実施可能です。
なおコード中にis_errで状態がErrorクラスであることを確認していますが、これは状態定義(states)にStateクラスによる定義が混在していた場合is_acepted確認で例外が発生することを避けているためです。

### 終端状態例外発生時のコールバックについて
状態編ではコールバックについて説明していませんが、transitionsパッケージには様々な種類のコールバックを複数設定することが可能です。（詳細は[コールバック編1](https://qiita.com/noca/items/6a809bd1527b8e667da0)へ）
一方でこのコールバックですがトリガーイベント実施中に例外が発生すると中断される場合があるのでそれについて言及しておきます。

[コールバック編1](https://qiita.com/noca/items/6a809bd1527b8e667da0#%E3%82%B3%E3%83%BC%E3%83%AB%E3%83%90%E3%83%83%E3%82%AF%E3%81%AE%E5%84%AA%E5%85%88%E9%A0%86%E4%BD%8D)によると、ガード判定も含め状態遷移前から状態遷移後に向かっていくつかのコールバックが発生します。
一方で、ある状態から終端状態例外の発生する状態に遷移した場合、実施されるコールバックは以下のとおりになります。

|        定義名        |    定義先   |     状態     |   状態遷移例外発生時も実施  |
| :------------------ | :---------- | :---------- | :----------------------- |
| prepare_event       | machine     | source      | YES                      |
| prepare             | transitions | source      | YES                      |
| conditions          | transitions | source      | YES                      |
| unless              | transitions | source      | YES                      |
| before_state_change | machine     | source      | YES                      |
| before              | transitions | source      | YES                      |
| on_exit             | state       | source      | YES                      |
| --- 状態変化 ---     |             |             |                          |
| on_enter            | state       | dest        | NO                       |
| after               | transitions | dest        | NO                       |
| after_state_change  | machine     | dest        | NO                       |
| finalize_event      | machine     | source/dest | YES                      |

このように終端状態例外の発生する状態へ遷移した際、状態変化後(dest状態)で実施される予定のコールバックはfinalize_event以外に実施されない点にご注意ください。
なおユーザの定義したon_enterコールバックは実施されないものの、終端状態例外そのものはon_enter内で発生します。

確認コードは長いので以下にしまっておきます。
<details><summary>確認に使用したコードと実行例はここをクリックして下さい。 </summary><div>

```Python:終端状態例外発生時のコールバック確認用ステートマシン定義
import sys
from transitions import Machine
from transitions.extensions.states import add_state_features, Error

@add_state_features(Error)
class CustomMachine(Machine):
    pass

states = [Error(name='A', on_exit='action_on_exit', on_enter='action_on_enter'), 'END']   #状態の定義
transitions = [
	{'trigger':'event', 'source':'A', 'dest':'END', 
	 'prepare'		:'action_prepare', 
	 'conditions'	:'action_conditions',
	 'unless'		:'action_unless',
	 'before'		:'action_before',
	 'after'		:'action_after'}
]

class Model(object):
	# defined machine
	def action_prepare_event(self):
		print('do "{}" on state ({})'.format(sys._getframe().f_code.co_name, self.state))
	
	# defined transitions
	def action_prepare(self):
		print('do "{}" on state ({})'.format(sys._getframe().f_code.co_name, self.state))
	
	# defined transitions
	def action_conditions(self):
		print('do "{}" on state ({})'.format(sys._getframe().f_code.co_name, self.state))
		return True
	
	# defined transitions
	def action_unless(self):
		print('do "{}" on state ({})'.format(sys._getframe().f_code.co_name, self.state))
		return False
	
	# defined machine
	def action_before_state_change(self):
		print('do "{}" on state ({})'.format(sys._getframe().f_code.co_name, self.state))

	# defined transitions
	def action_before(self):
		print('do "{}" on state ({})'.format(sys._getframe().f_code.co_name, self.state))

	# defined states
	def action_on_exit(self):
		print('do "{}" on state ({})'.format(sys._getframe().f_code.co_name, self.state))
	
	### --- change state ---
	# defined states
	def action_on_enter(self):
		print('do "{}" on state ({})'.format(sys._getframe().f_code.co_name, self.state))
		
	# defined transitions
	def action_after(self):
		print('do "{}" on state ({})'.format(sys._getframe().f_code.co_name, self.state))
	
	# machine	
	def action_after_state_change(self):
		print('do "{}" on state ({})'.format(sys._getframe().f_code.co_name, self.state))
	
	# machine
	def action_finalize_event(self):
		print('do "{}" on state ({})'.format(sys._getframe().f_code.co_name, self.state))
		
model = Model()
machine = CustomMachine(model=model, states=states, transitions=transitions, initial=states[0], 
                        auto_transitions=False, ordered_transitions=False)

machine.prepare_event.append('action_prepare_event')
machine.before_state_change.append('action_before_state_change')
machine.after_state_change.append('action_after_state_change')
machine.finalize_event.append('action_finalize_event')
```

以下、上記定義の動作確認結果です。

```Python:終端状態例外発生時のコールバック確認用ステートマシン動作例
>>> model.event()
do "action_prepare_event" on state (A)
do "action_prepare" on state (A)
do "action_conditions" on state (A)
do "action_unless" on state (A)
do "action_before_state_change" on state (A)
do "action_before" on state (A)
do "action_on_exit" on state (A)
do "action_finalize_event" on state (END)
Traceback (most recent call last):
～略～
transitions.core.MachineError: "Error state 'END' reached!"
```
</div></details>

# まとめ
今回は状態(State)の派生クラスであるTagsクラスとErrorクラスを紹介しました。
Tagsクラスは1つまたは複数の状態にタグを付けられ、それぞれタグの付いた状態が確認できることを示しました。
Errorクラスでは終端状態においてMahineError例外を発生できることと、終端状態例外発生の除外について説明しました。
これらは状態の拡張機能でありカスタムステートと呼ばれるものですが、実はまだ2つほどカスタムステートがあります。

今回は思ったよりも長くなってしまったので、次回に残りのカスタムステート(状態に関する拡張機能)について説明したいと思います。
ここまで読んでいただきありがとうございました。少しでもご参考になれば幸いです。