staticmethod
特にオブジェクトの動作に関連しているため、Pythonの記述子プロトコルの微妙な点を理解するのに少し助けが必要です。簡単な例から始めて、それを繰り返し拡張し、各ステップでの動作を調べます。
class Stub:
@staticmethod
def do_things():
"""Call this like Stub.do_things(), with no arguments or instance."""
print "Doing things!"
この時点では、これは期待どおりに動作しますが、ここで行われていることは少し微妙です。を呼び出すときStub.do_things()
、do_thingsを直接呼び出していません。代わりに、実際に呼び出すように、必要な関数を独自の記述子プロトコル内にラップStub.do_things
したインスタンスを参照します。これは、最初に必要な関数を返し、その後呼び出されます。staticmethod
staticmethod.__get__
>>> Stub
<class __main__.Stub at 0x...>
>>> Stub.do_things
<function do_things at 0x...>
>>> Stub.__dict__['do_things']
<staticmethod object at 0x...>
>>> Stub.do_things()
Doing things!
ここまでは順調ですね。次に、クラスのインスタンス化をカスタマイズするために使用されるデコレータでクラスをラップする必要があります。デコレータは、新しいインスタンス化を許可するか、キャッシュされたインスタンスを提供するかを決定します。
def deco(cls):
def factory(*args, **kwargs):
# pretend there is some logic here determining
# whether to make a new instance or not
return cls(*args, **kwargs)
return factory
@deco
class Stub:
@staticmethod
def do_things():
"""Call this like Stub.do_things(), with no arguments or instance."""
print "Doing things!"
さて、当然、この部分はそのままでstaticmethodsを壊すと予想されます。これは、クラスがデコレータの背後に隠されているためです。つまり、Stub
クラスではありませんが、そのインスタンスは、呼び出したときにfactory
インスタンスを生成できます。Stub
それはそう:
>>> Stub
<function factory at 0x...>
>>> Stub.do_things
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'function' object has no attribute 'do_things'
>>> Stub()
<__main__.Stub instance at 0x...>
>>> Stub().do_things
<function do_things at 0x...>
>>> Stub().do_things()
Doing things!
これまでのところ、私はここで何が起こっているのかを理解しています。staticmethods
私の目標は、クラスがラップされていても、期待どおりに機能する能力を回復することです。運が良ければ、Python stdlibにはfunctoolsと呼ばれるものが含まれています。これは、この目的のためだけにいくつかのツールを提供します。つまり、関数をラップする他の関数のように動作させます。そこで、デコレータを次のように変更します。
def deco(cls):
@functools.wraps(cls)
def factory(*args, **kwargs):
# pretend there is some logic here determining
# whether to make a new instance or not
return cls(*args, **kwargs)
return factory
今、物事は面白くなり始めています:
>>> Stub
<function Stub at 0x...>
>>> Stub.do_things
<staticmethod object at 0x...>
>>> Stub.do_things()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'staticmethod' object is not callable
>>> Stub()
<__main__.Stub instance at 0x...>
>>> Stub().do_things
<function do_things at 0x...>
>>> Stub().do_things()
Doing things!
待って……なに? functools
staticmethodをラッピング関数にコピーしますが、呼び出し可能ではありませんか?なぜだめですか?ここで何が恋しかったですか?
私はこれを少し遊んでいて、実際にこの状況で機能するように自分で再実装staticmethod
することを思いつきましたが、なぜそれが必要だったのか、これがこの問題の最善の解決策であるのかどうかはよくわかりません。完全な例は次のとおりです。
class staticmethod(object):
"""Make @staticmethods play nice with decorated classes."""
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
"""Provide the expected behavior inside decorated classes."""
return self.func(*args, **kwargs)
def __get__(self, obj, objtype=None):
"""Re-implement the standard behavior for undecorated classes."""
return self.func
def deco(cls):
@functools.wraps(cls)
def factory(*args, **kwargs):
# pretend there is some logic here determining
# whether to make a new instance or not
return cls(*args, **kwargs)
return factory
@deco
class Stub:
@staticmethod
def do_things():
"""Call this like Stub.do_things(), with no arguments or instance."""
print "Doing things!"
実際、期待どおりに機能します。
>>> Stub
<function Stub at 0x...>
>>> Stub.do_things
<__main__.staticmethod object at 0x...>
>>> Stub.do_things()
Doing things!
>>> Stub()
<__main__.Stub instance at 0x...>
>>> Stub().do_things
<function do_things at 0x...>
>>> Stub().do_things()
Doing things!
装飾されたクラス内でstaticmethodを期待どおりに動作させるには、どのようなアプローチを取りますか?これが最善の方法ですか?__call__
これが大騒ぎせずに機能するために、組み込みのstaticmethodがそれ自体で実装されないのはなぜですか?
ありがとう。