今回は Python 3.7 で標準ライブラリに追加された dataclasses
について見ていきたいと思います。
import dataclasses
目次
dataclasses
とはdataclasses
の使い方dataclasses
の使いどころ
dataclasses
とは
dataclasses
は、独自のクラスを定義するときに定型的な特殊メソッド( special methods )( __init__()
等)の記述を省略できる機能を提供するモジュールです。 Python 3.7 から標準ライブラリとして Python 本体に同梱されるようになりました。
代表的な機能は次の 2 つです。
dataclasses.dataclass
: クラスのデコレータ。対象のクラスのクラス変数をもとに特殊メソッドを追加する。dataclasses.field
: クラス変数に使う。dataclass
とあわせて使うことでアトリビュートごとの挙動を細かく設定できる。
dataclasses
の使い方
まずインストールについてですが、 dataclasses
は Python 3.7 以降に同梱されているためインストールする必要はありません。
代表的な機能である次の 2 つから見ていきましょう。
dataclasses.dataclass
dataclasses.field
dataclasses.dataclass
dataclass
はクラスに対して使うデコレータです。
dataclass
でデコレートされたクラスには、 __init__()
、 __repr__()
、 __eq__()
の 3 つの特殊メソッドが自動的に追加されます。
from dataclasses import dataclass
TAX_RATE = 0.1
@dataclass
class Book:
title: str
isbn13: str
price: int
def price_with_tax(self):
return int(self.price * (1 + TAX_RATE))
# __init__() が自動的に定義されている:
book1 = Book('小僧の神様・城の崎にて', '978-4101030050', 562)
# クラス変数と同名のアトリビュートが利用できる:
print(book1.title)
# => '小僧の神様・城の崎にて'
print(book1.isbn13)
# => '978-4101030050'
print(book1.price)
# => 562
print(book1.price_with_tax())
# => 618
# __repr__() も自動的に定義されている:
print(repr(book1))
# => Book(title='小僧の神様・城の崎にて', isbn13='978-4101030050', price=562)
# __repr__() の出力は eval() すると新しいインスタンスが作れる形になっている:
book2 = eval(repr(book1))
# __eq__() も自動的に定義されている:
print(book1 == book2)
# => True
dataclass
の最もシンプルな使い方は上のサンプルのように @dataclass
という形でデコレートするものですが、 @dataclass
にはオプションを渡すこともできます。オプションでは、特殊メソッドの追加の有無等を指定することができます。
from dataclasses import dataclass
@dataclass(repr=False, eq=False)
class Book:
title: str
isbn13: str
price: int
# __init__() が自動的に定義されている:
book1 = Book('暗夜行路', '978-4101030074', 961)
# repr=False により __repr__() は定義されていないので object のデフォルトの出力になる:
print(repr(book1))
# => <__main__.Book object at 0x106fa94a8>
book2 = Book('暗夜行路', '978-4101030074', 961)
# eq=False により __eq__() は自動追加されていないので、デフォルトの object.__eq__() で判定される:
print(book1 == book2)
# => False
ちなみに、 dataclass
デコレータが @dataclass
でも @dataclass()
でも同じような使い方ができるのは、 dataclass
がそのように(シンプルに使えるように)工夫して作られているからであり、一般のデコレータはこのようには行きません。
dataclass
デコレータがアトリビュート化する要素として認識するのは タイプアノテーションのあるクラス変数だけ です。タイプアノテーションの無いクラス変数は無視されるので、そこは注意が必要です。次のコードではタイプアノテーションの無い isbn13
は自動追加されません。
from dataclasses import dataclass
@dataclass
class Book:
title: str
isbn13 = ''
price: int
# isbn13 は dataclass には無視される:
book1 = Book('和解', 464)
print(repr(book1))
# => Book(title='和解', price=464)
また、 dataclass
は「タイプアノテーションがあるかどうか」という点だけを見ており、具体的な型が何なのかは見ていないという点もポイントです。つまり、 int
と定義されている変数に str
型の値を代入しても何も問題ありません(バリデーション等は特に追加されません)。
# 次のコードも特に問題なし
book1 = Book('和解', '464')
尚、 Python 3.7 における dataclass
の宣言部は次のようになっています。
@dataclasses.dataclass(
*,
init=True,
repr=True,
eq=True,
order=False,
unsafe_hash=False,
frozen=False)
引数がすべて *,
の後に並んでいるので、引数はすべてキーワードを明示的に指定した渡し方をする必要があります。
dataclasses
は dataclass
でデコレートされたクラスとそのインスタンスに対して使える関数をいくつか用意しています。次のコードでは fields()
・ asdict()
・ astuple()
の 3 つを試しています。
from dataclasses import dataclass, fields, asdict, astuple
@dataclass
class Book:
title: str
isbn13: str
price: int
# fields() 関数はそのクラスあるいはインスタンスのフィールドの一覧をタプルで返す:
# タプルの各要素は dataclasses.Field クラスのインスタンス
print(type(fields(Book)))
print([x.name for x in fields(Book)])
# => ['title', 'isbn13', 'price']
book1 = Book('小僧の神様・城の崎にて', '978-4101030050', 562)
print([x.name for x in fields(book1)])
# => ['title', 'isbn13', 'price']
# asdict() 関数は辞書を生成する
print(asdict(book1))
# => {'title': '小僧の神様・城の崎にて', 'isbn13': '978-4101030050', 'price': 562}
# astuple() 関数は tuple を生成する
print(astuple(book1))
# => ('小僧の神様・城の崎にて', '978-4101030050', 562)
dataclasses
のアイデアの概要は以上でおおよそ押さえられたかと思いますが、実際に利用する場合は詳細を確認してから使うようにしてください。
つづいて field
を見てみましょう。
dataclasses.field
field
は dataclass
でデコレートされたクラスのアトリビュートの挙動を細かく指定するためのものです。
例えば、アトリビュートのデフォルト値に list
を指定したいときには field
が次のように使えます。
from dataclasses import dataclass, field
from typing import List
@dataclass
class Film:
title: str = ''
cast: List[str] = field(default_factory=list)
f1 = Film('ジュラシック・ワールド', ['クリス・プラット', 'ブライス・ダラス・ハワード'])
print(repr(f1))
# => Film(title='ジュラシック・ワールド', cast=['クリス・プラット', 'ブライス・ダラス・ハワード'])
f2 = Film('ジュラシック・ワールド 炎の王国', ['クリス・プラット', 'ブライス・ダラス・ハワード'])
print(repr(f2))
# => Film(title='ジュラシック・ワールド 炎の王国', cast=['クリス・プラット', 'ブライス・ダラス・ハワード'])
また、次のように書くと、一部の要素( subtitle
)を __eq__()
での比較の対象外にすることができます。
from dataclasses import dataclass, field
@dataclass
class Film:
title: str
subtitle: str = field(compare=False)
f1 = Film('007', 'ワールド・イズ・ノット・イナフ')
f2 = Film('007', '慰めの報酬')
f3 = Film('007', 'スカイフォール')
# __eq__() には subtitle が使われないので、 == 演算で title のみが比較された結果、 True が返る
print(f1 == f2 == f3)
# => True
field
を使うとその他さまざまなカスタマイズを行うことができます。 Python 3.7 における field
の宣言部は次のようになっているので、実際に利用するときは事前に詳細を確認するようにしてください。
dataclasses.field(
*,
default=MISSING,
default_factory=MISSING,
repr=True,
hash=None,
init=True,
compare=True,
metadata=None)
かんたんにですが、以上 dataclasses
の基本的な使い方についてでした。
最後に使いどころについて。
dataclasses
の使いどころ
私の認識では dataclasses
は namedtuple
の進化版的な位置づけのものです。
使い捨てのスクリプト作成等の場合は、単純に特殊メソッドの作成を省略したいときに気軽に使うとよさそうです。規模が大きめの開発では、アーキテクチャの境界をまたぐ情報の受け渡しに使用するシンプルなデータ構造やプレーンなドメインモデルクラス等で使うと便利かと思います。
dataclass
デコレータが追加してくれる特殊メソッドのうち、 __init__()
や __eq__()
は(自分で定義したいので)不要な場合も多そうですが、 __repr__()
は多くの場合そのまま使えて便利なのではないでしょうか。
dataclasses
の機能を濫用すると「 Explicit is better than implicit. 」に反するコードになりそうな気もしますが、今後 dataclasses
がクラス定義における定番と言われるくらいよく使われるようになれば、( Python に不慣れな人にはわかりづらいけれど)ある程度経験のある Pythonista には implicit というほどのものではなくなってくる、のかも、しれません。
というわけで、 Python 3.7 で導入された dataclasses
の説明でした。
同じく「特殊メソッドの自動定義」で人気のライブラリ attrs
との違いや dataclasses
が標準ライブラリに追加された経緯等について興味のある方には以下のページ等が参考になるかと思います。
- PEP 557 - Data Classes | peps.python.org
- why not just attrs? · Issue #19 · ericvsmith/dataclasses · GitHub
- differences / compatibility with attrs project · Issue #60 · ericvsmith/dataclasses · GitHub
関連記事
参考
- dataclasses ― Data Classes — Python 3 documentation
- cpython/dataclasses.py at 3.7 · python/cpython · GitHub ( CPython 3.7 の dataclasses のソースコード)
- Python 3.7からは「Data Classes」がクラス定義のスタンダードになるかもしれない