可应用于方法的可调用对象装饰器不会在输入上获取自变量 [英] Callable object decorator applied to method doesn't get self argument on input

查看:159
本文介绍了可应用于方法的可调用对象装饰器不会在输入上获取自变量的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

  import functools 


class Decor(object):
def __init __(self,func):
self。 func = func

def __call __(self,* args,** kwargs):
def closure(* args,** kwargs):
print args,kwargs
返回self.func(* args,** kwargs)
返回闭包(* args,** kwargs)


类受害者(object):
@装饰
def sum(self,a,b):
返回a + b


v =受害者()
v.sum(1,2 )

结果:

 (1,2){} 
Traceback(最近一次调用最后一次):
在< module>文件中的第19行test.py
v.sum(1,2)
文件test.py,第11行,在__call__
返回闭包(* args,** kwargs)
文件test。 py,第10行,关闭
返回self.func(* args,** kwargs)
TypeError:sum()接受3个参数(给出2个)

如何为该方法获取 self 参数?



更新:
我设法创建了一个更有用的Martijn的答案,它返回 Decor 对象来响应 __ get __ ,但同时绑定 self 参数,当它被称为方法目的。有了这个版本,你可以说例如 Victim.sum.hooks.append(my_favorite_function) my_favorite_function 将在 Victim之前被调用。总和警告:此版本线程不安全

  class Decor对象):
def __init __(self,func):
self.func = func
self.hooks = []
wrap(self.func)(self)

def __get __(self,instance,klass):
if instance!= None:self.instance = instance
如果klass!= None:self.klass = klass
return self

def __call __(self,* args,** kwargs):
def closure(* args,** kwargs):
for self.hooks中的函数:
函数(* args,** kwargs)
func = self.func
retval = func(* args,** kwargs)#kwargs_copy #calcify notify = False
return retval
return closure .__ get __(self.instance,self.klass)(* args,** kwargs)


解决方案

Python函数充当描述符,这意味着只要你访问一个类或实例的函数,它们的 .__ get __()方法被调用,并返回一个方法对象,该方法对象保留对原始函数的引用,并且对实例引用实例。方法对象然后作为包装器;当调用它们时,它们调用底层函数并将实例引用作为 self 传递。您的可调用类对象on另一方面,它没有实现描述符协议,它没有 .__ get __()方法,因此永远不会有机会绑定到实例。你必须自己实现这个功能:

  class Decor(object):
def __init __(self,func ):
self.func = func

def __get __(self,instance,owner):
如果实例是None:
return self
d = self
#使用lambda生成绑定方法
mfactory = lambda self,* args,** kw:d(self,* args,** kw)
mfactory .__ name__ = self。 func .__ name__
return mfactory .__ get __(instance,owner)
$ b def __call __(self,instance,* args,** kwargs):
def closure(* args,* * kwargs):
打印实例,args,kwargs
返回self.func(实例,* args,** kwargs)
返回闭包(* args,** kwargs)

演示:

 >>>班级受害者(对象):
... @Decor
... def sum(self,a,b):
... return a + b
...
>>> v = Victim()
>>> v.sum
< __main __的受害者对象的绑定方法Victim.sum。0x11013d850处的受害者对象>>
>>> v.sum(1,2)
<__ main __。0x11013d850处的受害者对象> (1,2){}
3

将你绑定的实例直接存储在 Decor 实例中一个好主意;这是一个类属性,在实例之间共享。设置 self.instance 既不是线程安全的,也不允许存储方法以供以后调用;最近的 __ get __ 调用会改变 self.instance 并导致难以解决的错误。



您可以始终返回自定义代理对象而不是方法:

  class DecorMethod (object):
def __init __(self,decor,instance):
self.decor = decor
self.instance = instance
$ b $ def __call __(self,* args,** kw):
return self.decor(instance,* args,** kw)
$ b $ def __getattr __(self,name):
return getattr(self。装饰,名称)

def __repr __(self):
return'< bound method {}>'.format(self.decor,type(self))

并在您的 Decor中使用它.__ get __ ,而不是生成一个方法:

  def __get __(self,instance,owner):
如果实例是None:
返回self
DecorMethod(self,instance)

DecorMethod 在这里将所有未知属性的请求传回给 Decor 装饰器实例:

 >>>班级受害者(对象):
... @Decor
... def sum(self,a,b):
... return a + b
...
>>> v = Victim()
>>> v.sum
>>> v.sum.func
<函数和在0x102291848>


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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆