概要
以前「LINE公式垢でボットとチャット両立させてみた!」という記事を書いた際は、下記のようにenum.Enum
を用いて、現在の会話の状態を管理していました。
class Type(Enum):
BN_CREATE = auto()
BN_CREATE_TRACK1 = auto()
BN_CREATE_TRACK2 = auto()
BN_CREATE_TRACK3 = auto()
BN_CREATE_TRACK5 = auto()
...
本来、BN_CREATE_TRACK*
はBN_CREATE
から枝分かれした状態を表現したいのですが、それが出来ず、並列的に状態が管理されてしまっています。このネストした状態をPythonの enum.IntFlag
( enum.Flag
)を用いて、表現したという記事です。
ネストした(枝分かれした)状態のイメージはこちら
(フローチャート上部からメッセージを送っていくが、途中で分岐が起こり、どの状態かを保存しておく必要がありました。)
レポジトリはこちら!
ネストした状態を並列に表現することの問題
例えば、枝分かれした先の状態も含めて同じ処理をしたい時に、下記のように該当する状態を全て記述する必要がありました。
if ss_type in (Type.BN_CREATE, Type.BN_CREATE_TRACK1, Type.BN_CREATE_TRACK2, Type.BN_CREATE_TRACK3, Type.BN_CREATE_TRACK5):
...
enum.IntFlag
( enum.Flag
)を用いた解決法
enum.IntFlag
( enum.Flag
) はビット演算子 (&, |, ^, ~) を用いることができる列挙型です。 enum.IntFlag
はint型が使えるところでは使える enum.Flag
です。下記のような挙動をします。
from enum import Flag, auto
class Color(Flag):
RED = auto()
GREEN = auto()
BLUE = auto()
PURPLE = RED | BLUE
WHITE = RED | GREEN | BLUE
def print_color(color):
if color == Color.RED:
print('Color is red')
elif color == Color.GREEN:
print('Color is green')
elif color == Color.BLUE:
print('Color is blue')
elif color == Color.PURPLE:
print('Color is purple')
elif color == Color.WHITE:
print('Color is white')
else:
print('not defined')
if __name__ == '__main__':
print_color(Color.BLUE) # Color is blue
print_color(Color.PURPLE) # Color is purple
print_color(Color.RED | Color.BLUE) # Color is purple
print_color(Color.RED | Color.GREEN) # not defined
print_color(Color.WHITE) # Color is white
print_color(Color.RED | Color.GREEN | Color.BLUE) # Color is white
(enum 超まとめ python3.10 より)
概要で記述した部分のコードを下記のように変更し、ヘルパ関数も追加しました。(クラス名も変わっていますが、その部分はお気になさらずに)
# models/status_type.py
class StatusType(IntFlag):
_BN_CREATE_TRACK1 = auto()
_BN_CREATE_TRACK2 = auto()
_BN_CREATE_TRACK3 = auto()
_BN_CREATE_TRACK5 = auto()
BN_CREATE = auto()
BN_CREATE_TRACK1 = BN_CREATE | _BN_CREATE_TRACK1
BN_CREATE_TRACK2 = BN_CREATE | _BN_CREATE_TRACK2
BN_CREATE_TRACK3 = BN_CREATE | _BN_CREATE_TRACK3
BN_CREATE_TRACK5 = BN_CREATE | _BN_CREATE_TRACK5
...
def is_included(parent: StatusType, child: StatusType) -> bool:
return parent & child == parent
_
が最初に入っていないものを enum.Enum
を使っていた際と同じように用います。しかし、このヘルパ関数のおかげで、下記のように記述するだけでネストしたものも含めたい時の記述が簡潔になります。
if is_included(StatusType.BN_CREATE, ss_type):
...
少し雑な気もしますが、このようにネストした状態を列挙型を用いて表現することが出来ました。
何か他に良い方法がありましたら、是非とも教えてください!