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

时间:2019-04-22 12:23:34

标签: python python-3.x python-decorators default-parameters

我已经看到(很棒的)很多装饰器的教程和摘要,其中没有参数,包括我认为可以视为规范答案的两个:Decorators with argumentspython 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)完全不同。我不会重复凯文的论点。它们清澈透明,可供所有人使用。

最后,我走了出去,会说错误消息可能是这里所有问题中最不重要的,即使这显然是我的错误代码造成的。为此,我做出了修改,但这篇文章绝对不是对拟议中的“重复”的重述。

1 个答案:

答案 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")