用方法装饰一个函数(带任意参数)
参考资料
在写这个问题之前,我参考了一些有趣的问题,觉得这个情况没有被解释或覆盖:
- 如何制作一系列的函数装饰器?(特别是答案 #2)
- 如何装饰类里面的方法?
- 类中的Python装饰器
- 带参数的Python类装饰器,可以装饰方法或函数(接近,但不是我想问的)
我有一个 function
如下:
def simple_function():
print 'this is a simple_function'
还有一个 class
,里面有一些 methods
:
class Test:
def Test_method_1(self, func, args=None, kwargs=None):
print 'this is Test::Test_method_1'
<execute some instructions here>
def Test_method_2(self):
print 'this is Test::Test_method_2'
我使用 Test_method_1
和 simple_function
如下:
t = Test()
t.Test_method_1(simple_function)
假设 simple_function
接受任意参数,那么 Test_method_1
的调用方式如下:
def simple_function(a, b, c, d=f, g=h):
print 'this is a simple_function'
t = Test()
t.Test_method_1(simple_function, args=[a, b, c], kwargs={d:f, g:h})
现在,我想把 Test_method_2
用作 Test_method_1
的 decorator
版本。所以 simple_function
的定义可以写成这样:
t = Test()
@t.Test_method_2
def simple_function():
print 'this is a simple_function'
注意:Test_method_2
会用适当的参数调用 Test_method_1
。
现在的问题:
- 1 - 这可能吗?
- 2 - 如果可能,装饰的函数名称(这里是
simple_function
)将如何作为参数传递给Test_method_2
(包括self
)? - 3 - [我认为这是关键问题] 我想将任意参数传递给
Test_method_2
(装饰器)和simple_function
(被装饰的函数) -*args 和 **kwargs
-Test_method_2
应该如何定义以接收这些参数(包括self
)?
将 Test_method_2
作为 simple_function
的装饰器,并且两个都可以接受任意参数的用法如下:
t = Test()
@t.Test_method_2(a, b, c, d, e=f, g=h)
def simple_function(x, y, z, n=m):
print 'this is a simple_function'
1 个回答
当然是可以的。@decorator
这个语法的作用就是定义后面的函数,然后调用这个装饰器,把后面的函数传进去,并用装饰器返回的东西替换掉原来的函数引用。
所以,下面这段:
@foo
def bar():
pass
实际上是变成了:
def bar():
pass
bar = foo(bar)
这意味着你的 t.Test_method_2()
方法需要接受一个参数,就是要被装饰的函数,并返回一个可以调用的东西:
import functools
def Test_method_2(self, func):
@functools.wraps(func)
def wrapper(self, *args, **kw):
print 'Wrapped function name:', func.__name__
return func(*args, **kw)
return wrapper
这段代码就是一个最简单的装饰器,它返回一个包装函数,并在被调用时打印出被包装函数的名字。参数的名字可以随便取;我这里用的是 func
,但你可以用任何合法的 Python 标识符。
这里的 self
是方法签名的标准部分。因为你是在实例 t
上调用 Test_method_2
,所以 Python 会自动处理 self
参数,就像处理所有方法一样。
在 @
后面的内容只是一个表达式。所以如果你使用这样的语法:
@t.Test_method_2(a, b, c, d, e=f, g=h)
那么 Test_method_2()
应该返回一个装饰器函数。只需要多一层作用域嵌套就可以实现:
def Test_method_2(self, *args, **kw):
def decorator(func):
@functools.wraps(func)
def wrapper(*wrapperargs, **wrapperkw):
fargs = args + wrapperargs
fkw = dict(kw)
fkw.update(wrapperkw)
return func(*fargs, **fkw)
return wrapper
return decorator
拆解一下这个过程:
@t.Test_method_2(5, 6, 7, 8, baz='spam', ham='eggs')
def simple_function(x, y, z, n=m):
print 'this is a simple_function'
在 @
后面的部分,t.Test_method_2(5, 6, 7, 8, baz='spam', ham='eggs')
返回的是嵌套的函数 decorator
:
@decorator
def simple_function(x, y, z, n=m):
print 'this is a simple_function'
然后 Python 会把它变成:
simple_function = decorator(simple_function)
而 decorator(func)
会返回 wrapper(*wrapperargs, **wrapperkw)
。
调用 simple_function(1, 2, foo='bar')
就会导致调用 wrapper(1, 2, foo='bar')
,这会用 fargs = [5, 6, 7, 8, 1, 2]
和 fkw = {'baz': 'spam', 'ham': 'eggs', 'foo': 'bar'}
作为位置参数和关键字参数,去调用原始的 simple_function()
。
你在相关问题中看到的类装饰器模式也是类似的;@
后面的表达式返回的是一个可以被调用的东西;这里不是嵌套函数,而是创建了一个类实例。这只是两种不同的方式来存储装饰器需要使用的状态。
嵌套函数写起来稍微简洁一些,而使用类则给你提供了比嵌套作用域更多的反射选项。