この記事は^の記事の続きです。^を読んでから読むと良いと思います。 なお、今回のサンプルコードはこちらにあります。(間違いなどありましたらご指摘ください)
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]) # classレベルでの比較が可能です True >>> attr.asdict(sc) # class -> dict 変換 {'a_number': 1, 'list_of_numbers': [1, 2, 3]} >>> SomeClass() # default値も使えます. factory=list の場合はempty listになります。 SomeClass(a_number=42, list_of_numbers=[]) >>> C = attr.make_class("C", ["a", "b"]) # methodでクラスがつくれちゃいます。 >>> 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をつかうと以下のようにかける
# slots=Trueをつけるとslotsが有効に @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の公式ブログ
# use NamedTuple 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__
の効果
-
Scalaのcase class, Kotlinのdata class的なものだと思ってもらうと良いと思います(プロパティはデフォルトではimmutableではないですが)↩
-
https://devguide.python.org/#status-of-python-branches Python3.5/3.6もまだEOLでない↩