見出し画像

TypedDictとデータクラスとクラス(とNamedTuple)の使い分け

最近ではPythonにおいても型を意識する場面が増えました.Pythonでは直積型の表現方法が複数あり,個人的にはどれを使えばいいのか迷うことが多々あります.そんな使い分けについて,「ロバストPython ― クリーンで保守しやすいコードを書く」にわかりやすい指針が載ってたので紹介します.この本はPythonの型ヒントの使い方からそれを使った依存関係や設計手法の話まで,幅広く解説されておりPythonを業務で使っている方にはオススメです.

結論

「ロバストPython ― クリーンで保守しやすいコードを書く」P151 図10-1

ロバストPythonでは上のフローチャートでまとめられていました.まずは格納したいデータ型が同種(キー,値がそれぞれ同じデータ型からなる)のものか,それぞれ異なるのかで場合分けする.

同種の場合は,離散的なスカラー値の集まり,かつ静的で実行中に集合の要素が変化しないときは列挙型(Enum, Literal)を用い,それ以外のときは辞書を使用します.

格納したいデータ型が多種多様な場合は不変式の有無でデータクラスとクラスを使い分けます.ここで不変式とは

エンティティの生涯に渡って変化しないエンティティの性質

「ロバストPython ― クリーンで保守しやすいコードを書く」P135

のことです.例えば「円の半径は正の整数」など数学的な性質や,ビジネスルールなどのことです.

それぞれの型についておさらい

この記事に登場する型たちの簡単なおさらいです.適宜読み飛ばしてください

データクラスとは

from dataclasses import dataclass

@dataclass
class Book:
    title: str
    author: str
    pages: int
    published_year: int

# データクラスのインスタンスを作成
book1 = Book("The Great Gatsby", "F. Scott Fitzgerald", 218, 1925)

# データクラスのインスタンスの属性にアクセス
print(book1.title)  # 出力: The Great Gatsby

# データクラスの自動生成された__repr__メソッドの使用
print(book1)  # 出力: Book(title='The Great Gatsby', author='F. Scott Fitzgerald', pages=218, published_year=1925)

# 2つのデータクラスのインスタンスを比較
book2 = Book("The Great Gatsby", "F. Scott Fitzgerald", 218, 1925)
print(book1 == book2)  # 出力: True(属性が同じため)

@dataclassデコレータを使用して上記のようにクラス定義すると__init__、__repr__、__eq__などの特殊メソッドを自動的に生成してくれます.
実行時に定義された型と異なる型の値が代入されても特にエラーは出ません.実行時にバリデーションを入れたい場合はPydanticの使用を検討するとよいでしょう.

TypedDictとは

class Movie(TypedDict):
    title: str
    year: int

# Movie型の辞書を作成する
movie: Movie = {
    "title": "Inception",
    "year": 2010
}

print(movie) 

TypedDictは辞書に型ヒントを提供するためのクラスです.こちらもデータクラス同様,実行時のバリデーションはありません.

(おまけ)NamedTuple

Employee = namedtuple('Employee', ['name', 'id', 'role'])

# NamedTupleのインスタンスを作成
employee = Employee(name="Alice", id=1234, role="Engineer")

# 属性にアクセス
print(employee.name)  # 出力: Alice
print(employee.id)  # 出力: 1234
print(employee.role)  # 出力: Engineer

NamedTupleはタプルのサブクラスで,フィールドに名前をつけてアクセスしやすくしたものです.NamedTupleはイミュータブルになります.

異種で不変式がないときに何を使うか?

TypedDict vs データクラス

おそらくここがどちらを使うか一番悩むとこかなと思います.
参考図書にはとりあえずデータクラスを使えと書いてありました.理由は

  • メソッドの定義ができる

  • イミュータブル性や等価比較,大小比較の有無を定義できる(特殊メソッドをオーバーライドできるので)

といったメリットがあるからで,TypedDictはすでに辞書が使われている場合(JSONの処理など)などに使うことを検討するべきだそうです.

namedtuple(名前付きタプル) vs データクラス

ならば,namedtupleとデータクラスはどうなんでしょうか?ここでも著者はデータクラスを使うことを優先するようです.なぜならデータクラスにはnamedtupleにはない以下のメリットがあるからです.

  • 引数に明示的な型アノテーションをつけられる

  • イミュータブル性や等価比較,大小比較の有無を定義できる

  • データ型にメソッドを定義しやすい

ただ,これらは collections.namedtuple とデータクラスを比較した場合のもので,typing.NamedTuple には当てはまりません.
個人的には,NamedTupleはイミュータブルな一方でデータクラスはイミュータブルにするかどうかを選択できるので,データクラスを使えばいいのではないかなと考えています.

参考

この記事が気に入ったらサポートをしてみませんか?