我需要一点帮助来理解 Python 中描述符协议的微妙之处,因为它与staticmethod
对象的行为特别相关。我将从一个简单的示例开始,然后迭代地扩展它,检查它在每个步骤中的行为:
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!"
现在,自然这部分原样会破坏静态方法,因为该类现在隐藏在它的装饰器后面,即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 标准库包含了一个叫做 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
,让它在这种情况下运行,但我真的不明白为什么它是必要的,或者这是否是这个问题的最佳解决方案. 这是完整的示例:
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!
您将采取什么方法使静态方法在装饰类中按预期运行?这是最好的方法吗?为什么内置的静态方法不__call__
自己实现,以便它可以正常工作而不大惊小怪?
谢谢。