实例方法的装饰器能访问类吗?
我有一个大致如下的情况。基本上,我需要在实例方法的定义中,通过一个装饰器来访问这个实例方法所属的类。
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 个回答
正如其他人提到的,当装饰器被调用时,类还没有被创建。不过,我们可以在装饰器的参数中给函数对象添加一些信息,然后在元类的 __new__
方法中重新装饰这个函数。你需要直接访问函数的 __dict__
属性,因为对我来说,使用 func.foo = 1
会导致一个属性错误。
从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__
具体是什么时候被调用的,可以参考 关于“创建类对象”的文档。
如果你使用的是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”属性来标记这个方法是我们关注的对象——函数和方法其实也是对象,所以你可以给它们附加一些额外的信息。
在类创建完成后,类装饰器会检查所有的方法,并对那些被标记的方法进行相应的处理。
如果你希望所有的方法都受到影响,那么你可以不使用方法装饰器,直接使用类装饰器就可以了。