Keep on moving

あんまりまとまってないことを書きますよ

DataClass 3.10での機能追加(kw_only, slots)

こんにちはPython界のレアキャラです。

dataclassから機能が追加され attrsであった機能が取り込まれました。 以前書いた記事のアップデートをしておこうと思います。

TL;DR

  • dataclassにkw_onlyと slotsが入った
  • attrsのつかいどころ
    • ボイラープレートコードを避けたい.(特にdataclassにない機能を利用したい)
    • 今Python3.9 以下を使っているし、今後dataclassを使いたい.
  • ドキュメントをきちんと読むと色々書いてある

docs.python.org

kw_only

https://docs.python.org/ja/3.10/glossary.html#term-parameter キーワード専用フィールドを設定するためのもの.

>>> # クラスの全フィールドをキーワード専用にするもの
>>> @dataclass(kw_only=True)
... class A:
...   a: int
...   b:int
... 
>>> a = A(1,2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: A.__init__() takes 1 positional argument but 3 were given
>>> a = A(a=1,b=2)
>>> a
A(a=1, b=2)

ちなみに1フィールドだけ キーワード専用にしたいときは dataclasses.field でできる。

>>> from dataclasses import field
>>> @dataclass
... class B:
...   x:int
...   y:int = field(kw_only=True)
... 
>>> B(1,2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: B.__init__() takes 2 positional arguments but 3 were given
>>> B(1,y=2)
B(x=1, y=2)
>>> 

ちなみにattrsでは以下のように書く.attrsでは3.9以前のバージョンでも使える.

from attrs import define, field

@define(kw_only=True)
class A:
  a: int
  b: int

@define
class A:
  a: int
  b: int = field(kw_only=True)

slots オプション

全俺が欲しかったもの。 以前は明示的に __slots__をつけないといけなかった。3.10からはこんな感じで書ける

from dataclasses import dataclass

## 3.9 以前のスタイル
@dataclass
class SlotedMessage:
    __slots__ = ["sender", "recipient", "body"]
    sender: str
    recipient: str
    body: str

## 3.10からはslotsを指定する
@dataclass(slots=True)
class SlotedMessage:
    sender: str
    recipient: str
    body: str

以前試した メモリの使用量見てみよう。

masahito.hatenablog.com

ちなみに実行前に pymperをinstallしてほしい。

pip install pympler
from typing import NamedTuple
from pympler import asizeof
from attrs import define
from dataclasses import dataclass

Message = NamedTuple("Message", [("sender", str), ("recipient", str), ("body", str)])


@dataclass
class DataClassMessage:
    sender: str
    recipient: str
    body: str


@dataclass(slots=True)
class SlotedMessage:
    sender: str
    recipient: str
    body: str


@define(auto_attribs=True, slots=True, weakref_slot=False)
class AttrsMessage:
    sender: str
    recipient: str
    body: str


if __name__ == "__main__":
    d = {
        "sender": "sender@exmaple.com",
        "recipient": "recipient.example.com",
        "body": "Hello, World!",
    }
    message = Message(**d)
    simple_data = DataClassMessage(**d)
    slotted = SlotedMessage(**d)
    attrs = AttrsMessage(**d)
    print(
        "NamedTuple %d, Dataclass %d, Slotted dataclass %d, attrs %d"
        % asizeof.asizesof(message, simple_data, slotted, attrs)
    )
% python aaa.py    
NamedTuple 272, Dataclass 528, Slotted dataclass 56, attrs 56

ここまでくると attrsのweakre_slots も入れて欲しかった気がする.

久しぶりにdataclassのドキュメントを読み返すことで発見がたくさんありました。 Python 開発者、ドキュメント書いてくれている皆様に感謝!