首页 > 解决方案 > Python3 带有参数的“重复”装饰器:@repeat(n)

问题描述

我已经看到(很多)教程和带参数和不带参数的装饰器片段,包括那些我认为是规范答案的两个:带参数的装饰器,带@语法的python装饰器参数但我不明白为什么我的代码中出现错误。

下面的代码位于文件中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标志) 根本不清楚您声称的副本中的 OP 想要实现什么。我只能推测他/她打算从全局范围访问装饰器包装器内定义的计数器,但未能将其声明为nonlocal。事实上,我们甚至不知道 OP 是处理 Python 2 还是 Python 3,尽管它在这里基本上无关紧要。我向你承认,错误消息非常相似,如果不相等,如果不是一样。但是我的意图不是从全局范围访问包装器内定义的计数器。我打算使这个计数器纯粹是本地的,并且确实做到了。我的编码错误完全在其他地方。事实证明,由提供的出色讨论和解决方案Kevin(下图)是一种性质,与仅仅在包装器定义块中添加一个完全不同nonlocal <var>(在 Python 3.x 的情况下)。我不会重复 Kevin 的论点。它们是清晰的并且可供所有人使用。

最后,我冒昧地说,错误消息可能是这里最不重要的,即使它显然是我的错误代码的结果。为此我进行了修正,但这篇文章绝对不是对提议的“重复”的重新讨论。

标签: pythonpython-3.xpython-decoratorsdefault-parameters

解决方案


提出的重复问题,python 装饰器中的变量范围 - 更改参数提供了有用的信息,解释了为什么wrapper_repeat认为nbrTimes是局部变量,以及如何nonlocal使用它来识别nbrTimesrepeat. 这将解决异常,但我认为这不是您的情况的完整解决方案。您的装饰功能仍然不会重复。

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 语句导致立即终止,并且不会发生进一步的迭代。所以没有装饰功能会重复一次以上。一种可能的解决方案是删除并调用该函数。return func(*args, **kwargs)whilewrapper_repeatwhilereturn

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

推荐阅读