今天早上我遇到了一个有趣的问题。我有一个看起来像这样的基类:
# base.py
class Base(object):
@classmethod
def exists(cls, **kwargs):
# do some work
pass
还有一个看起来像这样的装饰器模块:
# caching.py
# actual caching decorator
def cached(ttl):
# complicated
def cached_model(ttl=300):
def closure(model_class):
# ...
# eventually:
exists_decorator = cached(ttl=ttl)
model_class.exists = exists_decorator(model_class.exists))
return model_class
return closure
这是我的子类模型:
@cached_model(ttl=300)
class Model(Base):
pass
问题是,当我实际调用 Model.exists 时,我收到关于参数数量错误的投诉!检查装饰器中的参数没有发现任何奇怪的事情 - 参数正是我所期望的,并且它们与方法签名匹配。如何向已使用 classmethod
装饰的方法添加更多装饰器?
并非所有模型都被缓存,但是 exists() 方法作为类方法存在于每个模型中,因此重新排序装饰器不是一个选项:cached_model
可以将类方法添加到 exists( ),那么是什么让 exists() 成为未缓存模型上的类方法呢?
最佳答案
在 Python 中,当一个方法被声明时,在一个函数体中,它就像一个函数—— 一旦类被解析并存在,通过“.”检索方法。运算符将该函数 - 即时 - 转换为方法。此转换确实将第一个参数添加到方法中(如果它不是静态方法)-
所以:
>>> class A(object):
... def b(self):
... pass
...
>>> A.b is A.b
False
因为每次检索“A”的“b”属性都会产生“方法对象 b”的不同实例
>>> A.b
<unbound method A.b>
原始函数“b”可以在不进行任何转换的情况下检索
>>> A.__dict__["b"]
<function b at 0xe36230>
对于用 @classmethod
修饰的函数,同样的情况会发生,当从 A 中检索值“class”时,它会被添加到参数列表中。
@classmethod
和@staticmethod
装饰器会将底层函数包装在与普通实例方法不同的描述符中。一个类方法对象——当它被 classmethod
包装时,函数变成了一个描述符对象,它有一个“__get__”方法,该方法将返回一个包装底层函数的函数——并添加“cls "参数在所有其他参数之前。
@classmethod
的任何进一步装饰器都必须“知道”它实际上是在处理描述符对象,而不是函数。 -
>>> class A(object):
... @classmethod
... def b(cls):
... print b
...
>>> A.__dict__["b"]
<classmethod object at 0xd97a28>
因此,让 @classmethod
装饰器成为最后一个应用于该方法的装饰器(堆栈中的第一个装饰器)要容易得多 - 这样其他装饰器就可以继续工作一个简单的函数(知道“cls”参数将作为第一个插入)。
关于python - 装饰一个已经是类方法的方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8977359/