この記事では、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__
を自動生成するかどうかを指定できる引数でデフォルトは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__
を自動生成するかどうかを指定できる引数でデフォルトは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__
を自動生成するかどうかを指定できる引数でデフォルトは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
引数は、__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
引数は、__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
引数は、フィールドへの代入に例外を発生させるかどうかを指定できる引数でデフォルトは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
タプルを自動生成するかどうかを指定できる引数でデフォルトは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
引数は、フィールドをキーワード専用にするかどうかを指定できる引数でデフォルトは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
属性を生成するかどうかを指定できる引数でデフォルトは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
デコレータを使うことでとても簡単に定義することができました。
データクラスを定義する場合は積極的に使っていきましょう!
それでは今回の内容はここまでです。ではまたどこかで〜( ・∀・)ノ
View Comments