装饰一个已经是类方法的方法?

And*_*rts 6 python decorator chaining

今天早上我有一个有趣的问题.我有一个基类看起来像这样:

# base.py
class Base(object):

    @classmethod
    def exists(cls, **kwargs):
        # do some work
        pass
Run Code Online (Sandbox Code Playgroud)

还有一个看起来像这样的装饰模块:

# 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
Run Code Online (Sandbox Code Playgroud)

这是我的子类模型:

@cached_model(ttl=300)
class Model(Base):
    pass
Run Code Online (Sandbox Code Playgroud)

事实上,当我实际上调用Model.exists时,我得到关于错误数量的参数的抱怨!检查装饰器中的参数显示没有任何奇怪的事情 - 参数正是我所期望的,并且它们与方法签名匹配.如何将其他装饰器添加到已经装饰的方法中classmethod

并非所有模型都被缓存,但exists()方法作为类方法存在于每个模型上,因此重新排序装饰器不是一个选项:cached_model可以将类方法添加到exists(),但是什么使exists()成为类方法在未缓存的模型?

jsb*_*eno 5

在Python中,当声明一个方法时,在一个函数体中,它就像一个函数 - 一旦解析并存在该类,就通过"."检索该方法.运算符将该函数(即时)转换为方法.此转换确实将第一个参数添加到方法中(如果它不是静态方法) -

所以:

>>> class A(object):
...    def b(self):
...        pass
... 
>>> A.b is A.b
False
Run Code Online (Sandbox Code Playgroud)

因此,每次检索"A"的"b"属性都会产生"方法对象b"的不同实例.

>>> A.b
<unbound method A.b>
Run Code Online (Sandbox Code Playgroud)

如果有的话,可以在没有任何转换的情况下检索原始函数"b"

>>> A.__dict__["b"]
<function b at 0xe36230>
Run Code Online (Sandbox Code Playgroud)

对于以@classmethod相同的方式装饰的函数,当从A中检索时,值"class"被添加到参数列表中.

@classmethod@staticmethod装饰将包装在不同的描述符比正常instancemethod底层函数.一个类方法对象 - 一个函数在被包装时变成的classmethod是一个描述符对象,它有一个'__get__'方法,它将返回一个包装底层函数的函数 - 并在所有其他函数之前添加"cls"参数.

任何进一步的装饰器必须@classmethod"知道"它实际上是处理描述符对象,而不是函数.-

>>> class A(object):
...    @classmethod
...    def b(cls):
...       print b
... 
>>> A.__dict__["b"]
<classmethod object at 0xd97a28>
Run Code Online (Sandbox Code Playgroud)

因此,让@classmethod装饰器成为应用于方法的最后一个(堆栈中的第一个)要容易得多 - 这样其他装饰器就可以处理一个简单的函数(知道"cls"参数将会作为第一个插入).


And*_*rts 1

据我所知,除了将方法绑定到类之外,在某些情况下,装饰器实际上还会在方法调用之前添加一个参数classmethodclass解决方案是编辑我的类装饰闭包:

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
Run Code Online (Sandbox Code Playgroud)

im_func属性似乎获得了对原始函数的引用,这允许我使用缓存装饰器进入并装饰原始函数,然后将整个混乱包装在调用中classmethod。总之,classmethod装饰是不可堆叠的,因为参数似乎是被注入的。