Edited at

Python3.7からは「Data Classes」がクラス定義のスタンダードになるかもしれない

More than 1 year has passed since last update.


はじめに

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__などの特殊メソッドを自動生成してくれます。

https://docs.python.org/ja/3.7/library/dataclasses.html


基本的な使い方


  • クラス定義に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 を使うのが標準的になりそうな予感がします。