この記事では、Pythonでデータクラスを定義する方法を解説します。
データクラスとは、データを保持するクラスのことを呼びます。ファイルやデータベースから読み込んだデータをデータクラスに保存して使ったりすることができます。
データクラスは、データを扱うためのクラスなので__init__や__repr__の定義が他のデータクラスと似通ったものになりやすいです。
データクラスを使うために同じようなメソッドを毎回定義する必要がありましたが、Python 3.7で追加されたdataclassesモジュールのdataclassデコレータを使うことで__init__などのプリミティブなメソッドを省略して実装できるようになりました。
外部リンクdataclasses --- データクラス — Python 3.10.4 ドキュメント
それでは、dataclassデコレータを使ってデータクラスを定義する方法を見ていきましょう!
データクラスの定義
データクラスは、Python 3.7で追加されたdataclassesモジュールのdataclassデコレータをクラスに修飾して定義します。
from dataclasses import dataclass @dataclass class クラス名: ・ ・ ・
次に、使用するデータをクラス変数のように定義します。その際、アノテーションを使って型ヒントを必ず付けます。
from dataclasses import dataclass @dataclass class クラス名: data1: str data2: int data3: bool
上記のように定義することで以下のような__init__が自動生成されます。
def __init__(self, data1: str, data2: int, data3: bool): self.data1 = data1 self.data2 = data2 self.data3 = data3
試しに簡単なクラスを定義して使ってみましょう。
from dataclasses import dataclass @dataclass class Person: name: str age: int p = Person('Mike', 20) print(p) # もちろん個別にアクセスできる print(p.name) print(p.age)
実行結果
Person(name='Mike', age=20) Mike 20
__init__や__repr__を定義していないのに初期化や出力を行うことができました。このようにdataclassデコレータを使うことでデータクラスを簡略化して定義することができます。
フィールドの初期値
定義するフィールドには初期値を指定することができます。
from dataclasses import dataclass @dataclass class Person: name: str = '名無し' p = Person() print(p)
実行結果
Person(name='名無し')
初期値を指定することで以下のような__init__が自動生成される。
def __init__(self, name: str = '名無し'): self.name = name
オプション
dataclassデコレータを修飾する際に引数を指定することで挙動を変更することができます。
@dataclasses.dataclass(*, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, kw_only=False, slots=False)
init
init引数は、__init__を自動生成するかどうかを指定できる引数でデフォルトはTrueです。
外部リンクobject.__init__(self[, ...]) - データモデル — Python 3.10.4 ドキュメント
init引数にFalseを指定することで__init__を自動生成させなくできる。
from dataclasses import dataclass @dataclass(init=False) class C: data: int c = C(1) print(c)
実行結果
TypeError: C() takes no arguments
その場合は任意の__init__を実装する必要がある。
from dataclasses import dataclass @dataclass(init=False) class C: data: int def __init__(self, data: int) -> None: self.data = data c = C(1) print(c)
実行結果
C(data=1)
いちいちinit引数にFalseを指定しなくてもクラスに__init__メソッドを定義することでそちらを優先させることができる。
from dataclasses import dataclass @dataclass class C: data: int def __init__(self) -> None: self.data = 'dataです' c = C() print(c)
実行結果
C(data='dataです')
repr
repr引数は、__repr__を自動生成するかどうかを指定できる引数でデフォルトはTrue。
外部リンクobject.__repr__(self) - データモデル — Python 3.10.4 ドキュメント
repr引数にFalseを指定することで__repr__を自動生成しない。
from dataclasses import dataclass @dataclass(repr=False) class C: data: int c = C(1) print(c)
実行結果
<__main__.C object at 0x10c855880>
repr引数にFalseを指定しなくてもクラスに__repr__メソッドを定義することでそちらを優先させることができる。
ep
ep引数は、__ep__を自動生成するかどうかを指定できる引数でデフォルトはTrueです。
自動生成される__ep__メソッドは、クラスのフィールドからなるタプルを用いて比較を行えるようにします。比較するインスタンスのクラスは同一でなければFalseとして認識されます。
from dataclasses import dataclass @dataclass class C: data: int c1 = C(1) c2 = C(2) c3 = C(1) print(c1 == c2) print(c1 == c3)
実行結果
False True
ep引数にFalseを指定しなくてもクラスに__ep__メソッドを定義することでそちらを優先させることができる。
order
order引数は、__lt__、 __le__、 __gt__、 __ge__を自動生成するかどうかを指定できる引数でデフォルトはFalseです。
自動生成されるメソッドは、クラスのフィールドからなるタプルを用いて比較を行えるようにします。比較するインスタンスのクラスは同一でなければFalseとして認識されます。
order引数がTrueの際にeq引数にFalseを指定するとValueErrorが送出されます。
from dataclasses import dataclass @dataclass(order=True) class C: data: int c1 = C(1) c2 = C(2) c3 = C(3) print(c1 < c2) print(c1 > c3)
実行結果
True False
クラスに__lt__, __le__, __gt__, __ge__のいずれかが定義されているとTypeErrorが送出されてしまうので注意してください。
unsafe_hash
unsafe_hash引数は、__hash__を自動生成するかどうかを指定できる引数でデフォルトはFalseです。こちらの引数は少し特殊でFalseの場合にeq引数とfrozen引数の設定状況によって__hash__メソッドが生成されます。
Linkobject.__hash__(self) - データモデル — Python 3.10.4 ドキュメント
データクラスをハッシュ化できるようにするには、ep引数とfrozen引数をTrueに指定する。
from dataclasses import dataclass # epはデフォルトでTrueなので明示的に指定しなくても良い @dataclass(frozen=True) class C: data: int c1 = C(1) print(hash(c1))
実行結果
-6644214454873602895
unsafe_hash引数にTrueを指定することでdataclass()に__hash__メソッドを生成することができるが、frozen引数がTrueでない場合に無理矢理__hash__メソッドを生成することでクラスが論理的には不変だがそれにもかかわらず変更できてしまう問題が発生する。
frozen
frozen引数は、フィールドへの代入に例外を発生させるかどうかを指定できる引数でデフォルトはFalseです。
frozen引数にTrueを指定することで読み出し専用のインスタンスを模倣します。つまり、フィールドに代入しようとした際にdataclasses.FrozenInstanceError例外を送出します。
from dataclasses import dataclass @dataclass(frozen=True) class C: data: int c1 = C(1) c1.data = 2
実行結果
dataclasses.FrozenInstanceError: cannot assign to field 'data'
match_args
match_args引数は、match_argsタプルを自動生成するかどうかを指定できる引数でデフォルトはTrueです。 __match_args__タプルは、生成される__init__メソッドのパラメータのリストから作成されます。
※ Python 3.10で追加
例えば、以下のようなクラスの場合、
from dataclasses import dataclass @dataclass class C: data1: int data2: int data3: int
以下のような__match_args__クラス変数が生成される。
__match_args__ = 'data1', 'data2', 'data3'
このクラス変数は、パターンマッチングでこのクラスが位置引数を持つクラスパターンの中で使われた場合、各位置引数は__match_args__の中の対応する値をキーワードとして、キーワード引数に変換されます。
例えば、以下のように__match_args__ = 'data2', 'data1', 'data3'と指定した場合、A(1, 2, 3)ではマッチせず、A(2, 1, 3)でマッチするようになる。
from dataclasses import dataclass @dataclass class C: __match_args__ = 'data2', 'data1', 'data3' data1: int data2: int data3: int c = C(1, 2, 3) match t: # C(data2=1, data1=2, data3=3) case C(1, 2, 3): print('マッチ1') # C(data2=2, data1=1, data3=3) case C(2, 1, 3): print('マッチ2')
実行結果
マッチ2
外部リンクCustomizing positional arguments in class pattern matching - データモデル — Python 3.10.4 ドキュメント
kw_only
kw_only引数は、フィールドをキーワード専用にするかどうかを指定できる引数でデフォルトはFalseです。
※ Python 3.10で追加
Trueに指定された場合はキーワード呼び出しをする必要があります。
from dataclasses import dataclass @dataclass(kw_only=True) class C: data: int # キーワード呼び出しをする c = C(data=1)
フィールド内に_: KW_ONLYを記述することでそれ以降のフィールドをキーワード専用に指定することもできます。
from dataclasses import KW_ONLY, dataclass @dataclass class C: data: int _:KW_ONLY x: int y:int c = C(1, x=2, y=3)
slots
slots引数は、slots属性を生成するかどうかを指定できる引数でデフォルトはFalseです。
※ Python 3.10で追加
Linkクラスのデータメンバーを明示的に指定する方法【__slots__】
slots引数がTrueの場合、__slots__属性が生成され、元のクラスの代わりに新しいクラスが返されます。__slots__がすでにクラスで定義されている場合はTypeErrorが発生します。
from dataclasses import dataclass @dataclass(slots=True) class C: data: int c = C(1) # 存在しないフィールドにアクセスするとAttributeError c.x = 2
実行結果
AttributeError: 'C' object has no attribute 'x'
外部リンクobject.__slots__ - データモデル — Python 3.10.4 ドキュメント
まとめ
この記事では、データクラスを定義する方法を解説しました。
__init__とか__repr__などのメソッドは特殊な処理を実装しない場合、同じような実装になるので記述がめんどくさかったですが、dataclassesモジュールのdataclassデコレータを使うことでとても簡単に定義することができました。
データクラスを定義する場合は積極的に使っていきましょう!
それでは今回の内容はここまでです。ではまたどこかで〜( ・∀・)ノ


