はじめに
デスクリプタ完全に理解した! nikkieです(※エンジニアの"完全に理解した"はすぐ分からなくなるやつです)
先日のみんなのPython勉強会#98のLTをきっかけに興味を持ったデスクリプタについて素振りしました。
※触り始めで深い理解はこれからですし、間違っているかもしれません。お気づきの点がありましたら@ftnextまでお願いします
目次
stapyのLT 「5分で理解するディスクリプタ」
みんなのPython勉強会#98のLT枠で発表した内容です!
— tamtam (@TamtamFitness) 2023年10月12日
「5分で理解するディスクリプタ」#stapy https://t.co/hrFLGFu63J
こちらのLTのおかげで完全に理解しました🙌
金額関連の実装例があったので
デスクリプタ HowTo ガイドを参照しつつ手を動かしました
デスクリプタを使ったMoneyクラス実装例
ミノ駆動本 3章のMoneyクラスをデスクリプタで実装していきます。
過去にdataclassで実装したもの1をデスクリプタを使って書き換えます。
- dataclassのクラス変数として
MoneyValueDescriptor
をインスタンス化しています` MoneyValueDescriptor
は一度だけ値(amount=金額)を代入できるようにしました- 一度だけの機会は、Moneyクラスの
__init__
となります - 金額が0以上というバリデーションはデスクリプタ側に移りました
- 一度だけの機会は、Moneyクラスの
ドキュメント参考箇所
- バリデータクラス https://docs.python.org/ja/3/howto/descriptor.html#validator-class を参考に
- https://docs.python.org/ja/3/howto/descriptor.html#invocation-from-an-instance
a.x に対するデスクリプタが見つかった場合、 desc.__get__(a, type(a)) という形式で呼び出されます。
- 引数としてデスクリプタに渡るため、デスクリプタはインスタンスにアクセスできるという理解です
- 組み込みのpropertyのPython版
- https://docs.python.org/ja/3/howto/descriptor.html#properties
self.__x
のようにインスタンスの属性にアクセスしていたので、これを真似ました
デスクリプタにした効果
object.__setattr__
2への耐性を獲得したようです!(理由はわかっていません)
dataclassで実装したときは、frozen=True
を指定しても、object.__setattr__
で値を書き換えることができてしまいました。
デスクリプタを使った実装では、この穴が塞がれています。
#ミノ駆動本 Pythonで不変、思ってたより難しそうな感触。
— nikkie にっきー (@ftnext) 2022年5月2日
dataclassでfrozen=Trueがいいかなと思っていたのですが、その実装でも使っているobject.__setattr__があるので不変ではないことに気づいちゃいました😅https://t.co/EMTxoa0NqB
namedtupleだとこれは効かない(例外送出)ですね。宿題山積み
「インスタンスの値を書き換えられないようにするのは、Pythonでは無理なんじゃないか」と考えていたので、今回発見できてよかったです。
ほか
- 「金額が0以上」というビジネスルールがデスクリプタに切り出された
- 他の箇所でも再利用できるかも
宿題事項
@dataclass
の旨味が少ない__init__
,__repr__
,__eq__
全部実装した- デコレートしなくていいのかも
- デスクリプタの
__set_name__
- マングリングを考慮した属性の組み立てのあたりがスッキリする?
money.spam = 42
のようにユーザが属性を追加できる- dataclassでは
frozen=True
が指定してあると、例外を送出します
- dataclassでは
終わりに
初めてデスクリプタに触った素振り記事でした。
「DjangoモデルやPydanticで見たあれらはデスクリプタだったのか!」と気付き、デスクリプタ HowTo ガイドからどんな実装をしていそうか、少しだけですが見当が付くようになりました。
使いこなしにはまだまだ知識が必要だと思うので、引き続き素振りしていきます