我正在尝试添加一个装饰器,它将可调用属性添加到返回与函数返回值略有不同的对象的函数,但会在某个时刻执行该函数。
我遇到的问题是,当函数对象传递给装饰器时,它是未绑定的,并且不包含隐式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
参数调用 ?
答案 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__
方法来访问实例方法的实际功能对象。
子>