LoginSignup
5
3

More than 5 years have passed since last update.

Python3.7からのdataclassデコレータの引数

Last updated at Posted at 2018-12-18

このアドベントカレンダーでも何度か登場している 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 __()の記述なしでインスタンス化できる

sample.py
@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に変更する

sample.py
@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プロパティを呼ぶとどうなるだろう。

sample.py
print(user_status.age)
# AttributeError: 'UserStatus' object has no attribute 'age'

つまり、インスタンス化はできるが、ageプロパティがないと怒られます。

ここからわかることは、init=Falseにすると、クラス名の直下に定義されている、インスタンス変数「username: str」「address: str」「age: int」は、無視されているようです。

repr

クラス名、各プロパティ名が、クラス内で定義された順序で並びます。

sample.py
@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

インスタンス同士やインスタンス変数同士が等しいかどうかを比較することができます。

sample.py
@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に変更する

sample.py
@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 thangreater thanを使って比較してみます。

sample.py
@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に変更する

sample.py
@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

今度は正常に比較してくれました。orderTrueの方が便利な場面が多そうです。

unsafe_hash

eq と frozen が両方とも真だった場合、デフォルトでは dataclass() は __ hash __ () メソッドを生成します。 eq が真で frozen が偽の場合、__ hash __ () は None に設定され、(可変なので) ハッシュ化不可能とされます。 eq が偽の場合は、 __ hash __ () は手を付けないまま、つまりスーパークラスの __ hash __ () メソッドが使われることになります (スーパークラスが object だった場合は、 id に基づいたハッシュ化にフォールバックするということになります)。

デフォルトだと、eqfrozenの値で__hash__()が生成されるかが決まるようですが、ミュータブルな状態では、__hash__()は生成されません。

sample.py
@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__()を生成するのは、安全ではない使用方法です。

sample.py
@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にすると、プロパティの書き換えができなくなります。値オブジェクトを作る際にクラスをイミュータブルにしたいなら、これを使うべきです。

sample.py
@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」を開発中です。ぜひ、使ってみて、レビューをください。

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3