[解決!Python]データクラスのフィールドの初期化をカスタマイズするには解決!Python

dataclassesモジュールが提供するfield関数や__post_init__メソッドを使って、データクラスのインスタンスの初期化をより細かく制御する方法を紹介する。

» 2023年06月27日 05時00分 公開
[かわさきしんじDeep Insider編集部]

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

「解決!Python」のインデックス

連載目次

from dataclasses import dataclass, field

# フィールドのデフォルト値を指定しない
@dataclass
class Person:
    name: str
    height: float
    weight: float

p = Person('no name', 150.0, 50.0# 全てのフィールドの初期値を指定する
repr(p)  # "Person(name='no name', height=150.0, weight=50.0)"

p = Person()  # TypeError:初期値の指定は省略できない

# field関数を使ってデフォルト値を指定する
@dataclass
class Person:
    name: str = field(default='no name')
    height: float = 150.0
    weight: float = 50.0

p = Person()
repr(p)  # "Person(name='no name', height=150.0, weight=50.0)"

# repr関数での文字列化に特定のフィールドを含めない
@dataclass
class Person:
    name: str = field(default='no name')
    height: float = field(default=150.0, repr=False)
    weight: float = field(default=50.0, repr=False)

p = Person()
repr(p)  # "Person(name='no name')"

# 同値性の比較対象から特定のフィールドを除外する
@dataclass
class Person:
    name: str = field(default='no name', compare=False)
    height: float = 150.0
    weight: float = 50.0

p0 = Person('kawasaki')
repr(p0)  # "Person(name='kawasaki', height=150.0, weight=50.0)"

p1 = Person('isshiki')
repr(p1)  # "Person(name='isshiki', height=150.0, weight=50.0)"

print(p0 == p1)  # True:nameフィールドは比較対象ではない

# 自動生成される__init__メソッドの初期化に特定のフィールドを含めない
@dataclass
class Person:
    name: str = field(default='no name', init=False)
    height: float = 150.0
    weight: float = 50.0

p = Person()
repr(p)  # "Person(name='no name', height=150.0, weight=50.0)"

# __init__メソッドに渡されなかったフィールドはクラス変数
# __init__メソッドに渡されたフィールドはインスタンス変数
Person.name = 'noname'
Person.height = 160.0
repr(p)  # "Person(name='noname', height=150.0, weight=50.0)"

# 初期化されずデフォルト値もないフィールドはオブジェクトに含まれなくなる
@dataclass
class Person:
    name: str = field(init=False)
    height: float = 150.0
    weight: float = 50.0

p = Person()  # AttributeError

# リストなどミュータブルなフィールドの初期化にはdefault_factoryを指定する
@dataclass
class Person:
    name: str = 'no name'
    height: float = 150.0
    weight: float = 50.0
    favor_list: list[str] = field(default_factory=list)

    def add(self, item):
        self.favor_list.append(item)

p0 = Person()
p0.add('apple')
repr(p0)

p1 = Person()
p1.add('banana')
repr(p1)

# クラス変数としてリストを持たせると複数のオブジェクトがそれを共有してしまう
class C:
    favor_list = []

    def add(self, item):
        self.favor_list.append(item)

c0 = C()
c0.add('apple')
print(c0.favor_list)  # ['apple']

c1 = C()
c1.add('banana')
print(c0.favor_list)  # ['apple', 'banana']

# __init__メソッドの呼び出し後に、__post_init__メソッドで独自の初期化を行う
@dataclass
class Person:
    name: str = 'no name'
    height: float = field(default=150.0, repr=False)
    weight: float = 50.0
    id: int = field(default=0, init=False)

    def __post_init__(self):
        self.__class__.id += 1
        self.id = self.__class__.id

p0 = Person()
repr(p0)  # "Person(name='no name', weight=50.0, id=1)"

p1 = Person()
repr(p1)  # "Person(name='no name', weight=50.0, id=2)"


パラメーター 説明
default このフィールドのデフォルト値を指定する
default_factory 引数なしで呼び出せる関数を指定。その戻り値がこのフィールドのデフォルト値となる
init 自動生成される__init__メソッドでの初期化にこのフィールドを含めるかどうかを指定。デフォルト値はTrue(含める)
repr 自動生成される__repr__メソッドでの文字列化にこのフィールドを含めるかどうかを指定。デフォルト値はTrue(含める)
hash 自動生成される__hash__メソッドでのハッシュ値の計算にこのフィールドを含めるかどうかを指定する。デフォルト値はNoneで、この場合はパラメーターcompareの値によって含めるかどうかが決まる
compare 自動生成される比較メソッド(__eq__メソッドなど)での計算にこのフィールドを含めるかどうかを指定する。デフォルト値はTrue(含める)
metadata マッピングオブジェクトかNoneを指定する(Noneの場合は空の辞書が指定されたものと見なされる)。サードパーティーでの拡張機構として使われることが前提となっているのでデータクラス自体では使われない
kw_only このフィールドの初期化をキーワード専用とするかどうかを指定。デフォルト値はdataclasses.MISSINGで、キーワード専用とはならない
dataclasses.field関数のパラメーター

dataclasses.dataclassデコレーター

 dataclassesモジュールが提供するdataclassデコレーターを使うと、クラス定義時に__init__メソッドなどの特殊メソッドを自動生成できるようになる(詳細については本連載の「データクラスを定義するには」を参照されたい)。

 以下はデータクラスを定義しているところだ。

from dataclasses import dataclass

@dataclass
class Person:
    name: str
    height: float
    weight: float

p = Person('no name', 150.0, 50.0# 全てのフィールドの初期値を指定する
repr(p)  # "Person(name='no name', height=150.0, weight=50.0)"

p = Person()  # TypeError:初期値の指定は省略できない


 ここで型アノテーションを使って宣言している3つのフィールド「name」「height」「weight」は実際にはクラス変数となっている。ここでは初期値を与えていないので、Personクラスのインスタンスを生成する際にはこれらのフィールドの値を与える必要がある。

 もちろん、事前にデフォルト値を与えることも可能だ。

@dataclass
class Person:
    name: str = 'no name'
    height: float = 150.0
    weight: float = 50.0


 しかし、dataclassesモジュールが提供するfield関数を使うと、各フィールドがどのような特徴を持つかを細かく指定できる。以下は簡単なfield関数の使用例である。

@dataclass
class Person:
    name: str = field(default='no name')
    height: float = 150.0
    weight: float = 50.0

p = Person()
repr(p)  # "Person(name='no name', height=150.0, weight=50.0)"


 この例では、nameフィールドのデフォルト値をdefaultパラメーターで指定している。なぜdefaultパラメーターが必要かというと、「name: str =」に続けてデフォルト値を書くところにfield関数呼び出しがあるので、このように指定するしかないからだ。実際には、上のコードでやっていることは以下と変わらない。

@dataclass
class Person:
    name: str = 'no name'
    height: float = 150.0
    weight: float = 50.0

p = Person()
repr(p)  # "Person(name='no name', height=150.0, weight=50.0)"


 つまり、field関数にはデフォルト値の指定以外にもやれることがあるということだ。実際、field関数のパラメーターによって、各フィールドについての特性を決められる。

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。