enum と dataclasses — 名前付き定数とデータクラス Enumでの文字列定数の置き換え、IntEnum+auto()の自動採番とソート、@dataclassの定型コード自動生成、field(default_factory=list)を実例で学べます。
値の意味を明示するための 2 つのモジュール を扱います。enumはコード中に直書きした文字列リテラルを、名前付き定数に置き換える ためのもので、タイポを実行時に検出できます。dataclassesは「データを持つだけのクラス」を 1 行で定義 できる仕組みで、__init__ / __eq__ / __repr__の定型コードを省きます。
Enum — 名前付き定数で文字列リテラルを置き換える コード中に"paid" / "shipped"のような文字列リテラル が散らばると、タイポ("shippped" のような) が実行時まで気付けず、補完も効かないため変更にも弱くなります。Enum は「定数の集まりをクラスとして定義する」 ことでこの問題を解決します — 定義した名前はエディタで補完が効き 、typo すると AttributeError で即座にエラー になり、反復(全候補の列挙) もそのままでき、辞書のキーや switch のラベル としても安全に使えます。
# コード中に「ステータス文字列」が散らばっている例
if status == "paid" :
ship_order (order_id)
elif status == "shippped" : # タイポ — "shipped" のはずが、実行時まで気付けない
notify_shipped (order_id)
elif status == "cancelled" :
refund (order_id)
文字列リテラルと Enum の比較 if status == "paid": (文字列リテラル) typo "paied" でも 例外は出ない → バグが残る if status == OrderStatus.PAID: (Enum) OrderStatus.PAEID で AttributeError → 即座に気付ける 素の文字列 は補完が効かず、typo すれば実行時まで気付けない。Enum は定義した名前のみ許容 され、補完・タイポ検出・全候補の反復が一気に手に入る。from enum import Enum
class OrderStatus ( Enum ):
PENDING = "pending"
PAID = "paid"
SHIPPED = "shipped"
CANCELLED = "cancelled"
# メンバアクセス: 名前と値の両方を持つ
print (OrderStatus. PAID ) # OrderStatus.PAID
print (OrderStatus. PAID .name) # 'PAID'
print (OrderStatus. PAID .value) # 'paid'
# 比較は == で
status = OrderStatus. PAID
if status == OrderStatus. PAID :
print ( "支払い済み" )
注文ステータスを Enum で定義し、メンバの情報を取り出します 。
① enum モジュールから Enum を読み込んでください
② OrderStatusという Enum クラスにPENDING / PAID / SHIPPED / CANCELLED の 4 メンバを定義してください(値はそれぞれ小文字の文字列"pending" / "paid" / "shipped" / "cancelled")
③ OrderStatus.PAID.nameをPAID の name: ◯◯ の形で表示してください
④ OrderStatus.PAID.valueをPAID の value: ◯◯ の形で表示してください
⑤ 全メンバの名前を全候補: ◯ の形でリスト化して表示してください(for s in OrderStatusで反復できます)
(正しく実行できれば解説が表示されます)
Enum メンバを if/elif で比較して、ステータスごとのメッセージを返す関数 を作ります。文字列リテラルではなくメンバ同士の比較 で分岐するのが安全な書き方です。
① 実践 1 と同じOrderStatusを定義してください
② get_message(status)関数を定義し、内部の if/elif で各メンバごとに次のメッセージを返してください — PENDING→「支払い待ちです」、PAID→「発送準備中です」、SHIPPED→「発送済みです」、CANCELLED→「キャンセル済みです」
③ get_message(OrderStatus.PAID)をPAID: ◯ の形で表示してください
④ get_message(OrderStatus.SHIPPED)をSHIPPED: ◯ の形で表示してください
IntEnum と auto — 自動採番と数値比較 IntEnum はint を継承した Enum で、メンバが整数 として比較・計算できる点が特徴です。優先度・レベル・順序など、「数の大小に意味がある定数群」 に向きます。auto() は値を自分で書く代わりに自動採番 してくれる関数で、auto()を並べるだけで1, 2, 3, 4...のように番号が振られます。
値の具体的な数字に意味が無い場合(並びさえ正しければよい場合)はauto() を使うほうが、追加・削除で番号を手で振り直す必要がなく安全 です。
IntEnum と auto の組み合わせ class Priority(IntEnum): LOW = auto() MEDIUM = auto() auto() が 1, 2, 3 を自動採番 Priority.LOW < Priority.HIGH → True (int 比較) auto() は1, 2, 3, ...と自動で番号を振る。IntEnum はint との比較 が成り立つので、Priority.LOW < Priority.HIGHのように数の大小で並びを表現 できる。Priority を IntEnum + auto で定義し、複数のタスクを優先度の高い順に並べ替えます 。auto()で自動採番されることと、IntEnum が int として比較・ソートできることを実用シナリオで確認します。
① enum からIntEnumとautoを読み込んでください
② Priorityという IntEnum クラスにLOW / MEDIUM / HIGH / URGENT の 4 メンバを定義してください(値はすべてauto())
③ 全メンバのvalueをリスト化して採番: ◯ の形で表示してください(auto()が1, 2, 3, 4を割り当てたことを確認)
④ タスクのリスト[("掃除", Priority.LOW), ("メール返信", Priority.HIGH), ("資料作成", Priority.URGENT), ("レビュー", Priority.MEDIUM)]を優先度の高い順 にソートし、実行順: の見出しの後に各タスクを- 名前 (優先度) の形式で表示してください
@dataclass — データを持つクラスを 1 行で定義する 「フィールドだけ持つ素朴なクラス」 を自分で書こうとすると、__init__で属性を初期化し、__eq__(==比較で呼ばれる特殊メソッド)で内容比較を実装し、__repr__(printや対話シェルで呼ばれる文字列表現の特殊メソッド)で読みやすい表示を作り、と同じパターンの定型コード (boilerplate、毎回ほぼ同じ書き方をする決まり文句のコード)が並びがちです。@dataclass デコレータを 1 行付けるだけで、Python が型ヒント付きフィールドの宣言からこれらを自動生成 してくれます。
つまり、型ヒント付きの属性を並べるだけ でクラスが完成するということです — 既存のクラスよりはるかに少ないコードで、必要な機能が揃います。
@dataclass が自動生成するもの @dataclass class Order: id: int customer: str → 自動生成 __init__ 属性の初期化 __eq__ 属性が同じなら == __repr__ Order(id=..., customer=...) フィールド宣言(型ヒント付き) を並べるだけで、__init__ / __eq__ / __repr__ の 3 つを自動生成 してくれる。field(default_factory=list) で空リスト等のミュータブル既定値を安全に設定でき、frozen=True で不変クラス(属性変更不可)にもできる。ミュータブル既定値は default_factory で
@dataclassでリストを既定値にするとき、items: list = [] と書くと SyntaxError (または非推奨警告)になります。これは「全インスタンスで同じリストを共有してしまう」 古典的バグの予防です。代わりにfield(default_factory=list) を使うと、インスタンスごとに新しい空リスト が生成されて安全です。dict / set でも同様で、field(default_factory=dict)のように書きます。
注文 Order を @dataclass で定義し、自動生成された __eq__ と __repr__ を確認します 。
① dataclasses からdataclassとfieldを読み込んでください
② @dataclassを付けたOrderクラスを定義してください — フィールドはid: int 、customer: str 、items: list = field(default_factory=list) 、is_paid: bool = False の 4 つ
③ Order(id=1234, customer="Alice", items=["apple", "banana"])でインスタンスを作り、そのままprintで表示してください(__repr__が自動生成されています)
④ 同じ内容のもう 1 つのインスタンスを作り、==で比較した結果を等しい: True / False の形で表示してください
⑤ 1 つ目のインスタンスのis_paidをTrueに書き換えてから、もう一度==で比較した結果を変更後に等しい: True / False の形で表示してください
Q1 文字列リテラル "paid" の比較をEnum に置き換える ことで得られるメリットはどれですか?
実行速度が速くなる typo を AttributeError で即座に検出 でき、補完も効くようになる メモリ使用量が減る JSON 化が速くなる
Q2 IntEnumでauto()を使うメリットはどれですか?
メンバを追加・削除しても 手で番号を振り直さなくて済む メンバを文字列として保存できる 比較演算子が使えなくなる Python 2 でも動くようになる
Q3 @dataclassでリストを既定値 にしたい場合、正しい書き方はどれですか?
items: list = []items: list = field(default_factory=list)items = list()items: list = Noneにして__post_init__で初期化
前へitertools と functools — 反復と関数の合成道具箱 次へ contextlib — リソース管理と with の自作