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

问题描述 投票:0回答:4

今天早上我遇到了一个有趣的问题。 我有一个如下所示的基类:

# 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
可以将类方法添加到exists(),但是什么使存在() 未缓存模型上的类方法?

python decorator chaining
4个回答
6
投票

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

在Python 3中,从类中检索方法将产生原始函数 - “未绑定方法”的形象不再存在:

>>> class A:
...     def b(self):
...         pass
...         
>>> A.b
<function A.b at 0x7fbebfd22160>
>>> A.b is A.b
True
>>> a = A()
>>> a.b
<bound method A.b of <__main__.A object at 0x7fbebfd282f0>>
>>> a.b is a.b
False

但是在旧的 Python 2 中:

>>> class A(object):
...    def b(self):
...        pass
... 
>>> A.b is A.b
False

因为每次检索“A”的“b”属性都会产生“方法对象 b”的不同实例

>>> A.b
<unbound method A.b>

在Python 2中,如果需要的话,可以在不进行任何转换的情况下检索原始函数“b”

>>> A.__dict__["b"]
<function b at 0xe36230>

(再次强调:这在现代 Python 中是不需要的 - 我们使用的是 3.12 版本)

对于用

@classmethod
修饰的函数,也会发生同样的情况,并且当从 A 检索值“class”时,它会被添加到参数列表中。

@classmethod
@staticmethod
装饰器会将底层函数包装在与普通实例方法不同的描述符中。一个 classmethod 对象 - 当函数用
classmethod
包装时就会变成一个描述符对象,它有一个 '_get_' 方法,它将返回一个包装底层函数的函数 - 并添加“cls”参数位于所有其他参数之前。

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

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

因此,让

@classmethod
装饰器成为最后一个应用于该方法的装饰器(堆栈上的第一个装饰器)要容易得多 - 以便其他装饰器处理一个简单的函数(知道“ cls”参数将作为第一个插入)。


3
投票

感谢jsbueno提供有关Python的信息。 我正在根据“装饰类的所有方法”的情况来寻找这个问题的答案。 基于寻找这个问题的答案和 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

有一些冗余和一些变体,您可以使用它来减少一点。

据我所知,在某些情况下,除了将方法绑定到类之外,

1
投票
装饰器实际上还会在方法调用之前添加一个

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
装饰是不可堆叠的,因为参数似乎是被注入的。

只是一个功能示例,可以添加到 Scott Lobdell 的精彩答案中......

0
投票

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}' ]


© www.soinside.com 2019 - 2024. All rights reserved.