このアドベントカレンダーでも何度か登場している dataclassデコレータの引数 をまとめます。
目次
- dataclassデコレータの引数紹介
- init
- repr
- eq
- order
- unsafe_hash
- frozen
- おまけ
対象
- dataclassデコレータを使ってみたい人
- dataclassデコレータを使ってるけど、引数をいじったことがない人
dataclassデコレータの引数紹介
引数は、6つ。
引数名 | デフォルト値 |
---|---|
init | True |
repr | True |
eq | True |
order | False |
unsafe_hash | False |
frozen | False |
init
__ init __()の記述なしでインスタンス化できる
@dataclass()
class UserStatus:
username: str
address: str
if __name__ == "__main__":
user_status = UserStatus("kota", "Nagoya")
print(user_status)
# => UserStatus(username='kota', address='Nagoya')
initをFalse
に変更する
@dataclass(init=False)
class UserStatus:
username: str
address: str
age: int # インスタンス変数を追加
def __init__(self, username, address):
self.username = username
self.address = address
if __name__ == "__main__":
user_status = UserStatus(username="kota", address="Nagoya")
dataclassのインスタンス変数の定義の仕方に従って、age
という変数を追加した。そして、インスタンス化時にage
を渡さないとどうなるでしょうか。
実行結果は、TypeError: __init__() missing 1 required positional argument: 'age'
のエラーにはなりません。
そこで、インスタンスのageプロパティを呼ぶとどうなるだろう。
print(user_status.age)
# AttributeError: 'UserStatus' object has no attribute 'age'
つまり、インスタンス化はできるが、ageプロパティ
がないと怒られます。
ここからわかることは、init=False
にすると、クラス名の直下に定義されている、インスタンス変数「username: str」「address: str」「age: int」は、無視されているようです。
repr
クラス名、各プロパティ名が、クラス内で定義された順序で並びます。
@dataclass()
class UserStatus:
username: str
address: str
age: int
if __name__ == "__main__":
user_status = UserStatus("kota", "Nagoya", 23)
print("__repr__(): ", user_status.__repr__())
# => __repr__(): UserStatus(username='kota', address='Nagoya', age=23)
引数内でrepr=False
とすると、__repr__(): <__main__.UserStatus object at 0x1073be6a0>
と表示され、プロパティは表示されません。
eq
インスタンス同士やインスタンス変数同士が等しいかどうかを比較することができます。
@dataclass()
class UserStatus:
username: str
address: str
age: int
if __name__ == "__main__":
user_status = UserStatus("kota", "Nagoya", 23)
user_status_2 = UserStatus("yuki", "Nagoya", 24)
print(user_status.__eq__(user_status_2))
# => False
print(user_status.address.__eq__(user_status_2.address))
# => True
デフォルト値をFalse
に変更する
@dataclass(eq=False)
class UserStatus:
# 省略
if __name__ == "__main__":
user_status = UserStatus("kota", "Nagoya", 23)
user_status_2 = UserStatus("yuki", "Nagoya", 24)
print(user_status.__eq__(user_status_2))
# => NotImplemented
print(user_status.address.__eq__(user_status_2.address))
# => True
eq
のデフォルト値をFalse
に変更して、先程と同内容を比較する。
すると、インスタンス変数同士の比較は、正しく行ってくれるが、インスタンス同士の比較は、行われずにNotImplemented
と出力されます。
「公式ドキュメントのデータモデル」には、このように書かれています。
拡張比較メソッドは与えられた引数のペアに対する演算を実装していないときに、 シングルトン NotImplemented を返すかもしれません。
なので、dataclassでeq=False
の状態で、インスタンス同士の比較をしたい際は、自分で実装しないといけないです。だったら、デフォルト値のままにしておいて、比較できる状態にしておく方が、便利ですよねw
order
order
がデフォルト値のまま、less than
やgreater than
を使って比較してみます。
@dataclass()
class UserStatus:
username: str
address: str
age: int
if __name__ == "__main__":
user_status = UserStatus("kota", "Nagoya", 23)
user_status_2 = UserStatus("yuki", "Nagoya", 24)
print("UserStatusインスタンス同士の比較: ", user_status.__lt__(user_status_2))
# => UserStatusインスタンス同士の比較: NotImplemented
print("age同士の比較: ", user_status.age.__gt__(user_status_2.age))
# => age同士の比較: False
上記と同じ理由で、インスタンス同士の比較はうまくできません。そこで、order=True
にしてみます。
デフォルト値をTrue
に変更する
@dataclass(order=True)
class UserStatus:
# 省略
if __name__ == "__main__":
user_status = UserStatus("kota", "Nagoya", 23)
user_status_2 = UserStatus("yuki", "Nagoya", 24)
print("UserStatusインスタンス同士の比較: ", user_status.__lt__(user_status_2))
# => UserStatusインスタンス同士の比較: True
今度は正常に比較してくれました。order
はTrue
の方が便利な場面が多そうです。
unsafe_hash
eq と frozen が両方とも真だった場合、デフォルトでは dataclass() は __ hash __ () メソッドを生成します。 eq が真で frozen が偽の場合、__ hash __ () は None に設定され、(可変なので) ハッシュ化不可能とされます。 eq が偽の場合は、 __ hash __ () は手を付けないまま、つまりスーパークラスの __ hash __ () メソッドが使われることになります (スーパークラスが object だった場合は、 id に基づいたハッシュ化にフォールバックするということになります)。
デフォルトだと、eq
とfrozen
の値で__hash__()
が生成されるかが決まるようですが、ミュータブルな状態では、__hash__()
は生成されません。
@dataclass(eq=True, frozen=False)
class UserStatus:
username: str
address: str
age: int
if __name__ == "__main__":
user_status = UserStatus("kota", "Nagoya", 23)
print(user_status.__hash__())
# => TypeError: 'NoneType' object is not callable
__ hash __() があるということはそのクラスのインスタンスが不変 (イミュータブル) であることを意味します。
なので、下記のように、ミュータブルな状態で、unsafe_hash=True
として、__hash__()
を生成するのは、安全ではない使用方法です。
@dataclass(eq=True, frozen=False, unsafe_hash=True)
class UserStatus:
username: str
address: str
age: int
if __name__ == "__main__":
user_status = UserStatus("kota", "Nagoya", 23)
print(user_status.__hash__())
# => 69849991143891
frozen
frozen=True
にすると、プロパティの書き換えができなくなります。値オブジェクトを作る際にクラスをイミュータブルにしたいなら、これを使うべきです。
@dataclass(frozen=True)
class UserStatus:
username: str
address: str
age: int
if __name__ == "__main__":
user_status = UserStatus("kota", "Nagoya", 23)
user_status.address = "Tokyo"
# => dataclasses.FrozenInstanceError: cannot assign to field 'address'
おまけ
少しでも皆さんのお役に立てれば、幸いです。
このアドベントカレンダー内の内容で実装しているLINE Botです。ぜひ、使って遊んでみてください。
計画・企画を多様な人から意見を聞けるサービス「Renttle」を開発中です。ぜひ、使ってみて、レビューをください。