@singledispatchmethodとは
きっかけ
コードレビュー中に見かけたので、調査
2, 3個の個人ブログやらを経由してやっと理解したので、ここでまとめておきたい
あと前提の知識が必要なやつだったので、一気に理解できると嬉しいよねって気持ち
ちなみに @singledispatchmethod
を理解するにあたり、以下の順番で理解すると良さそう
- ジェネリック関数
singledispatch
singledispatchmethod
←今回学ぶやつ
前置き長いですが、順番に理解する方が意外と近道かもです 🐥
ジェネリック関数
- 「第一引数の型に応じて、異なる処理を行う関数」
ざっくりイメージとしては
def function(obj): if type(obj) == int: return print(f'int型が渡されました,obj={obj}') if type(obj) == str: return print(f'string型が渡されました,obj={obj}') # int型渡す function(1) # string型を渡す function('moji') ===========結果================== int型が渡されました,obj=1 string型が渡されました,obj=moji
こんな感じで引数の型によって function
関数の挙動が変わる
こういうやつをジェネリック関数 というらしい
もっと詳しくの方は、以下を参考にしてください。
https://retroid2016.com/2018/08/09/【pyhton】ジェネリック関数【備忘録】/
singledispatch
- Pythonから提供されているライブラリである「functools」の一部
公式ライブラリは以下ですが、まぁ・・・わからなかった
https://docs.python.org/ja/3/library/functools.html#functools.singledispatch
- ジェネリック関数を定義するためのデコレータ
- つまり、ジェネリック関数を作りたい!ってなったら、関数の先頭につけるデコレータ
- クラスの中にある関数(正しくはメソッド と言いますが)ではない、関数用のデコレータ
ジェネリック関数の説明で書いたソースコードを、singledispatch
で書き換えてみる
from functools import singledispatch @singledispatch def function(obj): <- 元となる関数名 return print(f'{type(obj)}型が渡されました, obj={obj}') # int型がきた時の関数の挙動を宣言 # ちなみに関数名が 「 _ 」なのは、何でもいいよって意図らしいです @function.register <- 元となる関数名.register で定義する def _(obj: int): return print(f'int型が渡されました,obj={obj}') # string型が来たときの関数の挙動を宣言 @function.register def _(obj: str): return print(f'string型が渡されました,obj={obj}') # int型渡す function(1) # string型を渡す function('moji') # list型を渡す function([1, 2, 3]) ===========結果================== int型が渡されました,obj=1 string型が渡されました,obj=moji <class 'list'>型が渡されました, obj=[1, 2, 3]
ちゃんと型によって挙動が変わった・・・!
すごい便利なデコレータですね 👀
では、やっとですが、本来知りたいやついきましょう。
ちなみに、このsingledispatch
がバッチリ理解できた人は、もう singledispatchmethod
も理解したに等しいですので、ご安心を。。。
singledispatchmethod
- 同じく、Pythonから提供されているライブラリである「functools」の一部
まぁ、同じく公式ドキュメント難しい
https://docs.python.org/ja/3/library/functools.html#functools.singledispatchmethod
singledispatch
は関数用でしたが、singledispatchmethod
はメソッド用- つまり、クラスの中とかで定義されたメソッドをジェネリック関数にしたいとなったら、こっちを使いましょう ということ
- そして使い方は、
singledispatch
と一緒です 🙌
singledispatch
で書いたソースコードをクラスの中のメソッドにして、singledispatchmethod
で書き直してみましょう
from functools import singledispatchmethod class Func: @singledispatchmethod def function(self, obj): return print(f'{type(obj)}型が渡されました, obj={obj}') # int型がきた時の関数の挙動を宣言 @function.register def return_int(self, obj: int): <- 今回はメソッド名をつけてみた return print(f'int型が渡されました,obj={obj}') @function.register def return_str(self, obj: str): return print(f'string型が渡されました,obj={obj}') # Funcクラスを作成 func = Func() # int型渡す func.function(1) <- 元の関数名で呼んであげるで良さそう # string型を渡す func.function('moji') # list型を渡す func.function([1, 2, 3]) ===========結果================== int型が渡されました,obj=1 string型が渡されました,obj=moji <class 'list'>型が渡されました, obj=[1, 2, 3]
ちなみに他のデコレータと一緒に使いたいってなった時は、以下のようにsingledispatchmethod
を一番外にして書いてあげると良いらしい
今回は staticmethod
を追加してあげる
=> については以下を見るとわかりやすいかも
https://www.lifewithpython.com/2014/02/python-difference-between-staticmethod-and-classmethod.html
from functools import singledispatchmethod class Func: @singledispatchmethod @staticmethod def function(obj): return print(f'{type(obj)}型が渡されました, obj={obj}') # int型がきた時の関数の挙動を宣言 @function.register def return_int(obj: int): return print(f'int型が渡されました,obj={obj}') @function.register def return_str(obj: str): return print(f'string型が渡されました,obj={obj}') # int型渡す Func.function(1) # string型を渡す Func.function('moji') # list型を渡す Func.function([1, 2, 3])
ちなみに、今回調べていて、公式ドキュメントを参考に書いてみたら、動かない現象を発見しましたので、一応メモ
以下のコードを動かすと
class Negator: @singledispatchmethod @classmethod def neg(cls, arg): raise NotImplementedError("Cannot negate a") @neg.register @classmethod def _(cls, arg: int): return -arg @neg.register @classmethod def _(cls, arg: bool): return not arg
以下のエラーが出ます
TypeError: Invalid first argument to `register()`: <classmethod object at 0x1043063d0>. Use either `@register(some_class)` or plain `@register` on an annotated function.
↑のコードは、 Python3.8.1 と3.8.3 でしか動作しないコードなのでご注意を・・・
どう修正すればいいのかまでは、調べてないですが・・・
1年前はまだ解決してなさそうですが、今はどうなんでしょう・・・ 🤔
まとめ
こちらのデコレータを使うと、何がいいかってif文が乱立するソースコードではなくなり、可読性があがりそうって感じでしょうかね 👐
今回の調査で参考にしたサイトたち
https://marusankakusikaku.jp/python/standard-library/functools/#singledispatch-ジェネリック関数の定義