装饰一个已经是类方法的方法?
今天早上我遇到了一个有趣的问题。我有一个看起来像这样的基类:
# 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
装饰的方法添加更多装饰器?
并非所有模型都会被缓存,但存在()方法作为类方法存在于每个模型上,因此重新排序装饰器不是一个选项:cached_model
可以将类方法添加到存在(),但是那么是什么使得存在()成为未缓存模型上的类方法呢?
I had an interesting problem this morning. I had a base class that looked like this:
# base.py
class Base(object):
@classmethod
def exists(cls, **kwargs):
# do some work
pass
And a decorator module that looked like this:
# 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
Here's my subclass model:
@cached_model(ttl=300)
class Model(Base):
pass
Thing is, when I actually call Model.exists, I get complaints about the wrong number of arguments! Inspecting the arguments in the decorator shows nothing weird going on - the arguments are exactly what I expect, and they match up with the method signature. How can I add further decorators to a method that's already decorated with classmethod
?
Not all models are cached, but the exists() method is present on every model as a classmethod, so re-ordering the decorators isn't an option: cached_model
can add classmethod to exists(), but then what makes exists() a classmethod on uncached models?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
在Python中,当在函数体中声明一个方法时,它就像一个函数 -
一旦类被解析并存在,就通过“.”检索方法。来自实例的运算符将该函数即时转换为方法。此转换确实将第一个参数添加到方法中(如果它不是静态方法) -
在 Python 3 中,从类中检索方法将产生原始函数 - “未绑定方法”的图形不再存在:
但是在旧 Python 2:
因为每次检索“A”的“b”属性都会产生“方法对象 b”的不同实例。
在 Python 2 中,如果需要的话,可以在不进行任何转换的情况下检索原始函数“b”
(再次:这不是现代 Python 中需要 - 我们的版本是 3.12)
对于用
@classmethod
修饰的函数也会发生同样的情况,并且当从 A 检索值“class”时,它会被添加到参数列表中。
@classmethod
和@staticmethod
装饰器会将底层函数包装在与普通实例方法不同的描述符中。 classmethod 对象 - 当函数用 classmethod 包装时就会变成描述符对象,它有一个 '_get_' 方法,该方法将返回一个函数包装底层函数 - 并在所有其他参数之前添加“cls”参数。@classmethod
的任何进一步装饰器都必须“知道”它实际上是在处理描述符对象,而不是函数。 -因此,让
@classmethod
装饰器成为最后一个应用于该方法的装饰器(堆栈上的第一个装饰器)要容易得多 - 这样其他装饰器就可以简单地工作函数(知道“cls”参数将作为第一个参数插入)。In Python, when a method is declared, in a function body, it is exactly like a function -
once the class is parsed and exists, retrieving the method through the "." operator from an instance, transforms that function - on the fly - into a method. This transform does add the first parameter to the method (if it is not an staticmethod) -
In Python 3, retrieving the method from a class will yield the original function - the figure of the "unbound method" does not exist anymore:
But in old Python 2:
Because each retrieving of the "b" attribute of "A" yields a different instance of the "method object b"
In Python 2, the original function "b" can be retrieved without any transform if one does
(again: this is not needed in modern Python - we are at version 3.12)
For a function decorated with
@classmethod
just the same happens, and the value "class" is added to the parameter list when it is retrieved from A.The
@classmethod
and@staticmethod
decorators will wrap the underlying function in a different descriptor than the normal instancemethod. A classmethod object - which is what a function becomes when it is wrapped withclassmethod
is a descriptor object, which has a '_get_' method which will return a function wrapping the underlying function - and adding the "cls" parameter before all the other ones.Any further decorator to a
@classmethod
has to "know" it is actually dealing with a descriptor object, not a function. -So, it is a lot easier to let the
@classmethod
decorator to be the last one to be applied to the method (the first one on the stack) - so that the other decorators work on a simple function (knowing that the "cls" argument will be inserted as the first one).感谢 jsbueno 提供有关 Python 的信息。我正在根据 装饰类的所有方法。基于寻找这个问题的答案和 jsbueno 的回应,我能够收集到一些类似的东西:
有一些冗余和一些变化,你可以用它来减少一点。
Thanks to jsbueno for the information about Python. I was looking for an answer to this question based on the case of decorating all methods of a class. Based on looking for an answer to this question and jsbueno's reponse, I was able to gather something along the lines of:
There's a bit of redundancy and a few variations you could use to chop this down a bit.
据我所知,在某些情况下,除了将方法绑定到类之外,
classmethod
装饰器实际上还会在调用方法之前添加一个class
参数。解决方案是编辑我的类装饰闭包:im_func
属性似乎获取对原始函数的引用,这使我可以使用缓存装饰器访问并装饰原始函数,然后包装整个函数classmethod
调用中出现混乱。总之,classmethod
装饰是不可堆叠的,因为参数似乎是被注入的。The
classmethod
decorator actually prepends aclass
argument to calls to the method, in certain circumstances, as far as I can tell, in addition to binding the method to the class. The solution was editing my class decoration closure:The
im_func
property appears to get a reference to the original function, which allows me to reach in and decorate the original function with my caching decorator, and then wrap that whole mess in aclassmethod
call. Summary,classmethod
decorations are not stackable, because arguments seem to be injected.只是一个功能示例,可以添加到 Scott Lobdell 的精彩答案中......
messages.py
用法
输出
Just a functional example to add to Scott Lobdell's great answer...
messages.py
usage
output