在“样板”中包装类的方法Python装饰器将把该方法视为常规函数,并使其失去引用类实例对象的__self__
属性。可以避免吗?
参加以下课程:
class MyClass(object):
def __init__(self, a=1, b=2):
self.a = a
self.b = b
def meth(self):
pass
如果未修饰meth()
,则MyClass().meth.__self__
引用实例方法并启用类似setattr(my_class_object.meth.__self__, 'a', 5)
的内容。
但是在将任何东西包装在装饰器中时,只会传递函数对象;实际绑定到的对象不会随其传递。 (请参见this答案。)
import functools
def decorate(method):
@functools.wraps(method)
def wrapper(*args, **kwargs):
# Do something to method.__self__ such as setattr
print(hasattr(method, '__self__'))
result = method(*args, **kwargs)
return result
return wrapper
class MyClass(object):
def __init__(self, a=1, b=2):
self.a = a
self.b = b
@decorate
def meth(self):
pass
MyClass().meth()
# False <--------
可以覆盖吗?
答案 0 :(得分:3)
您在这里的主要误解是操作顺序。
当调用decorate()
装饰器时,meth()
还不是一种方法-它仍然是一个函数-仅当class
块位于meth
上方时由元类描述符转换为方法! -这就是为什么它还没有__self__
的原因。
换句话说,要装饰方法,您必须忽略它们是方法并将其视为常规函数的事实-因为调用装饰器时就是这样。
实际上,原始的meth
函数永远不会变成一种方法-从装饰器返回的函数wrapper
将成为类的一部分,并且将成为获得{ {1}}属性。
答案 1 :(得分:2)
让我澄清一下装饰过程:
在类meth
中用decorate
装饰MyClass
时,您正在做的事情:
class MyClass(object):
... omit
meth = decorate(meth) # the meth in "()" is the original function.
如您所见,decorate
将method
作为函数,并返回wrapper
,这是另一个功能。现在,meth
中的原始MyClass
被新的wrapper
所代替。因此,当您调用myclass_instance.meth()
时,就是在调用新的wrapper
函数。
没有任何黑魔法,因此self
可以肯定地传递到wrapper
中,并且可以安全地使用self
接受wrapper(self, *args, **kwargs)
。
答案 2 :(得分:1)
如果装饰类的方法,则第一个参数始终是self
对象(可以使用args[0]
访问它):
import functools
def decorate(method):
@functools.wraps(method)
def wrapper(*args, **kwargs):
print(hasattr(args[0], 'a'))
result = method(*args, **kwargs)
return result
return wrapper
class MyClass(object):
def __init__(self, a=1, b=2):
self.a = a
self.b = b
@decorate
def meth(self):
pass
MyClass().meth()
打印:
True
编辑:
您还可以在self
函数中根据具体评论指定wrapper
:
import functools
def decorate(method):
@functools.wraps(method)
def wrapper(self, *args, **kwargs):
print(hasattr(self, 'a'))
result = method(self, *args, **kwargs)
return result
return wrapper
class MyClass(object):
def __init__(self, a=1, b=2):
self.a = a
self.b = b
@decorate
def meth(self):
pass
MyClass().meth()
也打印:
True