关于python:在装饰器中解析args和kwargs

Parsing args and kwargs in decorators

我有一个接受args和kwargs的函数,我需要根据函数中第二个arg的值在decorator中执行一些操作,如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
def workaround_func():
    def decorator(fn):
        def case_decorator(*args, **kwargs):
            if args[1] == 2:
                print('The second argument is a 2!')
            return fn(*args, **kwargs)
        return case_decorator
    return decorator

@workaround_func()
def my_func(arg1, arg2, kwarg1=None):
    print('arg1: {} arg2: {}, kwargs: {}'.format(arg1, arg2, kwarg1))

问题在于,python允许用户使用第二个参数作为常规参数或关键字参数来调用函数,因此,如果用户使用arg2作为kwarg来调用my_func,则会引发IndexError,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
In [8]: d.my_func(1, 2, kwarg1=3)
The second argument is a 2!
arg1: 1 arg2: 2, kwargs: 3

In [9]: d.my_func(1, arg2=2, kwarg1=3)
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-9-87dc89222a9e> in <module>()
----> 1 d.my_func(1, arg2=2, kwarg1=3)

/home/camsparr/decoratorargs.py in case_decorator(*args, **kwargs)
      2     def decorator(fn):
      3         def case_decorator(*args, **kwargs):
----> 4             if args[1] == 2:
      5                 print('The second argument is a 2!')
      6             return fn(*args, **kwargs)

IndexError: tuple index out of range

有没有一种方法可以解决这个问题,而不只是做一个try/except并抓住IndexError


这是我能想到的处理它的最有力的方法…技巧是检查第二个参数的名称。然后,在decorator中,检查kwargs中是否存在该名称。如果是,那你就用它。如果没有,则使用args

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from inspect import getargspec    

def decorate(fn):
    argspec = getargspec(fn)
    second_argname = argspec[0][1]
    def inner(*args, **kwargs):
        special_value = (kwargs[second_argname]
                         if second_argname in kwargs else args[1])
        if special_value == 2:
            print"foo"
        else:
            print"no foo for you"
        return fn(*args, **kwargs)
    return inner

@decorate
def foo(a, b, c=3):
    pass

foo(1,2,3)
foo(1,b=2,c=4)
foo(1,3,5)
foo(1,b=6,c=5)

运行此命令将导致:

1
2
3
4
foo
foo
no foo for you
no foo for you

果不其然。


我使用python decorator包找到了一个答案。这个包的一个特性是,不管用户如何传递位置/关键字参数,它都会保留它们。它还有减少大量代码的额外好处,因此我的原始代码:

1
2
3
4
5
6
7
8
9
10
11
12
def workaround_func():
    def decorator(fn):
        def case_decorator(*args, **kwargs):
            if args[1] == 2:
                print('The second argument is a 2!')
            return fn(*args, **kwargs)
        return case_decorator
    return decorator

@workaround_func()
def my_func(arg1, arg2, kwarg1=None):
    print('arg1: {} arg2: {}, kwargs: {}'.format(arg1, arg2, kwarg1))

变成:

1
2
3
4
5
6
7
8
9
10
11
from decorator import decorator

@decorator
def workaround_decorator(f, *args, **kwargs):
    if args[1] == 2:
        print('The second argument is 2!')
    return f(*args, **kwargs)

@workaround_decorator
def my_func(arg1, arg2, kwarg1=None):
    print('arg1: {} arg2: {}, kwargs: {}'.format(arg1, arg2, kwarg1))


arg1似乎是必需的参数,但arg2可能是可选的。您可以将第3行更改为:

1
2
    def case_decorator(arg1, arg2=None, *args, **kwargs):
                            # or =0 or ='' whichever best

第6行:

1
        return fn(arg1, arg2, *args, **kwargs)

如果arg1也是可选的,请将第3行更改为arg1=none或=0或='