实例方法的装饰器能访问类吗?

151 投票
14 回答
90528 浏览
提问于 2025-04-15 19:56

我有一个大致如下的情况。基本上,我需要在实例方法的定义中,通过一个装饰器来访问这个实例方法所属的类。

def decorator(view):
    # do something that requires view's class
    print view.im_class
    return view

class ModelA(object):
    @decorator
    def a_method(self):
        # do some stuff
        pass

现在的代码运行后会出现:

AttributeError: 'function' object has no attribute 'im_class'

我找到了一些类似的问题和答案,比如 Python 装饰器让函数忘记它属于哪个类在 Python 装饰器中获取类,但这些方法都是通过在运行时抓取第一个参数来解决问题的。在我的情况下,我需要根据类的信息来调用这个方法,所以我不能等到方法被调用时再去获取。

14 个回答

17

正如其他人提到的,当装饰器被调用时,类还没有被创建。不过,我们可以在装饰器的参数中给函数对象添加一些信息,然后在元类的 __new__ 方法中重新装饰这个函数。你需要直接访问函数的 __dict__ 属性,因为对我来说,使用 func.foo = 1 会导致一个属性错误。

56

从Python 3.6开始,你可以使用 object.__set_name__ 这个方法来很简单地实现某些功能。文档中提到,__set_name__ 是在拥有这个方法的类被创建的时候被调用的。

class class_decorator:
    def __init__(self, fn):
        self.fn = fn

    def __set_name__(self, owner, name):
        # do something with owner, i.e.
        print(f"decorating {self.fn} and using {owner}")
        self.fn.class_name = owner.__name__

        # then replace ourself with the original method
        setattr(owner, name, self.fn)

注意,这个方法是在类创建的时候被调用的:

>>> class A:
...     @class_decorator
...     def hello(self, x=42):
...         return x
...
decorating <function A.hello at 0x7f9bedf66bf8> and using <class '__main__.A'>
>>> A.hello
<function __main__.A.hello(self, x=42)>
>>> A.hello.class_name
'A'
>>> a = A()
>>> a.hello()
42

如果你想了解更多关于类是如何创建的,特别是 __set_name__ 具体是什么时候被调用的,可以参考 关于“创建类对象”的文档

81

如果你使用的是Python 2.6或更新的版本,你可以使用一个类装饰器,可能像这样(注意:这段代码没有经过测试)。

def class_decorator(cls):
   for name, method in cls.__dict__.iteritems():
        if hasattr(method, "use_class"):
            # do something with the method and class
            print name, cls
   return cls

def method_decorator(view):
    # mark the method as something that requires view's class
    view.use_class = True
    return view

@class_decorator
class ModelA(object):
    @method_decorator
    def a_method(self):
        # do some stuff
        pass

方法装饰器通过添加一个“use_class”属性来标记这个方法是我们关注的对象——函数和方法其实也是对象,所以你可以给它们附加一些额外的信息。

在类创建完成后,类装饰器会检查所有的方法,并对那些被标记的方法进行相应的处理。

如果你希望所有的方法都受到影响,那么你可以不使用方法装饰器,直接使用类装饰器就可以了。

撰写回答