あらすじ
『Python3.7からは「Data Classes」がクラス定義のスタンダードになるかもしれない』に触発されてドキュメントを読んでみたところ、面白そうな関数があったので紹介します。
以下のコードはこちらを参考にしてpython3.7環境で実行しています。
dataclasses
This module provides a decorator and functions for automatically adding generated special methods such as
__init__()
and__repr__()
to user-defined classes.
ユーザが定義するクラスに対して、特殊メソッドを自動生成してくれるデコレータ・関数のモジュールとのこと。
上記ドキュメントに関数が載っているほか、こちらとかこちらで日本語の解説がされています。
dataclassのコンソール実行例
>>> from dataclasses import dataclass
>>> @dataclass
... class Hoge:
... x: int
... y: str
... z: int = 0
...
>>> hoge = Hoge(x=100, y='hoge')
>>> hoge
Hoge(x=100, y='hoge', z=0)
>>>
>>> Hoge
<class '__main__.Hoge'>
Hoge
クラスにdataclass
デコレータをくっつけることで、__init__
を定義しなくても変数x
、y
、z
を初期化してくれています。
z
のデフォルト引数も処理できていますね。
dataclasses.make_dataclassとは
dataclasses.make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)
Creates a newdataclass
with namecls_name
, fields as defined infields
, base classes as given inbases
, and initialized with a namespace as given innamespace
.
fields
is an iterable whose elements are each either name,(name, type)
, or(name, type, Field)
.
If just name is supplied,typing.Any
is used for type.
The values ofinit
,repr
,eq
,order
,unsafe_hash
, andfrozen
have the same meaning as they do indataclass()
.
cls_name
に与えた名前のdataclassを作成する関数とのこと。とりあえず実行してみましょう。
>>> import dataclasses
>>> Fuga = dataclasses.make_dataclass(
... cls_name='Fuga',
... fields=[
... ('a', int),
... ('b', str),
... ('c', int, dataclasses.field(default=0))
... ]
... )
>>> fuga = Fuga(a=100, b='fuga')
>>> fuga
Fuga(a=100, b='fuga', c=0)
>>>
>>> Fuga
<class 'types.Fuga'>
make_dataclass
に引数を渡すと、その引数の情報を元にしてFuga
クラスを作ってくれました。
Fuga
を評価してみるとtypes.Fuga
になっており、types.new_class()
によって生成されているようです。
types.new_class(name, bases=(), kwds=None, exec_body=None)
適切なメタクラスを使用して動的にクラスオブジェクトを生成します。
こう記述されているので、make_dataclass
で生成されたFuga
はクラスというよりはクラスオブジェクトと呼ぶべきかもしれません。
が、面倒なのでこれ以後もクラスオブジェクトのことをクラスと呼びます。ご留意ください。
make_dataclassを使った継承
継承するクラスをbases
に与えることで、そのクラスを継承できます。
上記のFuga
クラスを継承したPiyo
クラスをmake_dataclass
で作ってみましょう。
>>> Piyo = dataclasses.make_dataclass(
... cls_name='Piyo',
... fields=[
... ('d', str, dataclasses.field(default='piyo'))
... ],
... bases=(Fuga,),
... )
>>> piyo = Piyo(a=-1, b='piyopiyo') # Fugaを継承しているので変数aとbが存在する
>>> piyo
Piyo(a=-1, b='piyopiyo', c=0, d='piyo')
>>>
>>> Piyo
<class 'types.Piyo'>
上記では引数bases
にFuga
クラスを指定することで、Fuga
クラスを継承したPiyo
クラスを記述しています。
piyo
の初期化時にPiyo
クラスのfields
に存在しないa
とb
を指定してもエラーが発生しないことやpiyo
の評価結果からFuga
クラスを継承できていることがわかります。
bases
の型としてtupleが指定されているため、多重継承も可能と思われます(まだ試してない)。
make_dataclassを使ったメソッド・クラス変数
namespace
にラムダ式を書くことでメソッドを、定数を書くことでクラス変数を与えることができます。
>>> C = dataclasses.make_dataclass(
... cls_name='C',
... fields=[
... ('n', int, dataclasses.field(default=0))
... ],
... namespace={
... 'n_lt': lambda self, x: self.n <= x, # インスタンスメソッド
... 'debug': lambda n: print(n), # クラスメソッド/スタティックメソッド的なものを書きたかった
... 'N': 100 # クラス変数
... }
... )
>>> c = C()
>>> c.n_lt(1)
True
>>> C.debug('test')
test
>>> c.debug('test')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: <lambda>() takes 1 positional argument but 2 were given
>>> C.N
100
>>> c.N
100
-
C.debug('test')
は処理できていますが、c.debug('test')
の実行が失敗していますね...。
エラーメッセージでは引数が多すぎると言われているので、おそらく内部的にc.debug(self, 'test')
として処理されているのでしょう。
名前空間周りはまだ勉強していないのですが、クラスオブジェクト内の名前空間ではスタティックメソッドとして/クラスオブジェクトのインスタンス内の名前空間ではインスタンスメソッドとして処理されているのでしょうか? -
lambda式しか書けないので代入などを行う処理が書きにくいのが難点かと思います。
可読性の点から言っても、ちょっと込み入ったメソッドを書くなら素直に@dataclass
デコレータをつけたクラスで書いた方がいいかな...。
結局、make_dataclass ←これいる?
This function is not strictly required, because any Python mechanism for creating a new class with
__annotations__
can then apply thedataclass()
function to convert that class to adataclass
.
This function is provided as a convenience.
クラス定義時にアノテーションをつければそのクラスはdataclass()
でdataclass
に変換されるので、厳密に言えばいらないらしい。
実際にクラスを生成する処理はtypes.new_class()
でやっているので当然っちゃ当然ですね。
あとがき
-
動的クラス生成が行えること自体は面白いと思うんですが、どうにも使い方が思い浮かばない。
リフレクションみたいにフレームワークのコード内で使われてたりするんでしょうか? -
クラス定義書いてインスタンス作ってメソッド呼んで、みたいな普通?の書き方に慣れていると動的クラス生成が出てきた時に頭が混乱しそうな気がします。
動的クラス生成がメジャーな手法になって、これがスタンダードな記法になりました!みんな知ってる!ってとこまでいかないとアンチパターン扱いされかねない。 -
dataclasses
で初めて動的クラス生成が実装されたのかと思って書き始めましたが、調べてるうちにPython3.3でtypes.new_class()
が実装されていたことを知りました。とほほ