如何在Python 2.7中为包装函数添加关键字参数?

时间:2016-12-05 18:13:19

标签: python-2.7 decorator python-decorators functools

我首先要强调的是,我已经非常广泛地搜索了网络和Python文档+ StackOverflow,并且没有设法找到这个问题的答案。我还要感谢任何花时间阅读本文的人。

正如标题所示,我正在用Python编写装饰器,我希望它为包装函数添加关键字参数(请注意:我知道如何向装饰器本身添加参数,那不是我要求的。

这是我编写的一段代码的工作示例,它完全适用于Python 3(特别是Python 3.5)。它使用装饰器参数,为包装函数添加关键字参数,并为包装函数定义和添加新函数。

from functools import wraps

def my_decorator(decorator_arg1=None, decorator_arg2=False):
    # Inside the wrapper maker

    def _decorator(func):
        # Do Something 1

        @wraps(func)
        def func_wrapper(
                *args,
                new_arg1=False,
                new_arg2=None,
                **kwds):
            # Inside the wrapping function
            # Calling the wrapped function
            if new_arg1:
                return func(*args, **kwds)
            else:
                # do something with new_arg2
                return func(*args, **kwds)

        def added_function():
            print("Do Something 2")

        func_wrapper.added_function = added_function
        return func_wrapper

    return _decorator

现在可以通过以下方式使用此装饰器:

@my_decorator(decorator_arg1=4, decorator_arg2=True)
def foo(a, b):
    print("a={}, b={}".format(a,b))

def bar():
    foo(a=1, b=2, new_arg1=True, new_arg2=7)
    foo.added_function()

现在,虽然这适用于Python 3.5(我假设任何3.x),但我还没有设法让它适用于Python 2.7。在导入包含此代码的模块时,我在第一行尝试为SyntaxError: invalid syntax定义一个新的关键字参数func_wrapper,意思是说明new_arg1=False,的行。

将新关键字移动到func_wrapper的参数列表的开头,解决了SyntaxError,但似乎搞砸了包装函数的签名;我在调用TypeError: foo() takes exactly 2 arguments (0 given)时收到错误foo(1, 2)。如果我明确地分配参数,这个错误就会消失,就像在foo(a=1, b=2)中一样,但这显然是不够的 - 不出所料,我的新关键字参数似乎是"窃取"前两个位置参数发送到包装函数。这是Python 3没有发生的事情。

我很想得到你的帮助。感谢您抽出宝贵时间阅读本文。

谢伊

2 个答案:

答案 0 :(得分:3)

如果您只将其他参数指定为关键字,则可以将它们从kw字典中删除(参见下文)。如果你需要它们作为位置 AND 关键字参数,那么我认为你应该能够在原始函数上使用inspect.getargspec,然后在func_wrapper中处理args和kw。

以下代码在Ubuntu 14.04上使用Python 2.7,3.4(Ubuntu提供)和3.5(来自Continuum)进行测试。

from functools import wraps

def my_decorator(decorator_arg1=None, decorator_arg2=False):
    # Inside the wrapper maker

    def _decorator(func):
        # Do Something 1
        @wraps(func)
        def func_wrapper(
                *args,
                **kwds):
            # new_arg1, new_arg2 *CANNOT* be positional args with this technique
            new_arg1 = kwds.pop('new_arg1',False)
            new_arg2 = kwds.pop('new_arg2',None)
            # Inside the wrapping function
            # Calling the wrapped function
            if new_arg1:
                print("new_arg1 True branch; new_arg2 is {}".format(new_arg2))
                return func(*args, **kwds)
            else:
                print("new_arg1 False branch; new_arg2 is {}".format(new_arg2))
                # do something with new_arg2
                return func(*args, **kwds)

        def added_function():
            # Do Something 2
            print('added_function')

        func_wrapper.added_function = added_function
        return func_wrapper

    return _decorator

@my_decorator(decorator_arg1=4, decorator_arg2=True)
def foo(a, b):
    print("a={}, b={}".format(a,b))

def bar():
    pass
    #foo(1,2,True,7) # won't work
    foo(1, 2, new_arg1=True, new_arg2=7)
    foo(a=3, b=4, new_arg1=False, new_arg2=42)
    foo(new_arg2=-1,b=100,a='AAA')
    foo(b=100,new_arg1=True,a='AAA')
    foo.added_function()

if __name__=='__main__':
    import sys
    sys.stdout.flush()
    bar()

输出

new_arg1 True branch; new_arg2 is 7
a=1, b=2
new_arg1 False branch; new_arg2 is 42
a=3, b=4
new_arg1 False branch; new_arg2 is -1
a=AAA, b=100
new_arg1 True branch; new_arg2 is None
a=AAA, b=100
added_function

答案 1 :(得分:0)

要将参数添加到现有函数的签名中,同时使该函数的行为像普通的python函数(正确的帮助,签名和TypeError在提供错误的参数的情况下进行加注),则可以使用makefun,我专门开发了它来解决这个用例。

特别是makefun替代了@wraps,该new_sig具有一个try: # python 3.3+ from inspect import signature, Parameter except ImportError: from funcsigs import signature, Parameter from makefun import wraps def my_decorator(decorator_arg1=None, decorator_arg2=False): # Inside the wrapper maker def _decorator(func): # (1) capture the signature of the function to wrap ... func_sig = signature(func) # ... and modify it to add new optional parameters 'new_arg1' and 'new_arg2'. # (if they are optional that's where you provide their defaults) new_arg1 = Parameter('new_arg1', kind=Parameter.POSITIONAL_OR_KEYWORD, default=False) new_arg2 = Parameter('new_arg2', kind=Parameter.POSITIONAL_OR_KEYWORD, default=None) new_sig = add_signature_parameters(func_sig, last=[new_arg1, new_arg2]) # (2) create a wrapper with the new signature @wraps(func, new_sig=new_sig) def func_wrapper(*args, **kwds): # Inside the wrapping function # Pop the extra args (they will always be there, no need to provide default) new_arg1 = kwds.pop('new_arg1') new_arg2 = kwds.pop('new_arg2') # Calling the wrapped function if new_arg1: print("new_arg1 True branch; new_arg2 is {}".format(new_arg2)) return func(*args, **kwds) else: print("new_arg1 False branch; new_arg2 is {}".format(new_arg2)) # do something with new_arg2 return func(*args, **kwds) # (3) add an attribute to the wrapper def added_function(): # Do Something 2 print('added_function') func_wrapper.added_function = added_function return func_wrapper return _decorator @my_decorator(decorator_arg1=4, decorator_arg2=True) def foo(a, b): """This is my foo function""" print("a={}, b={}".format(a,b)) foo(1, 2, True, 7) # works, except if you use kind=Parameter.KEYWORD_ONLY above (py3 only) foo(1, 2, new_arg1=True, new_arg2=7) foo(a=3, b=4, new_arg1=False, new_arg2=42) foo(new_arg2=-1,b=100,a='AAA') foo(b=100,new_arg1=True,a='AAA') foo.added_function() help(foo) 参数,您可以在其中指定新签名。这是您的示例的编写方式:

new_arg1 True branch; new_arg2 is 7
a=1, b=2
new_arg1 True branch; new_arg2 is 7
a=1, b=2
new_arg1 False branch; new_arg2 is 42
a=3, b=4
new_arg1 False branch; new_arg2 is -1
a=AAA, b=100
new_arg1 True branch; new_arg2 is None
a=AAA, b=100
added_function
Help on function foo in module <...>:

foo(a, b, new_arg1=False, new_arg2=None)
    This is my foo function

它按预期运行:

kind=Parameter.KEYWORD_ONLY

因此,您可以看到公开的签名符合预期,并且您的用户看不到内部信息。请注意,您可以通过在新签名中设置from decopatch import function_decorator, DECORATED @function_decorator def my_decorator(decorator_arg1=None, decorator_arg2=False, func=DECORATED): # (1) capture the signature of the function to wrap ... func_sig = signature(func) # ... # (2) create a wrapper with the new signature @wraps(func, new_sig=new_sig) def func_wrapper(*args, **kwds): # Inside the wrapping function ... # (3) add an attribute to the wrapper def added_function(): # Do Something 2 print('added_function') func_wrapper.added_function = added_function return func_wrapper 来使两个新参数成为“仅关键字”,但是您已经知道这在python 2中不起作用。

最后,您可能有兴趣使用decopatch使装饰代码更具可读性和鲁棒性,以防止使用括号。除其他功能外,它还支持一种“扁平”样式,该样式非常适合您的情况,因为它消除了一层嵌套:

python manage.py inspectdb table1 >> app/models.py

(我也是这一本书的作者,因为我厌烦了嵌套和无括号处理,所以创建了它)