LoginSignup
1
2

More than 1 year has passed since last update.

Pythonのenumでネストした (枝分かれした) 状態を管理

Last updated at Posted at 2022-01-11

概要

以前「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 )を用いて、表現したという記事です。

ネストした(枝分かれした)状態のイメージはこちら
(フローチャート上部からメッセージを送っていくが、途中で分岐が起こり、どの状態かを保存しておく必要がありました。)

qiita.png

レポジトリはこちら!

ネストした状態を並列に表現することの問題

例えば、枝分かれした先の状態も含めて同じ処理をしたい時に、下記のように該当する状態を全て記述する必要がありました。

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):
    ...

少し雑な気もしますが、このようにネストした状態を列挙型を用いて表現することが出来ました。

何か他に良い方法がありましたら、是非とも教えてください!

参考サイト

1
2
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2