【Python】enumとは?使い方から実用例までやさしく解説

はじめに

Pythonを学び始めると、定数として扱う値をどこでどのように管理すればよいのか迷うことがあるかもしれません。
一つの方法としてはクラスに定数を定義するやり方がありますし、モジュールのトップレベルに書いてしまうケースも考えられます。
しかし、複数の定数をまとめて安全に扱いたい場面では、enum(列挙型)がとても便利です。

enumを使うと、定数群をひとまとめにして管理できます。
例えば月の名前や曜日など、事実上変わらない要素を管理する時などに役立ちます。
コード例を使いながら順番に解説していきますので、一緒に見ていきましょう。

この記事を読むとわかること

  • enumの基本的な仕組み
  • enumの導入メリット
  • 実務でのenum活用例
  • enumに関する応用的な機能と使い分けのポイント

ここまで読んでいただくことで、初心者の方でもenumを使うイメージがつかめるはずです。
それでは詳しく見ていきましょう。

Pythonのenumとは?

enumは複数の関連する定数を整理するための仕組みです。
Pythonでは標準ライブラリのenumモジュールに用意されています。
具体的には、曜日を列挙したり、ユーザーのステータスを列挙したり、リクエストの状態を列挙するといった用途で使われることが多いです。

定数を列挙して管理するメリットは以下のようなものがあります。

  • コードが読みやすくなる
  • 意図しない値の代入を防ぎやすくなる
  • 複数の定数を一箇所にまとめるのでメンテナンスしやすい

とくに、アプリケーションの規模が大きくなってくると「どの値が正しいのか」や「どこで定義されているのか」がわかりにくくなりがちです。
そこでenumを利用すれば、あらかじめ定義された値以外を誤って使うリスクを減らし、コードの可読性を向上できます。

enumを使うメリット

enumを使うことで得られる利点

enumを使う最大の利点は、誤った値の代入を防ぐ点にあります。
列挙型として指定されたメンバー(要素)以外は使えないので、想定外の値が混ざるリスクを下げられます。
また、複数人で開発している時にも、メンバー名を読めば何を表しているかすぐに把握できるのも利点の一つです。

さらに、列挙型で定義されるメンバーは一つひとつに独自の名前と値を持っています。
この名前と値はプログラム内で重複を起こさず、表現したい内容ごとにわかりやすいラベルをつけられます。
これにより可読性が向上し、後々の仕様変更にも対応しやすくなります。

enumを使わない場合によくある失敗

enumを使わないと、たとえば文字列や数値を直接使って状態を判定してしまう場面が増えるかもしれません。
後から状態が増えたり命名が変わった場合に、あちこちのコードを修正することになるケースが考えられます。
また、スペルミスや異なる文字列を使ってしまうと、思わぬバグを生み出す要因にもなります。
こうした問題を避けるためにも、enumで値を一元管理しておくと安心できるでしょう。

enumの基本的な使い方

enumの作り方

Pythonのenumは標準ライブラリに含まれています。
以下の例のように、まずenumモジュールからEnumクラスをインポートし、自分の列挙型クラスを作ります。

from enum import Enum

class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

# メンバーへアクセス
print(Color.RED)       # Color.RED
print(Color.RED.value) # 1
print(Color.RED.name)  # RED

ここで定義したColorクラスは、REDGREENBLUEという3つのメンバーを持ち、それぞれ値として1・2・3を持ちます。
列挙型はあくまでクラスなので、Color.REDColor.RED.valueのようにドットでアクセスできます。
またname属性を参照すれば、メンバー名そのもの(ここでは文字列"RED")を取得できます。

enumメンバーの扱い

enumクラスのメンバーは、いわば「決められた定数の集合」です。
メンバーはクラスのコンストラクタを呼んで作るものとは異なり、自動で生成される形になっています。
そのため、普通のクラスインスタンス生成のようにColor(1)などの呼び方をするとエラーになる場合があります(ただし後ほど紹介するIntEnumなどでは少し異なる振る舞いもあります)。

また、メンバー同士を比較する場合は、==演算子やis演算子を使って「同じ列挙型メンバーかどうか」を判定します。
文字列同士の比較ではないため、たとえばColor.RED == "RED"のように比較すると常に偽になります。
この点は初心者の方がつまずきやすいので、注意してください。

実務での活用シーン

Webアプリケーションにおける状態管理

Webアプリケーションには多くの状態が存在します。
たとえば、ユーザーのアカウントステータス(アクティブ・退会済みなど)や、注文のステータス(受付中・発送中・完了など)といったものが典型例です。

もしこれらを文字列で直接扱っていると、スペルを間違えた場合にバグが発生するかもしれません。
しかし、enumであらかじめ定義しておくと、誤ったステータスを設定できなくなるため、余計なトラブルを防げます。
保守性と安全性が増すため、ミドル〜大規模のシステムでは特に役立ちます。

GUIアプリケーションにおける画面遷移のタイプ管理

GUIアプリケーションでも、画面遷移の種類やUI状態をenumで管理するケースがあります。
たとえば、画面を「編集モード」「閲覧モード」「一覧モード」のように切り替える場合、文字列で判定すると行き先を間違えるリスクがあります。
一方でenumなら、意図しないモードを指定してしまうことを防ぎやすいです。

実際にボタンを押したら遷移する先をenumで指定し、画面遷移のロジックをシンプルにまとめると、後から開発メンバーが仕様を読み取る際も明確になります。
このように、GUI系の開発でもenumはわかりやすい選択肢になりえます。

よくある失敗例

列挙メンバーを文字列で直接比較する

Pythonのenumを使い始めると、つい列挙メンバーを文字列と比較してしまうことがあります。
例えば以下のようなコードです。

if Color.RED == "RED":
    print("赤ですね")

このような比較は常に偽になってしまい、意図通りに動きません。
enumメンバーはあくまでもColor.REDというオブジェクトなので、文字列"RED"とは別物です。
比較するときは次のいずれかの方法で書く必要があります。

# メンバー同士を比較する
if Color.RED == Color.RED:
    print("赤ですね")

# 文字列と比較したい場合は name 属性で
if Color.RED.name == "RED":
    print("赤ですね")

列挙型が持つ値を後から変更してしまう

enumは定義した時点でメンバーが確定します。
しかし、何らかの理由で「列挙の値を動的に変更したい」と考えるケースがあるかもしれません。
一般的にはenumの値は固定の想定なので、動的に変えようとするのは避けましょう。
もし「状況に応じて変化する可能性がある値」を扱いたいなら、enumよりも他の方法(たとえばデータベースに保存するなど)がふさわしい場合もあります。

enumの応用的な使い方

auto()関数で自動的に値を割り振る

enumの定義で値を明示せずに、Python側で自動的に割り当ててもらいたい時にはauto()関数が使えます。
以下のように書くと、1番目には1、2番目には2・・・というように自動採番が行われます。

from enum import Enum, auto

class Status(Enum):
    ACTIVE = auto()
    INACTIVE = auto()
    SUSPENDED = auto()

print(Status.ACTIVE.value)   # 1
print(Status.INACTIVE.value) # 2
print(Status.SUSPENDED.value)# 3

この書き方を使うと、列挙体のメンバーを追加・削除しても連番の管理を手動で行う必要がありません。
数値の内容にこだわらず、「異なる値であれば何でもいい」という場合はこの方式が手軽です。

IntEnumで数値と互換性を持たせる

enumの中にはIntEnumというクラスがあります。
これは、enumの各メンバーを整数として扱いたい時に便利です。

from enum import IntEnum

class ResponseCode(IntEnum):
    SUCCESS = 200
    NOT_FOUND = 404
    SERVER_ERROR = 500

if ResponseCode.SUCCESS == 200:
    print("リクエストが成功しました")

IntEnumを使うと、定義したメンバーが普通の整数と比較できるようになります。
この場合、ResponseCode.SUCCESS == 200の比較は真となります。
Web系の開発でHTTPステータスコードを扱うときなどに役立つでしょう。

uniqueデコレータで値の重複を防ぐ

uniqueデコレータを使うと、enumクラスの中で同じ値を持つメンバーが2つ定義されることを禁止できます。
例えば次の例では、OKDONEが両方とも1を持っていますが、uniqueデコレータを付けることでエラーが発生します。

from enum import Enum, unique

@unique
class MyEnum(Enum):
    OK = 1
    DONE = 1

このように付けておけば、開発規模が大きくなってから同じ値の重複に気づかずバグが起きるのを防げます。

enumとクラス定数の使い分け

どちらを使うかの判断基準

「そもそも、列挙型ではなくクラス変数として定数を定義すればいいのでは?」という疑問を持つ方もいるかもしれません。
たとえば次のような書き方です。

class ColorConst:
    RED = 1
    GREEN = 2
    BLUE = 3

単純な定数管理なら、こうしたクラス定数だけで済む場面もあります。
しかし、enumの場合は「定義されたメンバー以外は受け付けない」「特定のメソッドを追加できる」「比較の際にはenum同士しか使わない」といった安全性表現力が得られます。

また、他の人がコードを読んだときにも「これは列挙型なのだな」とひと目でわかるので、可読性が上がる面も無視できません。
実務上は、明確に「決まった集合である」ことがはっきりしているものはenumを使うと良いでしょう。
一方で「変化の可能性がある」「一部だけ値を変えたい」などの要件がある場合は、普通のクラス定数や別の構造を検討すると良いかもしれません。

enumの拡張機能

FlagIntFlagによるビット演算

FlagIntFlagを使うと、ビット演算を組み合わせた列挙型を定義できます。
複数の状態をビット単位で合成したい場合に向いています。
例えば以下のように、ある権限フラグを管理するとしましょう。

from enum import IntFlag

class Permission(IntFlag):
    READ = 1   # 0b001
    WRITE = 2  # 0b010
    EXECUTE = 4  # 0b100

# 権限を複数設定したい場合
user_permission = Permission.READ | Permission.WRITE
if Permission.WRITE in user_permission:
    print("書き込み権限があります")

IntFlagを使えば、Permission.READ | Permission.WRITEというようにビットOR演算で複数のメンバーをまとめられます。
また、in演算子を使って「特定の権限を持っているか?」をチェックするのもわかりやすい書き方です。

列挙型を継承して機能を追加する

enumは継承を利用してメソッドを追加することもできます。
例えば、以下のようにEnumを継承して独自メソッドを持つ列挙型を作ることも可能です。

from enum import Enum

class Priority(Enum):
    LOW = 1
    MEDIUM = 2
    HIGH = 3

    def is_urgent(self):
        return self == Priority.HIGH

# 使用例
if Priority.HIGH.is_urgent():
    print("最優先タスクです")

このように、自分のenumだけが必要とする処理を追加すれば、値の管理とロジックを一貫性のある形でまとめられます。
特定の状態に関する判定を列挙型自体に含めることで、コードの分散を抑えられるでしょう。

enumを使った実用的なサンプル

シンプルなタスク管理

ここでは、enumを活用したタスク管理の例を考えてみます。
一連のタスクが「TODO→IN_PROGRESS→DONE」のサイクルで流れる設定としましょう。

from enum import Enum

class TaskStatus(Enum):
    TODO = 1
    IN_PROGRESS = 2
    DONE = 3

class Task:
    def __init__(self, title):
        self.title = title
        self.status = TaskStatus.TODO

    def start(self):
        if self.status == TaskStatus.TODO:
            self.status = TaskStatus.IN_PROGRESS

    def complete(self):
        if self.status == TaskStatus.IN_PROGRESS:
            self.status = TaskStatus.DONE

# サンプルの使い方
my_task = Task("資料作成")
print(my_task.status)         # TaskStatus.TODO
my_task.start()
print(my_task.status)         # TaskStatus.IN_PROGRESS
my_task.complete()
print(my_task.status)         # TaskStatus.DONE

この例では、タスクの状態管理としてTaskStatusをenumで定義しています。
こうすることで、タスクが明らかにありえない状態遷移を起こす可能性が下がり、コードを理解しやすくなります。
実務のプロジェクトでも、状態管理をenumに置き換えることで、状態数が増えても一貫した書き方を保てるでしょう。

HTTPリクエストのステータスを定義する

もう少し具体的なケースとして、HTTPリクエストの状況をenumで表現する例を見てみます。
こうすることで、処理フローの中でリクエストがどういう段階にいるのかを簡単に判定できます。

from enum import Enum

class RequestState(Enum):
    INIT = 1
    SENT = 2
    SUCCESS = 3
    FAILED = 4

def send_request(url):
    state = RequestState.INIT

    # リクエストを送る処理
    state = RequestState.SENT

    # 何らかの応答判定
    response_code = 200  # 仮に200が返ってきたとする
    if response_code == 200:
        state = RequestState.SUCCESS
    else:
        state = RequestState.FAILED

    return state

result = send_request("https://example.com")
print(result)  # RequestState.SUCCESS

このように段階をわかりやすくenumで列挙しておくと、後から別の段階が増えたり、失敗原因を細かく分類したい時にも対応しやすくなります。
また、メソッドの返り値としてenumが返ってくるため、呼び出し側でも結果を明確にハンドリングしやすいのがメリットです。

enumを活用する際の注意点

過度に細分化しすぎない

enumが便利だからといって、あまり細かく分割しすぎるとコードの量が増えすぎてしまいます。
たとえば「状態がほぼ同じものなのに、微妙に名前だけ違うenumメンバーがたくさんある」という状況になると、かえって混乱する恐れがあります。
定義するときは「本当に必要な数だけを明確に定義する」というバランスが重要です。

設計段階での合意が大切

enumはプロジェクト全体で使われる可能性があります。
つまり、各メンバーの命名や割り当て値の意味づけはチーム内で合意をとっておかないと、「あれ、この値ってなんだっけ?」となってしまうかもしれません。
事前に「こういうステータスが必要だよね」「それぞれの名前はこれで良いよね」というポイントをしっかり話し合うことをおすすめします。

enumを使いこなすためのベストプラクティス

必要最小限のメンバーで始める

列挙型は数が増えすぎると、管理しづらくなるかもしれません。
まずは必要最低限のメンバーだけ定義し、追加が必要になったら随時検討する方法がトラブルを起こしにくいです。
余計なメンバーを最初から入れてしまうと、使われない値が残ってしまい、利用者が混乱する原因にもなります。

比較にはenumメンバー同士か、namevalueを使う

列挙メンバーを比較する際には、なるべくenumメンバー同士の比較を優先しましょう。
文字列を返したい場合でも、name属性を使うと比較が誤りなく行えます。
また、valueで数値を取り出す時もありますが、valueが何を表しているのかは明確にコメントを残したり、コード上でわかりやすくする工夫が必要です。

使い方をドキュメント化する

複数の開発者が関わっている場合、「enumの使い道」「各メンバーの定義意図」を簡単にまとめておくとよいでしょう。
先述の通り、enumはプロジェクト全体に影響を与えることがあります。
特に、名称や値の意味が頻繁に更新されるような列挙型の場合、ドキュメントなしで変更をすると混乱が大きくなるかもしれません。

まとめ

ここまで、Pythonのenumについて初心者向けに詳しく解説しました。
enumを利用すると、定数の管理がしやすくなり、コードの可読性と安全性も向上するというメリットがあります。
特に、実務で状態管理や設定値を扱う時には重宝するはずです。

  • Pythonのenumは標準ライブラリのenumモジュールに含まれており、Enumクラスを継承して実装する
  • メンバーにはそれぞれ名前と値が割り当てられ、enum内で定義された以外の値を受け付けないことで安全性が高まる
  • auto()IntEnumFlagなどを使えば、より柔軟な動きやビット演算などにも対応できる
  • 定数をenumで管理することで、後から定義を変更したり、値を追加するのも統一的に行いやすい

一方で、細かく分割しすぎたり、用途が曖昧なままenumを乱用すると、開発者の混乱を招く可能性がある点も押さえておきましょう。
最初は少なめの定義にしておき、必要に応じてメンバーを拡張する形が現実的かもしれません。

これで、Pythonのenumに関する基本的な仕組みや使い方はほぼ押さえられたはずです。
初心者の皆さんも、ぜひ実装の中でenumを取り入れてみてください。
コードの可読性やバグの減少といった効果を体感しやすいかと思います。

Pythonをマスターしよう

この記事で学んだPythonの知識をさらに伸ばしませんか?
Udemyには、現場ですぐ使えるスキルを身につけられる実践的な講座が揃っています。