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

Python3 'repeat' decorator with argument: @repeat(n)

我看过(很多)许多带有和 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 是一个 "unused argument"。

我在 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 argument" 警告和执行错误让我有点高和干燥。 Python 还是比较新的。非常感谢帮助。

编辑:(响应@recnac 的 duplicate 标志) 根本不清楚您声称的副本中的 OP 想要实现什么。我只能推测 he/she 打算从全局范围访问装饰器包装器内定义的计数器,但未能将其声明为 nonlocal。事实上,我们甚至不知道 OP 处理的是 Python 2 还是 3,尽管这在很大程度上无关紧要。我承认错误消息非常相似,如果不等同,如果不相同。但是,我的意图不是从全局范围访问包装内定义的计数器。我打算让这个柜台纯粹是本地的,并且做到了。我的编码错误完全在别处。事实证明,Kevin(下文)提供的出色讨论和解决方案具有某种性质,与仅在包装器定义块中添加 nonlocal <var> 完全不同(在 Python 3.x 的情况下) .我不会重复凯文的论点。它们清澈透明,可供所有人使用。

最后我冒昧地说,错误消息可能是这里所有消息中最不重要的,尽管它显然是我的错误代码的结果。为此,我做出了补偿,但是这个 post 绝对不是对提议的 "duplicate".

的翻版

提议的重复问题,Scope of variables in python decorators - changing parameters 提供了有用的信息,解释了为什么 wrapper_repeat 认为 nbrTimes 是一个局部变量,以及 nonlocal 如何被用来使它识别由 repeat 定义的 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):
            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"各只显示1次,"baz"显示0次。我认为这不是所需的行为。

由于 while 循环中的 return func(*args, **kwargs),前两次调用 display 失败。 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")