我已经看过(很棒的)很多教程和装饰器w /和w / o参数的片段,包括那两个我认为是典型的答案: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
)直到内部函数的后期才被“使用”,因此执行时“未使用的参数”警告和错误会让我感到高度干燥。对Python来说还是比较新的。非常感谢。
编辑:(响应@recnac的duplicate旗帜)目前尚不清楚你声称的副本中的OP想要实现什么。我只能猜测他/她打算从全局范围访问装饰器包装器中定义的计数器,并且未能将其声明为nonlocal
。事实是我们甚至不知道OP是否处理Python 2或3,尽管这在很大程度上与此无关。我向你们承认错误信息非常相似,如果不相同的话,如果不相同的话。但是我的意图不是从全局范围访问包装器定义的计数器。我打算让这个柜台纯粹是本地的,并且做到了。我的编码错误完全在别处。事实证明,Kevin(下面)提供的优秀讨论和解决方案具有一定的性质,完全不同于在包装器定义块中添加nonlocal <var>
(在Python 3.x的情况下)。我不会重复凯文的论点。它们清澈透明,适合所有人。
最后,我会说出错误消息可能是最不重要的,尽管这显然是我的错误代码的结果。为此我做了补偿,但这篇文章绝对不是对提议的“重复”的重复。
提出的重复问题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”显示为零次。我认为这不是理想的行为。
由于你的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")