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

问题描述 投票:0回答:1

我已经看过(很棒的)很多教程和装饰器w /和w / o参数的片段,包括那两个我认为是典型的答案: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)直到内部函数的后期才被“使用”,因此执行时“未使用的参数”警告和错误会让我感到高度干燥。对Python来说还是比较新的。非常感谢。

编辑:(响应@recnac的duplicate旗帜)目前尚不清楚你声称的副本中的OP想要实现什么。我只能猜测他/她打算从全局范围访问装饰器包装器中定义的计数器,并且未能将其声明为nonlocal。事实是我们甚至不知道OP是否处理Python 2或3,尽管这在很大程度上与此无关。我向你们承认错误信息非常相似,如果不相同的话,如果不相同的话。但是我的意图不是从全局范围访问包装器定义的计数器。我打算让这个柜台纯粹是本地的,并且做到了。我的编码错误完全在别处。事实证明,Kevin(下面)提供的优秀讨论和解决方案具有一定的性质,完全不同于在包装器定义块中添加nonlocal <var>(在Python 3.x的情况下)。我不会重复凯文的论点。它们清澈透明,适合所有人。

最后,我会说出错误消息可能是最不重要的,尽管这显然是我的错误代码的结果。为此我做了补偿,但这篇文章绝对不是对提议的“重复”的重复。

python python-3.x python-decorators default-parameters
1个回答
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”显示为零次。我认为这不是理想的行为。

由于你的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")
© www.soinside.com 2019 - 2024. All rights reserved.