装饰一个函数并添加保留参数数量的功能

时间:2010-05-28 20:38:43

标签: python decorator

我想用这样的模式装饰一个函数:

def deco(func):
    def wrap(*a,**kw):
        print "do something"
        return func(*a,**kw)
    return wrap

问题是如果装饰的函数有这样的原型:

def function(a,b,c): return

当装饰时,原型被varargs破坏,例如,调用函数(1,2,3,4)不会导致异常。这是避免这种情况的方法吗? 如何使用与装饰(func)相同的原型定义wrap函数?

概念上有什么错误吗?

修改

我的不正确的想法是在不修改签名的情况下减轻“调用父方法”。像

这样的东西
def __init__(self, something)
    super(ClassName, self).__init__(something)

为:

@extended
def __init__(self, something):
    ...

我正在弄清楚这是否可能,如果这是有道理的。

修改的 正如Alex所指出的,以下代码没有例外:

function(1,2,3,4)

4 个答案:

答案 0 :(得分:4)

当你声明“调用函数(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之后)。

为了保留包装函数的元数据(name,docstring,使用functools.wraps。预测被调用函数在调用时会引发(以避免在调用它之前做其他工作)总是很难(一般来说,“不可能”)因为它等同于停止问题;当你只关心为参数名称和数字不匹配引发类型错误并希望以不同于任何其他异常情况的方式处理该特殊异常情况时,它具体是“特别难” - 一个特殊的要求; -0)

如果你坚持认为你绝对需要它(也许可以解释为什么?)我会很高兴(好吧,不是高兴,但会抓住我的毕竟我在我面前度过了一个漫长的周末;-)带你走迷宫之路。

答案 1 :(得分:3)

decorator module可帮助您创建保留函数签名的装饰器。

因此,您将在调用函数时获得预期的异常,inspect.getargspec将为您提供正确的签名。

它通过动态构建函数定义并使用exec来工作。不幸的是,没有一种更简单的内置方法可以做到这一点。

答案 2 :(得分:3)

这是一个技巧,涉及从装饰函数中获取原始参数规范,然后通过评估具有相同参数的字符串来创建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)

答案 3 :(得分:0)

首先调用result=func(*a,**kw),在打印“do something”之前得到TypeError。

def deco(func):
    def wrap(*a,**kw):
        result=func(*a,**kw)
        print "do something"
        return result
    return wrap

@deco
def function(a,b,c): return

function(1,2,3)
# do something
function(1,2,3,4)
# TypeError: function() takes exactly 3 arguments (4 given)