今天早上我遇到了一个有趣的问题。我的基类看起来像这样:
# 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()方法作为classmethod存在于每个模型上,因此重新排序装饰器不是一个选项:cached_model
可以将classmethod添加到exists(),但是那么是什么让exists()成为未缓存模型的类方法?
答案 0 :(得分:5)
在Python中,当声明一个方法时,在一个函数体中,它就像一个函数 - 一旦解析并存在该类,通过"。"检索该方法。运算符将该函数(即时)转换为方法。此转换确实将第一个参数添加到方法中(如果它不是静态方法) -
这样:
>>> class A(object):
... def b(self):
... pass
...
>>> A.b is A.b
False
每次检索" b"属性" A"产生"方法对象b"
的不同实例>>> A.b
<unbound method A.b>
原始功能&#34; b&#34;可以在没有任何trasnform的情况下检索
>>> A.__dict__["b"]
<function b at 0xe36230>
对于用@classmethod
装饰的函数,只会发生相同的事情,并且值为#34; class&#34;从A。
@classmethod
和@staticmethod
装饰器将底层函数包装在与普通instancemethod不同的描述符中。一个classmethod对象 - 它是一个函数在用classmethod
包装时变成的对象是一个描述符对象,它有一个&#39; __ get __&#39;将返回包含底层函数的函数的方法 - 并添加&#34; cls&#34;所有其他参数之前的参数。
@classmethod
的任何其他装饰者必须&#34;知道&#34;它实际上是处理描述符对象,而不是函数。 -
>>> class A(object):
... @classmethod
... def b(cls):
... print b
...
>>> A.__dict__["b"]
<classmethod object at 0xd97a28>
因此,让@classmethod
装饰器成为应用于方法的最后一个(堆栈中的第一个)更容易 - 这样其他装饰器就可以处理一个简单的函数(知道&#34; cls&#34;参数将作为第一个参数插入。)
答案 1 :(得分:2)
感谢jsbueno获取有关Python的信息。我正在根据decorating all methods of a class的案例寻找这个问题的答案。基于寻找这个问题的答案和jsbueno的回应,我能够收集到以下内容:
def for_all_methods(decorator):
def decorate(cls):
for attr in dir(cls):
possible_method = getattr(cls, attr)
if not callable(possible_method):
continue
# staticmethod
if not hasattr(possible_method, "__self__"):
raw_function = cls.__dict__[attr].__func__
decorated_method = decorator(raw_function)
decorated_method = staticmethod(decorated_method)
# classmethod
elif type(possible_method.__self__) == type:
raw_function = cls.__dict__[attr].__func__
decorated_method = decorator(raw_function)
decorated_method = classmethod(decorated_method)
# instance method
elif possible_method.__self__ is None:
decorated_method = decorator(possible_method)
setattr(cls, attr, decorated_method)
return cls
return decorate
有一些冗余和一些变化,你可以用它来剁下来。
答案 2 :(得分:1)
除了将方法绑定到类之外,在某些情况下,classmethod
装饰器实际上预先设置class
参数来调用该方法。解决方案是编辑我的类装饰闭包:
def cached_model(ttl=300):
def closure(model_class):
# ...
# eventually:
exists_decorator = cached(ttl=ttl, cache_key=exists_cache_key)
model_class.exists = classmethod(exists_decorator(model_class.exists.im_func))
return model_class
return closure
im_func
属性似乎获得对原始函数的引用,这允许我使用我的缓存装饰器进入并装饰原始函数,然后在classmethod
调用中包装整个混乱。总结,classmethod
装饰不可堆叠,因为似乎注入了参数。
答案 3 :(得分:0)
只是一个功能性的例子,以添加到Scott Lobdell的伟大答案......
messages.py
from distutils.cmd import Command
import functools
import unittest
def for_all_methods(decorator):
def decorate(cls):
for attr in cls.__dict__:
possible_method = getattr(cls, attr)
if not callable(possible_method):
continue
# staticmethod
if not hasattr(possible_method, "__self__"):
raw_function = cls.__dict__[attr].__func__
decorated_method = decorator(raw_function)
decorated_method = staticmethod(decorated_method)
# classmethod
if type(possible_method.__self__) == type:
raw_function = cls.__dict__[attr].__func__
decorated_method = decorator(raw_function)
decorated_method = classmethod(decorated_method)
# instance method
elif possible_method.__self__ is None:
decorated_method = decorator(possible_method)
setattr(cls, attr, decorated_method)
return cls
return decorate
def add_arguments(func):
"""
The add_arguments decorator simply add the passed in arguments
(args and kwargs) the returned error message.
"""
@functools.wraps(func)
def wrapped(self, *args, **kwargs):
try:
message = func(self, *args, **kwargs)
message = ''.join([message,
"[ args:'", str(args), "'] ",
"[ kwargs:'", str(kwargs), "' ] "
])
return message
except Exception as e:
err_message = ''.join(["errorhandler.messages.MESSAGE: '",
str(func),
"(", str(args), str(kwargs), ")' ",
"FAILED FOR UNKNOWN REASON. ",
" [ ORIGINAL ERROR: ", str(e), " ] "
])
return err_message
return wrapped
@for_all_methods(add_arguments)
class MESSAGE(object):
"""
log.error(MSG.triggerPhrase(args, kwargs))
"""
@classmethod
def TEMPLATE(self, *args, **kwargs):
message = "This is a template of a pre-digested message."
return message
使用
from messages import MESSAGE
if __name__ == '__main__':
result = MESSAGE.TEMPLATE(1,2,test=3)
print result
输出
This is a template of a pre-digested message.[ args:'(1, 2)'] [ kwargs:'{'test': 3}' ]