在函数属性中访问self

时间:2016-10-21 21:46:38

标签: python python-decorators

我正在尝试添加一个装饰器,它将可调用属性添加到返回与函数返回值略有不同的对象的函数,但会在某个时刻执行该函数。

我遇到的问题是,当函数对象传递给装饰器时,它是未绑定的,并且不包含隐式self参数。当我调用创建的属性函数(即string())时,我无法访问self并且无法将其传递给原始函数。

def deco(func):
    """
    Add an attribute to the function takes the same arguments as the
    function but modifies the output.
    """
    def string(*args, **kwargs):
        return str(func(*args, **kwargs))
    func.string = string
    return func


class Test(object):

    def __init__(self, value):
        self._value = 1

    @deco
    def plus(self, n):
        return self._value + n

当我去执行装饰器创建的属性时,这是我得到的错误,因为args不包含self引用。

>>> t = Test(100)
>>> t.plus(1)         # Gets passed self implicitly
101
>>> t.plus.string(1)  # Does not get passed self implicitly
...
TypeError: plus() takes exactly 2 arguments (1 given)

有没有办法创建这样的装饰器,可以获得对self的引用?或者有没有办法绑定添加的属性函数(string()),以便使用隐式self参数调用

2 个答案:

答案 0 :(得分:6)

您可以在此处使用descriptors

class deco(object):

    def __init__(self, func):
        self.func = func
        self.parent_obj = None

    def __get__(self, obj, type=None):
        self.parent_obj = obj
        return self

    def __call__(self, *args, **kwargs):
        return self.func(self.parent_obj, *args, **kwargs)

    def string(self, *args, **kwargs):
        return str(self(*args, **kwargs))


class Test(object):

    def __init__(self, value):
        self._value = value

    @deco
    def plus(self, n):
        return self._value + n

这样:

>>> test = Test(3)
>>> test.plus(1)
4
>>> test.plus.string(1)
'4'

这需要解释。 deco是装饰者,但它也是descriptor。描述符是一个对象,它定义了在将对象作为其父对象的属性进行查找时要调用的替代行为。有趣的是,bounds方法本身是使用描述符协议实现的

那是满口的。让我们看一下运行示例代码时会发生什么。首先,当我们定义plus方法时,我们应用deco装饰器。现在通常我们将函数视为装饰器,函数的返回值是装饰结果。这里我们使用一个类作为装饰器。因此,Test.plus不是函数,而是deco类型的实例。此实例包含对我们希望包装的plus函数的引用。

deco类有一个__call__方法,允许它的实例像函数一样运行。此实现只是传递给它引用的plus函数的参数。请注意,第一个参数将是对Test实例的引用。

棘手的部分在于实施test.plus.string(1)。为此,我们需要引用test实例作为属性的plus实例。为此,我们使用描述符协议。也就是说,我们定义一个__get__方法,只要将deco实例作为某个父类实例的属性进行访问,就会调用该方法。发生这种情况时,它会将父对象存储在自身内部。然后我们可以简单地在plus.string类上实现deco作为方法,并使用对deco实例中存储的父对象的引用来获取test实例plus属于哪个。

这是很多魔术,所以这里有一个免责声明:虽然这看起来很酷,但实现这样的东西可能不是一个好主意。

答案 1 :(得分:4)

您需要在实例化时(在创建实例方法之前)修饰您的函数。您可以通过覆盖__new__方法来执行此操作:

class Test(object):

    def __new__(cls, *args_, **kwargs_):
        def deco(func):
            def string(*args, **kwargs):
                return "my_str is :" + str(func(*args, **kwargs))
            # *1
            func.__func__.string = string
            return func

        obj = object.__new__(cls, *args_, **kwargs_)
        setattr(obj, 'plus', deco(getattr(obj, 'plus')))
        return obj

    def __init__(self, value):
        self._value = 1

    def plus(self, n):
        return self._value + n

演示:

>>> t = Test(100)
>>> t.plus(1)
>>> t.plus.string(5)
>>> 'my_str is :6'

<子> 1.由于python不允许您在设置时访问实际实例属性,因此您可以使用__func__方法来访问实例方法的实际功能对象。