はじめに
Python3.7が2018/06/15にリリースされる予定です。
https://www.python.org/dev/peps/pep-0537/#release-schedule
Python3.7の新機能に Data Classes がありますが、これを使いこなせばクラス定義が楽になりそうです。
尚、Python3.7の環境作成は以下を参照して下さい。
もうすぐリリースされるPython3.7環境をDockerで作る - Qiita
Data Classes とは
データを格納するためのクラスを簡単に定義できる機能です。
クラス定義にデコレータを1つ付けるだけで__init__
や__str__
などの特殊メソッドを自動生成してくれます。
基本的な使い方
- クラス定義に
dataclass
デコレータを付ける - クラス変数でフィールドを定義する
import dataclasses
@dataclasses.dataclass
class User:
name: str
age: int = 0
User('tarou', 99)
Out[3]: User(name='tarou', age=99)
User('hanako')
Out[4]: User(name='hanako', age=0)
user1 = User('tarou')
user1.age = 99
user1.name, user1.age
Out[5]: ('tarou', 99)
user1 == User('hanako', 99)
Out[6]: False
user1 == User('tarou', 99)
Out[7]: True
カスタマイズ
メソッドを定義する
通常のクラスと同じようにメソッドを定義できます。
import dataclasses
@dataclasses.dataclass
class User:
name: str
age: int = 0
def format(self):
return f'{self.name}さん({self.age}歳)'
イミュータブルにする
デフォルトではミュータブル(変更可能)なオブジェクトになりますが、frozen=True
にするとイミュータブル(変更不可)なオブジェクトになります。
@dataclasses.dataclass(frozen=True)
class User:
name: str
age: int = 0
user1 = User('tarou')
user1.age = 99
FrozenInstanceError Traceback (most recent call last)
<ipython-input-60-cba1cafa11e3> in <module>()
----> 1 user1.age = 99
<string> in __setattr__(self, name, value)
FrozenInstanceError: cannot assign to field 'age'
ミュータブルなデフォルト値を使う
@dataclasses.dataclass
class User:
name: str
age: int = 0
items: List[int] = dataclasses.field(default_factory=list)
User('tarou')
Out[12]: User(name='tarou', age=0, items=[])
フィールドを比較対象から除外する
@dataclasses.dataclass
class User:
name: str
age: int = dataclasses.field(default=0, compare=False)
User('tarou', 10) == User('tarou', 20)
Out[15]: True
フィールドを__init__
の引数から除外する
@dataclasses.dataclass
class User:
name: str
age: int = dataclasses.field(default=0, init=False)
User('tarou')
Out[19]: User(name='tarou', age=0)
User('tarou', age=99)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-20-ab6e147bf66a> in <module>()
----> 1 User('tarou', age=99)
TypeError: __init__() got an unexpected keyword argument 'age'
自動生成された__init__
の後に処理を入れる
__post_init__
という名前のメソッドを定義すると__init__
の後に呼ばれます。
@dataclasses.dataclass
class User:
name: str
age: int = 0
def __post_init__(self):
print('__post_init__', self.name, self.age)
User('tarou')
__post_init__ tarou 0
Out[24]: User(name='tarou', age=0)
__init__
のみで使用する変数を指定する
クラス変数で型をdataclasses.InitVar
にすると、__init__
でのみ使用するパラメータになります。
dataclasses.InitVar
で定義したクラス変数はフィールドとは認識されずインスタンスには保持されません。
@dataclasses.dataclass
class User:
name: str = dataclasses.field(init=False)
age: int = dataclasses.field(init=False)
values: dataclasses.InitVar[Tuple[str, int]] = None
def __post_init__(self, values):
self.name, self.age = values
User(values=('tarou', 99))
Out[28]: User(name='tarou', age=99)
ユーティリティ関数
フィールドリストを取得する
dataclasses.fields(User)
Out[22]:
(Field(name='name',type=<class 'str'>,default=<dataclasses._MISSING_TYPE object at 0x7fd864bfc080>,default_factory=<dataclasses._MISSING_TYPE object at 0x7fd864bfc080>,init=True,repr=True,hash=None,compare=True,metadata={}),
Field(name='age',type=<class 'int'>,default=0,default_factory=<dataclasses._MISSING_TYPE object at 0x7fd864bfc080>,init=False,repr=True,hash=None,compare=True,metadata={}))
dictに変換する
dataclasses.asdict(User('tarou', 99))
Out[8]: {'name': 'tarou', 'age': 99}
tupleに変換する
dataclasses.astuple(User('tarou', 99))
Out[9]: ('tarou', 99)
最後に
基本的な使い方だけだと namedtuple と大きな違いはないのですが、Data Classes では色々とカスタマイズができることがわかりました。
今後クラス定義は Data Classes を使うのが標準的になりそうな予感がします。