为什么函数装饰器中的 **kwargs 值与函数中的值不同?

Why is **kwargs value in a function decorator different from the value in a function?

我正在尝试编写一个函数装饰器,它打印参数,用它调用一个函数,我注意到一件事。如果我们创建装饰器,它只打印 kwargs,它就可以工作。所以

def counted(fn):
    def wrapper(*args, **kwargs):
        print kwargs
        return fn(*args, **kwargs)
    return wrapper
@counted
def foo(a, b = 4, c = 'blah-blah', *args, **kwargs):
    return

foo(a=1, b=2, c=3, args=[4, 5], kwargs={'d': 6, 'g': 12.9})

产出

{'a': 1, 'c': 3, 'b': 2, 'args': [4, 5], 'kwargs': {'d': 6, 'g': 12.9}}

然而,如果调用函数时没有装饰器,但参数打印在函数内部,输出不同:

#@counted
def foo(a, b = 4, c = 'blah-blah', *args, **kwargs):
print kwargs    #added this line
return

输出:

{'args': [4, 5], 'kwargs': {'d': 6, 'g': 12.9}}

这是为什么?为什么 print kwargs 在装饰器中打印前三个参数,但在函数中却不打印?

kwargs 捕获函数签名中未明确命名的任何 key=value 参数。 foo 具体命名为 abc,因此不会捕获这些名称,但您的装饰器包装器名称为 none,所以所有内容都被捕获在 kwargs 中。请注意,这包括您传递给调用的 args=...kwargs=... 参数

您需要将 print 添加到 两个 位置,您会发现装饰器并没有在这里做任何特殊的事情:

>>> @counted
... def foo(a, b = 4, c = 'blah-blah', *args, **kwargs):
...     print kwargs
...
>>> foo(a=1, b=2, c=3, args=[4, 5], kwargs={'d': 6, 'g': 12.9})
{'a': 1, 'c': 3, 'b': 2, 'args': [4, 5], 'kwargs': {'d': 6, 'g': 12.9}}
{'args': [4, 5], 'kwargs': {'d': 6, 'g': 12.9}}

您也不打印 args,因此您看不到传递的 位置 参数。让我们再添加一些打印语句来显示实际情况:

def counted(fn):
    def wrapper(*args, **kwargs):
        print 'counted args: {!r}'.format(args)
        print 'counted kwargs: {!r}'.format(kwargs)
        return fn(*args, **kwargs)
    return wrapper

@counted
def foo(a, b = 4, c = 'blah-blah', *args, **kwargs):
    print 'foo a, b, c: {!r}, {!r}, {!r}'.format(a, b, c)
    print 'foo args: {!r}'.format(args)
    print 'foo kwargs: {!r}'.format(kwargs)

运行这样你会得到更好的照片:

>>> foo(a=1, b=2, c=3, args=[4, 5], kwargs={'d': 6, 'g': 12.9})
counted args: ()
counted kwargs: {'a': 1, 'c': 3, 'b': 2, 'args': [4, 5], 'kwargs': {'d': 6, 'g': 12.9}}
foo a, b, c: 1, 2, 3
foo args: ()
foo kwargs: {'args': [4, 5], 'kwargs': {'d': 6, 'g': 12.9}}

再次注意,'args''kwargs' 都是 本身的关键字参数 ;它们最终都成为 kwargs 字典 中的键。您可以给它们起任何其他名称,它们最终仍会使用新名称:

>>> foo(a=1, b=2, c=3, spam=[4, 5], eggs={'d': 6, 'g': 12.9})
counted args: ()
counted kwargs: {'a': 1, 'c': 3, 'b': 2, 'eggs': {'d': 6, 'g': 12.9}, 'spam': [4, 5]}
foo a, b, c: 1, 2, 3
foo args: ()
foo kwargs: {'eggs': {'d': 6, 'g': 12.9}, 'spam': [4, 5]}

您没有为 *args**kwargs 包罗万象的变量指定值,您在 中指定了更多要捕获的任意关键字参数kwargs.

剩余的参数,abc 是 'captured' 由 foo 函数的命名参数。您在 foo 中单独定义了它们,因此它们被专门分配给了它们。它们不是 wrapper() 定义的一部分,因此无法在那里捕获它们。

请注意,您没有向调用传递任何 positional 参数,因此 *args 变量为 empty。改为传递一些位置参数:

>>> foo(1, 2, 3, 42, args=[4, 5], kwargs={'d': 6, 'g': 12.9})
counted args: (1, 2, 3, 42)
counted kwargs: {'args': [4, 5], 'kwargs': {'d': 6, 'g': 12.9}}
foo a, b, c: 1, 2, 3
foo args: (42,)
foo kwargs: {'args': [4, 5], 'kwargs': {'d': 6, 'g': 12.9}}

现在前 3 个值仍然在 abc 中,但是 extra 位置值 42foo 中的 args 中仍然可用。在包装器中,没有明确命名的参数,它们 all 最终在 *args.

如果您希望 args=[..]kwargs={...} 值作为 单独的 参数应用,则需要使用相同的 *** 调用 而不使用捕获名称时的前缀 :

>>> foo(1, 2, 3, 42, *[4, 5], **{'d': 6, 'g': 12.9})
counted args: (1, 2, 3, 42, 4, 5)
counted kwargs: {'d': 6, 'g': 12.9}
foo a, b, c: 1, 2, 3
foo args: (42, 4, 5)
foo kwargs: {'d': 6, 'g': 12.9}

现在45也出现在args中,'d''g'键直接出现在kwargs字典中.