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

时间:2012-01-23 19:22:26

标签: python decorator chaining

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

# 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()成为未缓存模型的类方法?

4 个答案:

答案 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}' ] 
相关问题