应用于方法的可调用对象装饰器不会在输入时获得自参数 [英] Callable object decorator applied to method doesn't get self argument on input
问题描述
import functools类装饰(对象):def __init__(self, func):self.func = funcdef __call__(self, *args, **kwargs):def 闭包(*args, **kwargs):打印参数,kwargs返回 self.func(*args, **kwargs)返回闭包(*args,**kwargs)类受害者(对象):@装饰风格def sum(self, a, b):返回 a+bv = 受害者()v.sum(1, 2)
结果:
(1, 2) {}回溯(最近一次调用最后一次):文件test.py",第 19 行,在 <module> 中v.sum(1, 2)文件test.py",第 11 行,在 __call__ 中返回闭包(*args,**kwargs)文件test.py",第 10 行,关闭返回 self.func(*args, **kwargs)类型错误:sum() 正好需要 3 个参数(给出 2 个)
如何获取方法的 self
参数?
更新:我设法对 Martijn 的答案进行了更有用的改编,它返回 Decor
对象以响应 __get__
,但同时绑定 self
参数,当它作为对象的方法被调用时.有了这个版本,你可以说例如Victim.sum.hooks.append(my_favorite_function)
和 my_favorite_function
将在 Victim.sum
之前调用.警告:此版本线程不安全.
类装饰(对象):def __init__(self, func):self.func = funcself.hooks = []包装(self.func)(自我)def __get__(self, instance, klass):if instance != None: self.instance = instance如果 klass != None: self.klass = klass回归自我def __call__(self, *args, **kwargs):def 闭包(*args, **kwargs):对于 self.hooks 中的函数:函数(*args, **kwargs)func = self.funcretval = func(*args, **kwargs) #kwargs_copy #使用notify = False调用返回值返回闭包.__get__(self.instance, self.klass)(*args, **kwargs)
Python 函数充当 描述符,这意味着每当您访问类或实例上的函数时,它们的 .__get__()
方法 被调用并返回一个方法对象,该对象保留对原始函数的引用,例如,对实例的引用.方法对象然后充当包装器;当被调用时,它们调用底层函数并将实例引用作为 self
传入.
另一方面,你的可调用类对象没有实现描述符协议,它没有 .__get__()
方法,因此它永远没有机会绑定到实例.您必须自己实现此功能:
类装饰(对象):def __init__(self, func):self.func = funcdef __get__(self, instance, owner):如果实例是无:回归自我d = 自己# 使用 lambda 生成绑定方法mfactory = lambda self, *args, **kw: d(self, *args, **kw)mfactory.__name__ = self.func.__name__返回 mfactory.__get__(instance, owner)def __call__(self, instance, *args, **kwargs):def 闭包(*args, **kwargs):打印实例、args、kwargs返回 self.func(instance, *args, **kwargs)返回闭包(*args,**kwargs)
演示:
<预><代码>>>>类受害者(对象):... @装饰风格... def sum(self, a, b):...返回 a+b...>>>v = 受害者()>>>总和<绑定方法 Victim.sum of <__main__.Victim object at 0x11013d850>>>>>v.sum(1, 2)<__main__.Victim 对象在 0x11013d850>(1, 2) {}3将您绑定的实例直接存储在Decor
实例上不是一个好主意;这是一个类属性,在实例之间共享.设置 self.instance
既不是线程安全的,也不允许存储方法供以后调用;最近的 __get__
调用将改变 self.instance
并导致难以解决的错误.
您始终可以返回自定义代理对象而不是方法:
类装饰方法(对象):def __init__(self,decor,instance):self.decor = 装饰self.instance = 实例def __call__(self, *args, **kw):返回 self.decor(instance, *args, **kw)def __getattr__(self, name):返回 getattr(self.decor, name)def __repr__(self):return ''.format(self.decor, type(self))
并在您的 Decor.__get__
中使用它而不是生成方法:
def __get__(self, instance, owner):如果实例是无:回归自我返回装饰方法(自我,实例)
DecorMethod
此处将任何对未知属性的请求传递回 Decor
装饰器实例:
import functools
class Decor(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
def closure(*args, **kwargs):
print args, kwargs
return self.func(*args, **kwargs)
return closure(*args, **kwargs)
class Victim(object):
@Decor
def sum(self, a, b):
return a+b
v = Victim()
v.sum(1, 2)
Results in:
(1, 2) {}
Traceback (most recent call last):
File "test.py", line 19, in <module>
v.sum(1, 2)
File "test.py", line 11, in __call__
return closure(*args, **kwargs)
File "test.py", line 10, in closure
return self.func(*args, **kwargs)
TypeError: sum() takes exactly 3 arguments (2 given)
How do I get self
argument for the method?
UPDATE:
I've managed to create a more useful adaptation of Martijn's answer, which returns Decor
object in response to __get__
, but at the same time binds self
argument, when it is called as a method of object. With this version you can say e.g. Victim.sum.hooks.append(my_favorite_function)
and my_favorite_function
will be called before Victim.sum
. WARNING: this version is thread-unsafe.
class Decor(object):
def __init__(self, func):
self.func = func
self.hooks = []
wraps(self.func)(self)
def __get__(self, instance, klass):
if instance != None: self.instance = instance
if klass != None: self.klass = klass
return self
def __call__(self, *args, **kwargs):
def closure(*args, **kwargs):
for function in self.hooks:
function(*args, **kwargs)
func = self.func
retval = func(*args, **kwargs) #kwargs_copy #called with notify = False
return retval
return closure.__get__(self.instance, self.klass)(*args, **kwargs)
Python functions act as descriptors, which means that whenever you access a function on a class or instance, their .__get__()
method is invoked and a method object is returned which keeps a reference to the original function, and for instances, a reference to the instance. Method object then acts as wrappers; when called they call the underlying function and pass in the instance reference as self
.
Your callable class object, on the other hand, does not implement the descriptor protocol, it has no .__get__()
method, and thus it never is given an opportunity to bind to the instance. You'll have to implement this functionality yourself:
class Decor(object):
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
if instance is None:
return self
d = self
# use a lambda to produce a bound method
mfactory = lambda self, *args, **kw: d(self, *args, **kw)
mfactory.__name__ = self.func.__name__
return mfactory.__get__(instance, owner)
def __call__(self, instance, *args, **kwargs):
def closure(*args, **kwargs):
print instance, args, kwargs
return self.func(instance, *args, **kwargs)
return closure(*args, **kwargs)
Demo:
>>> class Victim(object):
... @Decor
... def sum(self, a, b):
... return a+b
...
>>> v = Victim()
>>> v.sum
<bound method Victim.sum of <__main__.Victim object at 0x11013d850>>
>>> v.sum(1, 2)
<__main__.Victim object at 0x11013d850> (1, 2) {}
3
It is not a good idea to store the instance you are bound to directly on the Decor
instance; this is a class attribute, shared among instances. Setting self.instance
is neither thread-safe nor allows methods to be stored for later invocation; the most recent __get__
call will alter self.instance
and lead to hard-to-resolve bugs.
You can always return a custom proxy object instead of a method:
class DecorMethod(object):
def __init__(self, decor, instance):
self.decor = decor
self.instance = instance
def __call__(self, *args, **kw):
return self.decor(instance, *args, **kw)
def __getattr__(self, name):
return getattr(self.decor, name)
def __repr__(self):
return '<bound method {} of {}>'.format(self.decor, type(self))
and use that in your Decor.__get__
instead of producing a method:
def __get__(self, instance, owner):
if instance is None:
return self
return DecorMethod(self, instance)
The DecorMethod
here passes any requests for unknown attributes back to the Decor
decorator instance:
>>> class Victim(object):
... @Decor
... def sum(self, a, b):
... return a + b
...
>>> v = Victim()
>>> v.sum
<bound method <__main__.Decor object at 0x102295390> of <class '__main__.DecorMethod'>>
>>> v.sum.func
<function sum at 0x102291848>
这篇关于应用于方法的可调用对象装饰器不会在输入时获得自参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!