我已经看到(很棒的)很多装饰器的教程和摘要,其中没有参数,包括我认为可以视为规范答案的两个:Decorators with arguments,python decorator arguments with @ syntax,但是我不知道没有看到为什么我的代码中出现错误。
下面的代码位于文件decorators.py
中:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Description: decorators
"""
import functools
def repeat(nbrTimes=2):
'''
Define parametrized decorator with arguments
Default nbr of repeats is 2
'''
def real_repeat(func):
"""
Repeats execution 'nbrTimes' times
"""
@functools.wraps(func)
def wrapper_repeat(*args, **kwargs):
while nbrTimes != 0:
nbrTimes -= 1
return func(*args, **kwargs)
return wrapper_repeat
return real_repeat
我从语法检查器得到的第一个警告是nbrTimes
是一个“未使用的参数”。
我在python3交互式控制台中使用以下命令测试了上述内容:
>>> from decorators import repeat
>>> @repeat(nbrTimes=3)
>>> def greetings():
>>> print("Howdy")
>>>
>>> greetings()
Traceback (most recent call last):
File "<stdin>", line 1 in <module>
File path/to/decorators.py, line xx in wrapper_repeat
'''
UnboundLocalError: local variable 'nbrTimes' referenced before assignment.
我只是看不到我在哪里弄乱它。在其他示例中,直到稍后在内部函数中,传递的参数(此处为nbrTimes
才被“ used” 使用,因此执行时出现的“ unused arguments”警告和错误使我有点过高,干。对于Python来说还是相对较新的。非常感谢。
编辑: (响应@recnac的duplicate标志)
完全不清楚您声称的副本中要实现什么OP。我只能推测他/她打算从全局范围访问装饰器包装器中定义的计数器,但未将其声明为nonlocal
。事实是,我们甚至都不知道OP处理的是Python 2还是3,尽管在这里与OP无关。我向您承认错误消息非常相似,如果不相同,甚至不相同。但是,我的目的不是从全局范围访问包装器内定义的计数器。我打算使这个计数器纯粹是本地化的,并且做到了。我的编码错误在其他地方。事实证明,凯文(下面)提供的出色的讨论和解决方案是自然的,与仅在包装器定义块内添加nonlocal <var>
(对于Python 3.x)完全不同。我不会重复凯文的论点。它们清澈透明,可供所有人使用。
最后,我走了出去,会说错误消息可能是这里所有问题中最不重要的,即使这显然是我的错误代码造成的。为此,我做出了修改,但这篇文章绝对不是对拟议中的“重复”的重述。
答案 0 :(得分:3)
建议的重复问题Scope of variables in python decorators - changing parameters提供了有用的信息,这些信息解释了wrapper_repeat
为何认为nbrTimes
是局部变量,以及如何使用nonlocal
来使其识别nbrTimes
定义的repeat
。这样可以解决该异常,但是对于您的情况,我认为这不是一个完整的解决方案。装饰后的功能仍然不会重复。
import functools
def repeat(nbrTimes=2):
'''
Define parametrized decorator with arguments
Default nbr of repeats is 2
'''
def real_repeat(func):
"""
Repeats execution 'nbrTimes' times
"""
@functools.wraps(func)
def wrapper_repeat(*args, **kwargs):
nonlocal nbrTimes
while nbrTimes != 0:
nbrTimes -= 1
return func(*args, **kwargs)
return wrapper_repeat
return real_repeat
@repeat(2)
def display(x):
print("displaying:", x)
display("foo")
display("bar")
display("baz")
结果:
displaying: foo
displaying: bar
“ foo”和“ bar”仅显示一次,而“ baz”显示0次。我认为这不是理想的行为。
由于display
循环内的return func(*args, **kwargs)
,因此对while
的前两个调用无法重复。 return语句使wrapper_repeat
立即终止,并且不会发生while
的进一步迭代。因此,任何修饰功能都不会重复一次以上。一种可能的解决方案是删除return
并仅调用该函数。
import functools
def repeat(nbrTimes=2):
'''
Define parametrized decorator with arguments
Default nbr of repeats is 2
'''
def real_repeat(func):
"""
Repeats execution 'nbrTimes' times
"""
@functools.wraps(func)
def wrapper_repeat(*args, **kwargs):
nonlocal nbrTimes
while nbrTimes != 0:
nbrTimes -= 1
func(*args, **kwargs)
return wrapper_repeat
return real_repeat
@repeat(2)
def display(x):
print("displaying:", x)
display("foo")
display("bar")
display("baz")
结果:
displaying: foo
displaying: foo
“ foo”被显示两次,但是现在“ bar”和“ baz”都没有出现。这是因为nbrTimes
使nonlocal
在装饰器的所有实例之间共享。一旦display("foo")
递减nbrTimes
到零,即使调用完成,它也保持为零。 display("bar")
和display("baz")
将执行其修饰符,看到nbrTimes
为零,并终止而根本不调用修饰的函数。
因此,事实证明您不希望自己的循环计数器是非本地的。但这意味着您不能为此目的使用nbrTimes
。尝试根据nbrTimes
'的值创建一个局部变量,然后递减该变量。
import functools
def repeat(nbrTimes=2):
'''
Define parametrized decorator with arguments
Default nbr of repeats is 2
'''
def real_repeat(func):
"""
Repeats execution 'nbrTimes' times
"""
@functools.wraps(func)
def wrapper_repeat(*args, **kwargs):
times = nbrTimes
while times != 0:
times -= 1
func(*args, **kwargs)
return wrapper_repeat
return real_repeat
@repeat(2)
def display(x):
print("displaying:", x)
display("foo")
display("bar")
display("baz")
结果:
displaying: foo
displaying: foo
displaying: bar
displaying: bar
displaying: baz
displaying: baz
...当您使用它时,不妨使用for
循环而不是while
。
import functools
def repeat(nbrTimes=2):
'''
Define parametrized decorator with arguments
Default nbr of repeats is 2
'''
def real_repeat(func):
"""
Repeats execution 'nbrTimes' times
"""
@functools.wraps(func)
def wrapper_repeat(*args, **kwargs):
for _ in range(nbrTimes):
func(*args, **kwargs)
return wrapper_repeat
return real_repeat
@repeat(2)
def display(x):
print("displaying:", x)
display("foo")
display("bar")
display("baz")