📌

python3での動的分岐プラクティス

に公開

次のような記事がある。内容的には大きな反論はない。

https://qiita.com/kotauchisunsun/items/d03c1e6936ffb250e4a1#どうせやる気はなくなる

ただ話の流れで、なかなか興味深いサンプルソースがあった。

https://github.com/kamawanu/zenn.dev-kamawanu-codes/blob/main/python3s/qiitad03c1e6936ffb250e4a1/v0.py#L1-L30

小生の都合で、若干修正はしているが、内容的には変わってない。
氏の主張は、「リフレクションを使ってるために、IDEの支援機能が効かずにリファクタリングが困難になる」というものだ。

主張はまあわかる。だが、この理由をいくつか掘り下げていけば次善策はある。逆説的にリファクタリングは可能である。

曲者のvalidate_objだが、中でgetattrを使ってるところを見ると、現時点の実装では矛盾はなさそうであるという推測はできる。
getattrは、指定のクラスに指定の要素がなければAttributeErrorで落ちるからだ。
であれば、それを手がかりにtypehintをつけていくことは可能だろう。

ただ小生にはもっと手前の話に疑問がある。type(self)のnameを使うことを思いつくひとが、こんなに頭の悪いやりかたをするのだろうか?
あるいはUser/ArticleにはValidatorを「書き変えたくない」のだろうか?

そうでないなら、最もeasyな手は、それぞれのメソッドを直接該当クラスに貼り付けてしまうことだ。

https://github.com/kamawanu/zenn.dev-kamawanu-codes/blob/main/python3s/qiitad03c1e6936ffb250e4a1/v1.py#L4-L13

幸か不幸か、もとのValidatorではstaticmethodになっており、それを普通のクラスに貼り付けることで、しれっとselfが第1引数で渡るようになる。
こうなれば、validate_objの仕事は、定義済みのはずの関数を呼ぶだけだ。ほとんど何もしてないので、ふつーにobjのvalidate()を呼ぶのと変わらないし、最終的にはそう修正するほうがよいだろう。

ただこのサンプルではクラス2つだけなので1個づつ修正してもやっていけるが、これが大量にある場合にそれぞれ名前を変えて貼り付けるのは手間は手間である。

今度はValidatorもUser/Articleも極力修正しないという方向で考えてみる。

pythonにはデコレータという、他に類を見ない言語機能があるのを思い出してほしい。

https://github.com/kamawanu/zenn.dev-kamawanu-codes/blob/main/python3s/qiitad03c1e6936ffb250e4a1/v2.py#L5-L33

これならデコレータをあちこちに貼り付けて回るだけで作業がおわる。
関連箇所にはデコレータがつくのでValidatorとの関与の手がかりにはなる。
相変わらずtype(cls)を使ってしまってはいるのでいまいち釈然とはしない。

この手の処理の実装方法として、小生が推奨したいのは次だ。

https://github.com/kamawanu/zenn.dev-kamawanu-codes/blob/main/python3s/qiitad03c1e6936ffb250e4a1/v3.py#L4-L19

先に例示した言語機能としてのデコレータそのものを思い出してほしいのだが、pythonではクラスそのものを動的定義して、その上でそのクラス自身を関数から返すことができる。pythonでの”クラス”は、厳密にはtype型のインスタンスであるからこそ可能になる。

そしてclass定義での名前の後の括弧は、継承元の”クラス”を指定するわけだが、同じくここはtype型インスタンスを流し込んでやればよい。
かくしてValidatorのメソッドと、利用クラスが完全に結合して、動的要素が関与しない状態になった。

小生個人的には、ここまでやればかなり満足ではある。よく眠れそうだ。だが主義によってはやりすぎと取られることも多いだろう。

ただ本稿の後半で紹介した手法をつかえば、大抵のケースに対応できると思われる。ご参考までに。

Discussion