masahito.hatenablog.com
この記事は^の記事の続きです。^を読んでから読むと良いと思います。
なお、今回のサンプルコードはこちらにあります。(間違いなどありましたらご指摘ください)
github.com
TL;DR
- attrsのつかいどころ
- 今Python3.5 or 3.6を使っている。今後dataclassを使いたい
- dataclassで slotsなどを使いたい場合にボイラープレートコードを避けたい
- slotsの効果
attrsとは
www.attrs.org
簡単にいうとPython でclassを書くときのボイラープレートになるところがスッキリかけるものです。 1
Overviewで書かれているコードを軽く解説します。
>>> import attr
>>> @attr.s
... class SomeClass(object):
... a_number = attr.ib(default=42)
... list_of_numbers = attr.ib(factory=list)
...
... def hard_math(self, another_number):
... return self.a_number + sum(self.list_of_numbers) * another_number
>>> sc = SomeClass(1, [1, 2, 3])
>>> sc
SomeClass(a_number=1, list_of_numbers=[1, 2, 3])
>>> sc.hard_math(3)
19
>>> sc == SomeClass(1, [1, 2, 3])
True
>>> sc != SomeClass(2, [3, 2, 1])
True
>>> attr.asdict(sc)
{'a_number': 1, 'list_of_numbers': [1, 2, 3]}
>>> SomeClass()
SomeClass(a_number=42, list_of_numbers=[])
>>> C = attr.make_class("C", ["a", "b"])
>>> C("foo", "bar")
C(a='foo', b='bar')
attrsこぼれ話
じつはこのライブラリ Python 3.7 で入った dataclass
のもとになった実装だったりします。
詳しくはpepを確認してください。
www.python.org
ちなみに dataclass入れるよりattrsをPythonで標準でいれちゃえば? という声もあったみたいですがそれはやめたみたいです。(guidoがstdlibに入れるのに反対している、この辺を参照。)
github.com
「attrsはいいもんだが、これがファイナルアンサーじゃないしいいところだけ取り出そうぜ」という感じっぽい。
attrsのdataclassとの使い分け
attrsを使うべき
- 今Python3.5 or 3.6を使っている. 2
- 特に現在namedtupleを使っているが、3.7にupgradeするタイミングでdataclassの使用を考えている
- dataclassで slotsなどをつかいたい(for Python3.7以降)
@dataclasses.dataclass
class B:
__slots__ = ('b',) # __slots__ 追加
b: int
例えばこんな感じでdataclassでも __slots__
が使えることを前回紹介した。が、 __slots__
に設定するところとプロパティを両方書かないといけないため
実行時にエラーになることが結構おおい。
これをattrsをつかうと以下のようにかける
@attr.s(auto_attribs=True, slots=True)
class AttrsMessage:
sender: str
recipient: str
body: str
個人的にはdataclassを使いたくなったら無条件でattrsを使うのをおすすめしたいと思っています。
ただプロジェクトの考え方でなるべく使うライブラリを増やしたくない場合はdataclassでも問題はないかもしれません。
が、複数人で開発するときではミスをするばあいが多いため入れておいたほうが便利だと思います。
余談 __slots__
の効用
Pympler を使ってPython3.8での __slots__
を使ったとき使わないときのメモリ使用量を比べてみました。
使い方についてはこちらが詳しいです。
Asizeof モジュールの使い方 | Xoxzoの公式ブログ
Message = NamedTuple("Message", [("sender", str), ("recipient", str), ("body", str)])
if sys.version_info.minor >=7:
import dataclasses
@dataclasses.dataclass
class DataClassMessage:
sender: str
recipient: str
body: str
@dataclasses.dataclass
class SlotMessage:
__slots__ = ["sender", "recipient", "body"]
sender: str
recipient: str
body: str
@attr.s(auto_attribs=True, slots=True, weakref_slot=False)
class AttrsMessage:
sender: str
recipient: str
body: str
if __name__ == "__main__":
pos = Message(
sender="sender@exmaple.com",
recipient="recipient.example.com",
body="Hello, World!",
)
if sys.version_info.minor >=7:
simple = DataClassMessage(
sender="sender@exmaple.com",
recipient="recipient.example.com",
body="Hello, World!",
)
slotted = SlotMessage(
sender="sender@exmaple.com",
recipient="recipient.example.com",
body="Hello, World!",
)
print(
"Dataclass %d, Slotted dataclass %d"
% asizeof.asizesof(simple, slotted)
)
attrs = AttrsMessage(
sender="sender@exmaple.com",
recipient="recipient.example.com",
body="Hello, World!",
)
Dataclass 536, Slotted dataclass 56
NamedTuple 272, Attrs 56
こんな感じで3propertiesのclassでも __slots__
のありなしで 10倍程度メモリ使用量が違ったりします。
特に実行回数が多い関数の返り値などでdataclassを使用する場合は使用メモリのためにも使っておくことが無難です。
まとめ
本記事では以下の説明をしました。
- attrsのつかいどころ
- 今Python3.5 or 3.6を使っている。今後dataclassを使いたい
- dataclassで
__slots__
などを使いたい場合にボイラープレートコードを避けたい
__slots__
の効果