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

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

问题描述

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 装饰器实例:

<预><代码>>>>类受害者(对象):... @装饰风格... def sum(self, a, b):...返回 a + b...>>>v = 受害者()>>>总和<绑定方法 <__main__.Decor 对象在 0x102295390><class '__main__.DecorMethod'>>>>>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天全站免登陆