装饰函数并增加功能,保持参数数量不变
我想给一个函数加上装饰器,想用这样的方式:
def deco(func):
def wrap(*a,**kw):
print "do something"
return func(*a,**kw)
return wrap
问题是,如果被装饰的函数有这样的原型:
def function(a,b,c): return
当加上装饰器后,原型会被可变参数搞坏,比如调用 function(1,2,3,4) 时不会报错。有没有办法避免这个问题呢?我该如何定义一个和被装饰函数(func)原型相同的包装函数?
这样做是不是从概念上就有问题呢?
编辑
我有个奇怪的想法,就是想在不改变函数签名的情况下,轻松调用“父方法”。就像这样:
def __init__(self, something)
super(ClassName, self).__init__(something)
变成:
@extended
def __init__(self, something):
...
我在想这样做是否可行,以及这样做是否有意义。
编辑
正如Alex指出的,下面的代码不会抛出异常:
function(1,2,3,4)
4 个回答
这里有一个小技巧,首先从被装饰的函数中获取原始的参数说明,然后通过评估一个包含相同参数的字符串来创建一个lambda函数。接着,这个装饰器就被包裹在这个lambda函数里,这样对外界来说,它的参数名称和默认值看起来是一样的:
import inspect, time
import functools
def decorator_wrapper(old_function, new_function):
args, arglist, kw, default = inspect.getargspec(old_function)
args = list(args)
if arglist:
args.append(arglist)
if kw:
args.append(kw)
callstring = inspect.formatargspec(args, arglist, kw, default, formatvalue=lambda value: "")
argstring = inspect.formatargspec(args, arglist, kw, default)[1:-1]
unique_name = "_func" + str(int(time.time()))
codestring = "lambda " + argstring + " : " + unique_name + callstring
decorated_function = eval(codestring, {unique_name: new_function})
return functools.wraps(old_function)(decorated_function)
你说“调用函数(1,2,3,4)不会导致异常”是错的。看看这个:
>>> def deco(f):
... def w(*a, **k):
... print 'do something'
... return f(*a, **k)
... return w
...
>>> def f(a, b, c): return
...
>>> f(1, 2, 3, 4)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: f() takes exactly 3 arguments (4 given)
>>> decorated = deco(f)
>>> decorated(1, 2, 3, 4)
do something
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 4, in w
TypeError: f() takes exactly 3 arguments (4 given)
正如你所看到的,调用没有装饰的 f
时,你会得到完全相同的异常(虽然是在你添加的 print
之后)。
为了保留被包装函数的元数据(比如名字、文档字符串),可以使用 functools.wraps。预测被包装的函数在调用时会抛出异常(为了避免在调用之前做其他工作)总是很难(一般来说是“不可能”的,因为这和停机问题是等价的;在特定情况下,如果你只关心参数名称和数量不匹配时抛出类型错误,并想把这个特殊的异常情况和其他异常情况区别对待,那就“只是难”了——这是个特别的要求;-0)。
如果你坚持认为你绝对需要这个(也许能解释一下为什么吗?),我会很乐意(好吧,不是乐意,但我会咬牙坚持做的,毕竟我面前有个长周末;-)带你走这条复杂的道路。
decorator模块可以帮助你创建一个装饰器,这个装饰器能够保持函数的签名不变。
这样,当你调用这个函数时,就能得到你预期的异常,而且使用inspect.getargspec
时也能得到正确的函数签名。
它的工作原理是动态构建一个函数定义,并使用exec
来执行。不幸的是,目前没有更简单的内置方法可以做到这一点。