Python に同梱のライブラリ dataclasses
が提供するデコレータ dataclass
の引数についてまとめました。
dataclasses
って何だっけという方は以下のページなどでご確認ください。
確認時のバージョン
- Python 3.10
利用可能な引数
Python 3.10 時点で dataclass()
で利用可能な引数は次のとおりです。
@dataclasses.dataclass(*, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, kw_only=False, slots=False)
引数 | 導入バージョン | デフォルト値 | 説明 |
---|---|---|---|
init |
3.7 | True |
__init__() の自動生成を行う |
repr |
3.7 | True |
__repr__() の自動生成を行う |
eq |
3.7 | True |
__eq__() の自動生成を行う |
order |
3.7 | False |
__lt__() __le__() __gt__() __ge__() の自動生成を行う |
unsafe_hash |
3.7 | False |
__hash__() の自動生成を強制的に行う |
frozen |
3.7 | False |
フィールドへの再代入をできなくする |
match_args |
3.10 | True |
__match_args__ の自動生成を行う |
kw_only |
3.10 | False |
__init__() の引数をキーワード専用引数にする |
slots |
3.10 | False |
__slots__ の自動生成を行う |
dataclasses
自体が追加された 3.7 のときからあったものと 3.10 で追加されたものに分かれます。
以下上から順に見ていきます。
init
True
の場合は __init__()
メソッドの自動生成を行う。
False
の場合は行わない。
ただし、 True
の場合でも __init__()
が定義されていれば自動生成は行わない。
True
:
@dataclass(init=True)
class Product:
id: str
name: str
# `id` `name` を引数として受け取る `__init__()` が定義されている:
p1 = Product(id='sample', name='サンプル')
assert p1.id == 'sample'
assert p1.name == 'サンプル'
False
:
@dataclass(init=False)
class Product:
id: str
name: str
# `id` `name` を受け付ける `__init__()` メソッドが定義されてないのでエラーになる
p1 = Product(id='sample', name='サンプル')
# => TypeError: Product() takes no arguments
repr
True
の場合は __repr__()
メソッドの自動生成を行う。
False
の場合は行わない。
ただし、 True
の場合でも __repr__()
が定義されていれば自動生成は行わない。
True
:
@dataclass(repr=True)
class LineItem:
product_id: str
quantity: int
subtotal: int
# クラス名と一連のフィールドからなる repr 文字列が出力される:
li1 = LineItem(product_id='sample', quantity=3, subtotal=300)
f'明細: {li1!r}'
# => "明細: LineItem(product_id='sample', quantity=3, subtotal=300)"
False
:
@dataclass(repr=False)
class LineItem:
product_id: str
quantity: int
subtotal: int
# `object` のデフォルトの repr 文字列が出力される:
li1 = LineItem(product_id='sample', quantity=3, subtotal=300)
f'明細: {li1!r}'
# => "明細: <__main__.LineItem object at 0x10783bac0>"
eq
True
の場合は __eq__()
メソッドの自動生成を行う。
False
の場合は行わない。
ただし、 True
の場合でも __eq__()
が定義されていれば自動生成は行わない。
True
:
@dataclass(eq=True)
class Position:
x: int
y: int
# アトリビュートの値で同値判定が行える:
p1, p2 = Position(x=5, y=7), Position(x=5, y=7)
assert p1 == p2
False
:
@dataclass(eq=False)
class Position:
x: int
y: int
# アトリビュートの値で同値判定が行えない:
# (この場合はベースクラスの `object` の `__eq__()` を利用するのでオブジェクト ID での比較になる)
p1, p2 = Position(x=5, y=7), Position(x=5, y=7)
assert p1 == p2
# => AssertionError
order
True
の場合は __lt__()
__le__()
__gt__()
__ge__()
の自動生成を行う。
False
の場合は行わない。
ただし、 True
の場合に __lt__()
__le__()
__gt__()
__ge__()
のいずれかが定義されていれば TypeError
があがる。
True
:
@dataclass(order=True)
class Version:
major: int
minor: int
patch: int
# 大小比較ができる:
ver_x = Version(3, 10, 12)
ver_y = Version(3, 9, 18)
assert ver_x > ver_y
False
:
@dataclass(order=False)
class Version:
major: int
minor: int
patch: int
# 大小比較ができない:
ver_x = Version(3, 10, 12)
ver_y = Version(3, 9, 18)
assert ver_x > ver_y
# => TypeError: '>' not supported between instances of 'Version' and 'Version'
unsafe_hash
False
の場合は、 eq
と frozen
がともに True
でなおかつ __hash__()
が未定義の場合のみ __hash__()
の自動生成が行われる。
True
の場合は、 eq
や frozen
の値によらず __hash__()
が未定義なら __hash__()
の自動生成が行われる。
True
:
@dataclass(unsafe_hash=True)
class Product:
brand_id: str
sku_id: str
# `eq` と `frozen` がともに `True` でなくても `__hash__()` が追加される:
p1 = Product('brand a', 'sku x')
products = {p1}
False
:
@dataclass(unsafe_hash=False)
class Product:
brand_id: str
sku_id: str
# デフォルトでは `frozen=False` なので `__hash__()` が追加されない:
p1 = Product('brand a', 'sku x')
products = {p1}
# => TypeError: unhashable type: 'Product'
ちなみに、 eq=False
で unsafe_hash=False
の場合は __hash__()
の自動生成は行われませんが、このときはベースクラスの __hash__()
が使われるのでそのクラスのオブジェクトは hashable になります。
そのため、 __hash__()
の自動生成を行わない設定にしたらそのオブジェクトが unhashable になるというわけではありません。
このあたりの仕組みは少し複雑なので、 unsafe_hash=True
を利用するときはドキュメントと dataclasses
のソース内のインラインコメントをよく読んで仕組みを正しく理解してから使うのがよいと思います。
frozen
True
の場合はインスタンス生成後にフィールドへの再代入ができなくなる。
False
の場合はフィールドへの再代入ができる。
True
:
@dataclass(frozen=True)
class Coordinate:
lon: float
lat: float
# フィールドへの再代入が行えない:
c1 = Coordinate(lon=134.998, lat=34.643)
c1.lon = -119.5
# => FrozenInstanceError: cannot assign to field 'lon'
False
:
@dataclass(frozen=False)
class Coordinate:
lon: float
lat: float
# フィールドへの再代入が行える:
c1 = Coordinate(lon=134.998, lat=34.643)
c1.lon = -119.5
frozen=True
はいわゆる immutable なオブジェクトを作りたいときに便利です。
match_args
True
の場合はクラス変数 __match_args__
の自動生成が行われる。
False
の場合は __match_args__
の自動生成が行われない。
__match_args__
は Python 3.10 で導入された match
文でのサブパターンマッチに使われる。
True
:
@dataclass(match_args=True)
class Rectangle:
width: int
height: int
shape1 = Rectangle(width=4, height=3)
# `match ~ case` 文で引数のサブパターンマッチングが行われる:
match shape1:
case Rectangle(w, h):
print(f'width: {w} / height: {h}')
case _:
pass
# => 'width: 4 / height: 3'
False
:
@dataclass(match_args=False)
class Rectangle:
width: int
height: int
shape1 = Rectangle(width=4, height=3)
# `match ~ case` 文で引数のサブパターンマッチングが行われない:
match shape1:
case Rectangle(w, h):
print(f'width: {w} / height: {h}')
case _:
pass
# => TypeError: Rectangle() accepts 0 positional sub-patterns (2 given)
参考: Python 公式ドキュメントや PEP の __match_args__
の説明箇所:
object.__match_args__
| Data model — Python 3.10 documentation- PEP 634 – Structural Pattern Matching: Specification | peps.python.org
kw_only
True
の場合は、生成される __init__()
においてすべての引数がキーワード専用引数( keyword-only )になる。
False
の場合は、通常の引数になる。
True
:
@dataclass(kw_only=True)
class Person:
name: str
# キーワード引数は OK
p1 = Person(name='太郎')
# 位置引数は NG
p2 = Person('次郎')
# => TypeError: Person.__init__() takes 1 positional argument but 2 were given
False
:
@dataclass(kw_only=False)
class Person:
name: str
# キーワード引数・位置引数ともに OK
p1 = Person(name='太郎')
p2 = Person('次郎')
dataclass
におけるキーワード専用引数の指定方法として他にも dataclasses.KW_ONLY
があるので、興味がある方はそちらもチェックしておくとよいと思います。
slots
True
の場合は __slots__
アトリビュートの自動生成が行われる。
False
の場合は __slots__
の自動生成が行われない。
ただし、 True
の場合に __slots__
が定義されていれば TypeError
があがる。
True
:
import sys
@dataclass(slots=True)
class Page:
title: str
path: str
# 定義されたフィールド以外のアトリビュートを追加できない:
p1 = Page(title='サイトについて', path='/about')
p1.body = 'このサイトは…'
# => AttributeError: 'Page' object has no attribute 'body'
# メモリ使用量が比較的小さい:
sys.getsizeof(Page(title='', path='').__slots__)
# => 56
False
:
import sys
@dataclass(slots=False)
class Page:
title: str
path: str
# 定義されたフィールド以外のアトリビュートを動的に追加できる:
p1 = Page(title='サイトについて', path='/about')
p1.body = 'このサイトは…'
assert p1.body == 'このサイトは…'
# メモリ使用量が比較的大きい:
sys.getsizeof(Page(title='', path='').__dict__)
# => 104
__slots__
については Python 公式ドキュメントの Data model のページで説明されているので興味のある方はチェックしてみてください。