[解決!Python]データクラスを定義するには:解決!Python
dataclassesモジュールのdataclassデコレーターを使って、クラスの定義でさまざまな特殊メソッドを自動的に生成する方法を紹介する。
from dataclasses import dataclass
@dataclass
class Person: # クラスのフィールド(属性)は型アノテーションを用いて指定
name: str
height: float
weight: float
def hello(self):
print(f'hello {self.name}') # メソッドも定義できる
p = Person('kawasaki', 180, 72)
p.hello() # hello kawasaki
# 文字列化
repr(p) # "Person(name='kawasaki', height=180, weight=72)"
# 同値性の比較
p2 = Person('isshiki', 190, 78)
print(p == p2) # False
@dataclass
class Person: # フィールドにはデフォルト値を指定可能
name: str
height: float = 0
weight: float = 0
def hello(self):
print(f'hello {self.name}')
p = Person('kawasaki')
print(p.height) # 0
# 生成する特殊メソッドのカスタマイズ
@dataclass(init=False)
class Person:
name: str
height: float
weight: float
def __init__(self, name='nanashi', height=170, weight=60):
self.name = name
self.height = height
self.weight = weight
def hello(self):
print(f'hello {self.name}') # メソッドも定義できる
p = Person(init=False)
print(p) # Person(name='nanashi', height=170, weight=60)
@dataclass(order=True) # インスタンス同士の大小比較を行えるようにする
class Person: # クラスのフィールド(属性)は型アノテーションを用いて指定
name: str
height: float = 0
weight: float = 0
def hello(self):
print(f'hello {self.name}') # メソッドも定義できる
p0 = Person('kawasaki', 180, 70)
p1 = Person('kawasaki', 178, 72)
if p0 > p1:
print('p0 is greater than p1')
else:
print('p0 is lesser than or equal to p1')
# 出力結果:
#
パラメーター | 説明 |
---|---|
init | __init__メソッドを生成するかどうか。デフォルト値はTrue(生成する) |
repr | __repr__メソッドを生成するかどうか。デフォルト値はTrue(生成する) |
eq | __eq__メソッドを生成するかどうか。デフォルト値はTrue(生成する) |
order | __lt__/__le__/__gt__/__ge__メソッドを生成するかどうか。デフォルト値はFalse(生成しない) |
unsafe_hash | __hash__メソッドを生成するかどうか。デフォルト値はFalse(eqパラメーターとfrozenパラメーターがTrueの場合は__hash__メソッドを生成するが、それ以外のときは生成しない)。Trueなら(仮にそのクラスのインスタンスがイミュータブルでなくとも)__hash__メソッドを生成する |
frozen | このクラスのインスタンスのフィールド(属性)への代入を許すかどうか。デフォルト値はFalse(代入を許す) |
match_args | __match_args__属性を作成するかどうか。デフォルト値はTrue(作成する) |
kw_only | 全てのフィールドをキーワード専用とするかどうか。デフォルト値はFalse(キーワード専用としない) |
slots | __slots__属性を作成するかどうか。デフォルト値はFalse(作成しない) |
weakref_slots | __weakref__属性を作成するかどうか。デフォルト値はFalse(作成しない) |
dataclassデコレーターのパラメーター |
dataclassデコレーターを使ったクラスの定義
Pythonに標準で添付されるdataclassesモジュールを使うと、クラス定義において自動的に特殊メソッド(__init__メソッド、__repr__メソッドなど)を生成したり、その属性(これをデータクラスについては「フィールド」と呼ぶ)のデフォルト値を可変なものにしたりするためのデコレーターや関数を使用できるようになる。
dataclassesモジュールにはdataclassデコレーターがあり、これを使うことでクラスの定義を簡単に行える。以下に例を示す。
from dataclasses import dataclass
@dataclass
class Person: # クラスのフィールド(属性)は型アノテーションを用いて指定
name: str
height: float
weight: float
def hello(self):
print(f'hello {self.name}') # メソッドも定義できる
データクラスを定義するには「from dataclasses import dataclass」としてdataclassデコレーターをインポートして、これをクラス定義の前に置く(上記コード例の強調書体部分)。
このクラスのインスタンスが持つフィールド(属性)については型アノテーションを使って指定する。上のコード例ではnameフィールドには文字列を、heightフィールドとweightフィールドには浮動小数点数を指定している。ただし、インスタンス生成時にこれをチェックするわけではない点には注意しよう。
また、helloメソッドのように通常のメソッドを定義することも可能だ(「データクラス」という名前からはデータを保存するだけで、何かの操作を定義できないように思う人もいるかもしれないがこのデコレーターにより作成されるのは通常のクラスである)。
インスタンスの生成は通常のクラスと同様だ。
p = Person('kawasaki', 180, 72)
p.hello() # hello kawasaki
デコレーターに引数を渡さない場合にはデフォルトで以下の特殊メソッドと属性が追加される。
- __init__メソッド
- __repr__メソッド
- __eq__メソッド
- __match_args__属性
そのため、インスタンスの初期化、repr関数(およびこれを使うstr関数)による文字列化、同値性の比較というクラス定義における基本処理の記述を省略できる。
# 文字列化
repr(p) # "Person(name='kawasaki', height=180, weight=72)"
# 同値性の比較
p2 = Person('isshiki', 190, 78)
print(p1 == p2) # False
__repr__メソッドでは上の出力結果のようにインスタンス生成に役立つ形式で文字列化が行われる。もっとシンプルな結果が必要なら、自分で__str__メソッドを定義するようにしよう。
データクラスのインスタンスのフィールドにデフォルト値を持たせるには次のように型アノテーションでデフォルト値を指定するだけでよい。
@dataclass
class Person: # フィールドにはデフォルト値を指定可能
name: str
height: float = 0
weight: float = 0
def hello(self):
print(f'hello {self.name}')
デフォルト値を持つフィールドについてはインスタンス生成時にその値の指定を省略できる。以下に例を示す。
p = Person('kawasaki')
print(p.height) # 0
この例ではインスタンス生成時にnameフィールドの値だけを指定して、他のフィールドは省略している。そのため、heightフィールドの値はデフォルト値である0になっている。
dataclassデコレーターのパラメーターを指定することで、どんな特殊メソッドを生成するかを制御できる。このデコレーターに指定できるパラメーターを以下に示す。なお、自前で特殊メソッドを定義した上で特殊メソッドの自動生成を指定した場合、自動生成が無視されるか例外が発生するので注意しよう。
パラメーター | 説明 |
---|---|
init | __init__メソッドを生成するかどうか。デフォルト値はTrue(生成する) |
repr | __repr__メソッドを生成するかどうか。デフォルト値はTrue(生成する) |
eq | __eq__メソッドを生成するかどうか。デフォルト値はTrue(生成する) |
order | __lt__/__le__/__gt__/__ge__メソッドを生成するかどうか。デフォルト値はFalse(生成しない) |
unsafe_hash | __hash__メソッドを生成するかどうか。デフォルト値はFalse(eqパラメーターとfrozenパラメーターがTrueの場合は__hash__メソッドを生成するが、それ以外のときは生成しない)。Trueなら(仮にそのクラスのインスタンスがイミュータブルでなくとも)__hash__メソッドを生成する |
frozen | このクラスのインスタンスのフィールド(属性)への代入を許すかどうか。デフォルト値はFalse(代入を許す) |
match_args | __match_args__属性を作成するかどうか。デフォルト値はTrue(作成する) |
kw_only | 全てのフィールドをキーワード専用とするかどうか。デフォルト値はFalse(キーワード専用としない) |
slots | __slots__属性を作成するかどうか。デフォルト値はFalse(作成しない) |
weakref_slots | __weakref__属性を作成するかどうか。デフォルト値はFalse(作成しない) |
dataclassデコレーターのパラメーター |
例えば、__init__メソッドで独自の処理を行いたければ、自前でそれを定義した上でデコレーターのinitパラメーターにFalseを渡せばよい。以下に例を示す。
@dataclass(init=False)
class Person:
name: str
height: float
weight: float
def __init__(self, name='nanashi', height=170, weight=60):
self.name = name
self.height = height
self.weight = weight
def hello(self):
print(f'hello {self.name}') # メソッドも定義できる
p = Person()
print(p) # Person(name='nanashi', height=170, weight=60)
ここでは「@dataclass(init=False)」として__init__メソッドの自動生成を抑制している。 その上で__init__メソッドでは特別なことはしていないが、自前でこれを定義している。
また、インスタンス同士の大小関係を比較できるようにするのであれば、デコレーターのorderパラメーターをTrueにする。以下に例を示す。
@dataclass(order=True) # インスタンス同士の大小比較を行えるようにする
class Person:
name: str
height: float = 0
weight: float = 0
def hello(self):
print(f'hello {self.name}') # メソッドも定義できる
この例では「@dataclass(order=True)」とすることで、__lt__/__le__/__gt__/__ge__の各特殊メソッドが自動生成されている。「PEP 557 - Data Classes」から類推するにおおよそ次のコードと同様なコードが生成されていると思われる。
def __lt__(self, other):
if other.__class__ is self.__class__:
return (self.name, self.height, self.weight) < (other.name, other.height, other.weight)
return NotImplemented
def __le__(self, other):
if other.__class__ is self.__class__:
return (self.name, self.height, self.weight) <= (other.name, other.height, other.weight)
return NotImplemented
def __gt__(self, other):
if other.__class__ is self.__class__:
return (self.name, self.height, self.weight) > (other.name, other.height, other.weight)
return NotImplemented
def __ge__(self, other):
if other.__class__ is self.__class__:
return (self.name, self.height, self.weight) >= (other.name, other.height, other.weight)
return NotImplemented
これはそれぞれのインスタンスが持っている3つのフィールドをタプルにまとめて、それらを比較するものだ。そのため、タプルの対応する要素同士の大小比較が行われる。以下の例なら、nameフィールド同士、heightフィールド同士、weightフィールド同士の比較により大小関係が決定するということだ。
p0 = Person('kawasaki', 180, 70)
p1 = Person('kawasaki', 178, 72)
if p0 > p1:
print('p0 is greater than p1')
else:
print('p0 is lesser than or equal to p1')
# 出力結果:
#p0 is greater than p1
この例ならnameフィールドは同じだが、heightフィールドについて見ると、p0.height(180)の方がp1.height(178)よりも大きい。そのため、p0の方が大きいと判断される。
他のパラメーターについてはPythonのドキュメント「dataclasses --- データクラス」や「PEP 557 - Data Classes」を参照されたい。
Copyright© Digital Advantage Corp. All Rights Reserved.