Python

【Python】データクラスの定義と使い方

この記事では、Pythonでデータクラスを定義する方法を解説します。

データクラスとは、データを保持するクラスのことを呼びます。ファイルやデータベースから読み込んだデータをデータクラスに保存して使ったりすることができます。

データクラスは、データを扱うためのクラスなので__init____repr__の定義が他のデータクラスと似通ったものになりやすいです。

データクラスを使うために同じようなメソッドを毎回定義する必要がありましたが、Python 3.7で追加されたdataclassesモジュールのdataclassデコレータを使うことで__init__などのプリミティブなメソッドを省略して実装できるようになりました。

Linkdataclasses --- データクラス — 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です。

Linkobject.__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です。

Linkobject.__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

LinkCustomizing 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で追加

slots引数がTrueの場合、__slots__属性が生成され、元のクラスの代わりに新しいクラスが返されます。__slots__がすでにクラスで定義されている場合はTypeErrorが発生します。

from dataclasses import KW_ONLY, dataclass

@dataclass(slots=True)
class C:
    data: int


c = C(1)

# 存在しないフィールドにアクセスするとAttributeError
c.x = 2

実行結果

AttributeError: 'C' object has no attribute 'x'

Linkobject.__slots__ - データモデル — Python 3.10.4 ドキュメント

まとめ

この記事では、データクラスを定義する方法を解説しました。

__init__とか__repr__などのメソッドは特殊な処理を実装しない場合、同じような実装になるので記述がめんどくさかったですが、dataclassデコレータを使うことでとても簡単に定義することができました。

データクラスを定義する場合は積極的に使っていきましょう!

それでは今回の内容はここまでです。ではまたどこかで〜( ・∀・)ノ