简体   繁体   English

Python3'repeat'装饰器,带有参数:@repeat(n)

[英]Python3 'repeat' decorator with argument: @repeat(n)

I have seen (a great) many tutorials and snippets of decorators w/ and w/o arguments, including those two I would look consider as canonical answers: Decorators with arguments , python decorator arguments with @ syntax , but I don't see why I get an error in my code. 我已经看过(很棒的)许多带w和w / o参数的装饰器的教程和片段,包括我认为可以视为规范的答案的两个: 带参数的 装饰器,带@语法的python装饰器参数 ,但我不明白为什么我的代码中出现错误。

The code below lives in the file decorators.py : 以下代码位于文件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

The first warning I get from my syntax-checker is that nbrTimes is an "unused argument". 我从语法检查器收到的第一个警告是nbrTimes是一个“未使用的参数”。

I tested the above in python3 interactive console with: 我在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.

I just don't see where I'm bungling it. 我只是看不到我在哪里乱扯。 In other examples the passed parameter (here nbrTimes ) was not "used" until later in the inner function, so the "unused argument" warning and error upon execution leave me kind of high and dry. 在其他示例中,直到稍后在内部函数中才使用“传递的参数”(此处为nbrTimes ),因此执行时出现的“未使用的参数”警告和错误使我nbrTimes Still relatively new to Python. 对于Python来说还是相对较新的。 Help much appreciated. 帮助非常感谢。

Edit: (in response to duplicate flag by @recnac) It is not clear at all what OP in your purported duplicate wanted to achieve. 编辑:( 响应@recnac的重复标志)根本不清楚您声称的重复项中要实现的OP。 I can only surmise that he/she intended to have access to a counter defined inside a decorator's wrapper, from global scope, and failed to declare it as nonlocal . 我只能推测他/她打算从全局范围访问装饰器包装器内定义的计数器,但未能将其声明为nonlocal Fact is we don't even know whether OP dealt with Python 2 or 3, although it is largely irrelevant here. 事实是,我们甚至都不知道OP处理的是Python 2还是3,尽管在这里与OP无关。 I concede to you that the error messages were very similar, if not equivalent, if not the same. 我向您承认错误消息非常相似,如果不相同,甚至不相同。 However my intent was not to access a in-wrapper-defined counter from global scope. 但是,我的目的不是从全局范围访问包装器内定义的计数器。 I intended to make this counter purely local, and did. 我打算使这个计数器纯粹是本地化的,并且做到了。 My coding errors were elsewhere altogether. 我的编码错误在其他地方。 It turns out the excellent discussion and solution provided by Kevin (below) are of a nature, totally different from just adding a nonlocal <var> inside the wrapper definition block (in case of Python 3.x). 事实证明,凯文(下面)提供的出色的讨论和解决方案是自然的,与在包装器定义块内添加nonlocal <var>完全不同(对于Python 3.x)。 I won't be repeating Kevin's arguments. 我不会重复凯文的论点。 They are limpid and available to all. 它们清澈透明,可供所有人使用。

Finally I go out on a limb and will say that the error message is perhaps the least important of all here, even though it is clearly a consequence of my bad code. 最终,我走了出去,会说错误消息可能是这里所有问题中最不重要的,即使这显然是我的错误代码造成的。 For that I make amends, but this post is definitely not a rehash of the proposed "duplicate". 为此,我做出了修改,但此职位绝对不是对提议的“重复”内容的重新讨论。

The proposed duplicate question, Scope of variables in python decorators - changing parameters gives useful information that explains why wrapper_repeat considers nbrTimes to be a local variable, and how nonlocal might be used to make it recognize the nbrTimes defined by repeat . 提出的重复问题“ python装饰器中的变量范围-更改参数”提供了有用的信息,这些信息解释了为什么wrapper_repeat认为nbrTimes是局部变量,以及如何使用nonlocal局部变量来使其识别由repeat定义的nbrTimes This would fix the exception, but I don't think it's a complete solution in your case. 这样可以解决该异常,但是对于您的情况,我认为这不是一个完整的解决方案。 Your decorated function will still not 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")

Result: 结果:

displaying: foo
displaying: bar

"foo" and "bar" are each displayed only one time, and "baz" is displayed zero times. “ foo”和“ bar”分别仅显示一次,而“ baz”则显示0次。 I assume this is not the desired behavior. 我认为这不是理想的行为。

The first two calls to display fail to repeat because of the return func(*args, **kwargs) inside your while loop. 由于while循环内有return func(*args, **kwargs)因此无法重复display的前两个调用。 The return statement causes wrapper_repeat to terminate immediately, and no further iterations of the while will occur. return语句使wrapper_repeat立即终止,并且while不会再进行任何迭代。 So no decorated function will repeat more than once. 因此,任何修饰功能都不会重复一次以上。 One possible solution is to remove the return and just call the function. 一种可能的解决方案是删除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")

Result: 结果:

displaying: foo
displaying: foo

"foo" is being displayed twice, but now neither "bar" nor "baz" appear. “ foo”被显示两次,但是现在“ bar”和“ baz”都没有出现。 This is because nbrTimes is shared across all instances of your decorator, thanks to nonlocal . 这是因为nbrTimes跨你的装饰的所有实例,由于共享nonlocal once display("foo") decrements nbrTimes to zero, it remains at zero even after the call completes. 一旦display("foo") nbrTimes减为零,即使调用完成后,它仍保持为零。 display("bar") and display("baz") will execute their decorators, see that nbrTimes is zero, and terminate without calling the decorated function at all. display("bar")display("baz")将执行其装饰器,看到nbrTimes为零,并终止而nbrTimes不调用装饰的函数。

So it turns out that you don't want your loop counter to be nonlocal. 因此,事实证明您不希望循环计数器是非本地的。 But this means you can't use nbrTimes for this purpose. 但这意味着您不能为此目的使用nbrTimes Try creating a local variable based on nbrTimes ' value, and decrement that instead. 尝试根据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")

Result: 结果:

displaying: foo
displaying: foo
displaying: bar
displaying: bar
displaying: baz
displaying: baz

... And while you're at it, you may as well use a for loop instead of a while . ...并且在使用时,最好使用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")

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM