rob*_*bru 9 python decorator python-2.7
我需要一些帮助来理解Python中描述符协议的细微之处,因为它特别涉及staticmethod对象的行为.我将从一个简单的例子开始,然后迭代地扩展它,检查它在每一步的行为:
class Stub:
@staticmethod
def do_things():
"""Call this like Stub.do_things(), with no arguments or instance."""
print "Doing things!"
Run Code Online (Sandbox Code Playgroud)
在这一点上,这表现得如预期的那样,但这里发生的事情有点微妙:当你打电话时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!
Run Code Online (Sandbox Code Playgroud)
到现在为止还挺好.接下来,我需要将类包装在将用于自定义类实例化的装饰器中 - 装饰器将确定是允许新的实例化还是提供缓存的实例:
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!"
Run Code Online (Sandbox Code Playgroud)
现在,这个部分当然会被打破静态方法,因为类现在隐藏在它的装饰器后面,即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!
Run Code Online (Sandbox Code Playgroud)
到目前为止,我了解这里发生了什么.我的目标是恢复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
Run Code Online (Sandbox Code Playgroud)
现在,事情开始变得有趣:
>>> 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!
Run Code Online (Sandbox Code Playgroud)
等等.... 什么? 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!"
Run Code Online (Sandbox Code Playgroud)
确实,它的工作方式完全如预期
>>> 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!
Run Code Online (Sandbox Code Playgroud)
你会采取什么方法使静态方法在装饰类中表现如预期?这是最好的方法吗?为什么内置静态方法不能__call__自行实现,以便它可以毫不费力地工作?
谢谢.
问题是您正在将类型Stub从类更改为函数。这是一个相当严重的违规行为,出现问题也就不足为奇了。
您的 s 被破坏的技术原因staticmethod是functools.wraps通过将__name__,__doc__等__module__(来源:http://hg.python.org/cpython/file/3.2/Lib/functools.py)从包装实例复制到包装器来工作,同时__dict__从包装实例的__dict__. 现在应该很明显为什么staticmethod不起作用了 - 它的描述符协议是在函数而不是类上调用的,因此它放弃返回绑定的可调用对象,而只返回其不可调用的自身。
当实际做你感兴趣的事情(某种单例?)时,你可能希望你的装饰器返回一个__new__具有所需行为的类。您无需担心__init__被称为不需要的,只要您的包装类__new__实际上不返回包装类类型的值,而是返回包装类的实例:
def deco(wrapped_cls):
@functools.wraps(wrapped_cls)
class Wrapper(wrapped_cls):
def __new__(cls, *args, **kwargs):
...
return wrapped_cls(*args, **kwargs)
return Wrapper
Run Code Online (Sandbox Code Playgroud)
wrapped_cls请注意装饰器的参数(在包装类中被封闭)和 的cls参数之间的区别Wrapper.__new__。
functools.wraps请注意,在包装类的类上使用是完全可以的- 只是不能在包装函数的类上使用!
您还可以修改包装的类,在这种情况下您不需要functools.wraps:
def deco(wrapped_cls):
def __new__(cls, *args, **kwargs)
...
return super(wrapped_cls, cls)(*args, **kwargs)
wrapped_cls.__new__ = classmethod(__new__)
return wrapped_cls
Run Code Online (Sandbox Code Playgroud)
但请注意,此方法最终将__init__调用现有实例,因此您必须解决该问题(例如,通过__init__在现有实例上进行短路)。
作为附录:也许可以让你的函数包装类装饰器在你知道的情况下工作,但你仍然会遇到问题 - 例如,isinstance(myObject, Stub)没有机会像Stub不再是了type!