同僚と「デコレータの実装って難しい」という話から、「Pythonの組み込みのデコレータ、例えば staticmethod
はどう実装されているの?」という話になりました。私もけっこう長い間Pythonを使っているのですが、そういえば挙動を知らなかったので調べてみることにします。
話していた間は、self
を穴埋めするような実装(このドキュメントにあるような以下の実装)を想定していたのですが、ただオーバーヘッドを作っているだけなので、もっと良い実装をしているはずです。
# Using the non-data descriptor protocol, a pure Python version of :func:`staticmethod` would look like this:
class StaticMethod:
"Emulate PyStaticMethod_Type() in Objects/funcobject.c"
def __init__(self, f):
self.f = f
def __get__(self, obj, objtype=None):
return self.f
staticmethodとは
次のコードのように、クラス定義のメソッドの self
を省略するためのデコレータです。詳しくは公式ドキュメントを読んでください。
class C:
@staticmethod
def f(arg1, arg2, ...): ...
ちなみに私自身はあまり staticmethod
は使いません。スタティックメソッドは要らない子?という記事も2010年の時点で書かれている不憫な印象があります😅
ドキュメントによると
組み込み関数のドキュメントには以下のように記載されています。
静的メソッドについて詳しい情報は 標準型の階層 を参照してください。
リンク先のドキュメントにはこのように書かれています。
静的メソッド (static method) オブジェクト
静的メソッドは、上で説明したような関数オブジェクトからメソッドオブジェクトへの変換を阻止するための方法を提供します。静的メソッドオブジェクトは他の何らかのオブジェクト、通常はユーザ定義メソッドオブジェクトを包むラッパです。静的メソッドをクラスやクラスインスタンスから取得すると、実際に返されるオブジェクトはラップされたオブジェクトになり、それ以上は変換の対象にはなりません。静的メソッドオブジェクトは通常呼び出し可能なオブジェクトをラップしますが、静的オブジェクト自体は呼び出すことができません。静的オブジェクトは組み込みコンストラクタ staticmethod() で生成されます。
どうやら通常のメソッド定義では「クラス内で関数からメソッドに変換」していて、 staticmethod
でラップするとその変換が行われないようです。
実際に試したところ、現状のPython3.8では次のような挙動になっています。
# メソッドと静的メソッドを持つクラスを定義
class C:
def method(self):
pass
@staticmethod
def stmethod(self):
pass
# クラスの場合は両方ともfunction
print(type(C.method))
# => <class 'function'>
print(type(C.stmethod))
# => <class 'function'>
# インスタンスの場合はメソッドはmethodに変換されていて、静的メソッドはfunctionのまま
c = C()
print(type(c.method))
# => <class 'method'>
print(type(c.stmethod))
# => <class 'function'>
ただ、そのすぐ上、「内部型」のドキュメントには以下のようにも書かれています。
これらの定義は将来のインタプリタのバージョンでは変更される可能性がありますが、ここでは記述の完全性のために触れておきます。
実際のコードを見てみようと思ってCPythonのリポジトリを見たのですが、構造体の定義箇所を見つけた以上は読み解けませんでした。ここから実際に初期化されるときに上記のような挙動に実装されているのが確認できたら面白かったのですが…。
まとめ
「(現行のPython3.8では)クラス内で定義された関数が、methodに変換されており、staticmethod
でラップするとその変換処理を打ち消すことができる」ようです。ただ、過去のバージョンや、今後のバージョンでは実装方法が違う可能性があります。
CPythonの実装がどうなっているのか、もし詳しく調べられる方がいたらコメントを頂けると嬉しいです。