きり丸の技術日記

技術検証したり、資格等をここに残していきます。

Pythonのenumはint等のプリミティブ型を継承すると便利

基本的には推奨されない書き方のようですが、使ってみて便利だったのでメモします。

環境

  • Python
    • 3.12

実装

int等のプリミティブな型の継承は次のとおりです。

from enum import Enum

class IntInheritEnum(int, Enum):
    ID = 1


# 列挙型ではなく、intとして振舞うため1にアクセスできる
IntInheritEnum.ID

# 本来のアクセス方法
IntInheritEnum.ID.value

ただし、自作クラス等は継承できません。metaclassの競合が起きるため、クラスで定義したかったものがあれば、namedtuple等を使用するとアクセスしやすくなります。

# metaclassの競合が起きるから、継承できない
# E   TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
# class DummyUser(User, Enum):
#     ADMIN = User(name="admin", email="admin@example.com")

DummyValue = namedtuple('DummyValue', ['name', 'email'])
class DummyUser(Enum):
    ADMIN = DummyValue("admin", "admin@example.com")

# こうやって各項目にアクセスする
DummyUser.ADMIN.value.name
DummyUser.ADMIN.value.email

推奨されない理由

型が違うまま計算できてしまうため、誤った処理をしかねないのが推奨されない原因のようです(ChatGPTより)

# 継承していなければエラーとなる
# 継承しているのでエラーならない
result: int = IntInheritEnum.ID + 10

# 型が違うのに計算できてしまうのは確かに違和感がある
def add(num1: int, num2: IntInheritEnum):
    return num1 + num2

ユースケース

indexでアクセスしたい時にintを継承したenumを使用することで簡潔にアクセスできる。

JOINしたテーブルの取得結果の操作や、pandasでCSVを読み込んだ時の列をenumを使用することで列名として代用できます。

本来はもっと人間に読みやすい定義方法もあると思うのですが、わかりませんでした。namedXXXを駆使すれば行けそうだとは思っていますが…。

ソースコード

終わりに

毎回.valueでアクセスしていた処理が無くなったので、コード自体は読みやすくなりました。

少しずつ、リーダブルなコードが書けるようにしたいですね。