【问题标题】:Python3 'repeat' decorator with argument: @repeat(n)Python3 带有参数的“重复”装饰器:@repeat(n)
【发布时间】:2019-04-22 12:23:34
【问题描述】:

我已经看到(很多)教程和带有和不带参数的装饰器的 sn-ps,包括那些我认为是规范答案的两个: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 还是 Python 3,尽管这在很大程度上无关紧要。我向您承认,错误消息非常相似,如果不相等,即使不一样。但是,我的意图不是从全局范围访问包装器内定义的计数器。我打算让这个柜台纯粹是本地的,并且做到了。我的编码错误完全在别处。事实证明,Kevin(如下)提供的出色讨论和解决方案具有本质上的性质,与仅在包装器定义块中添加 nonlocal &lt;var&gt; 完全不同(在 Python 3.x 的情况下)。我不会重复凯文的论点。它们是清晰的,可供所有人使用。

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

【问题讨论】:

  • 旁白:当代码风格一致时,代码更容易阅读。在其他 PEP 8 格式的代码中调用 nbrTimes 会在视觉上不和谐。考虑将其重命名为 nbr_timesnumber_of_times 甚至只是 times
  • @Chris;对不起,我是新手。我什至不知道有这种约定。在写作时,我倾向于使用我在笔中找到的任何墨水。用于单挑的 Tx ...
  • 不客气,Cbhihe。为了完整起见,这里是 PEP 8(Python 的官方风格指南)的链接:python.org/dev/peps/pep-0008。您不必必须遵循该指南,但大多数 Python 开发人员都会这样做。祝你好运!

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


【解决方案1】:

建议的重复问题Scope of variables in python decorators - changing parameters 提供了有用的信息,解释了为什么wrapper_repeatnbrTimes 视为局部变量,以及如何使用nonlocal 使其识别@987654326 定义的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”各只显示一次,“baz”显示零次。我认为这不是我们想要的行为。

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

【讨论】:

  • [Arch 上的 Python 3.7.3] -- UnboundLocalError: local variable 'nbrTimes' referenced before assignment 仍然存在问题。这是与之前直接从上面复制/粘贴的代码完全相同的错误......我认为差异源于我在控制台中调用from decorators, import repeat,而不是像你的情况那样将它全部放在一个脚本中(我假设)。我从 decorators.py 所在的同一目录启动控制台。会不会是 PYTHONPATH 问题?
  • 我试过了:@repeat()@repeat(3)@repeat(nbrTimes=4),最后是@repeat,然后是函数定义和函数调用……没想到后者能正常工作。没有。 :-|
  • 奇怪,我希望我的最终代码块即使您将其导入控制台也能正常工作。 local variable 'nbrTimes' referenced before assignment 应该是不可能的,因为我在作业的左侧从来没有nbrTimes。我怀疑您的代码以某种方式引用了您的 decorators 模块的旧版本。我建议关闭控制台并打开一个全新的控制台。重命名您的 decorators 文件也可能会有所帮助。
  • @repeat@repeat(3)@repeat(nbrTimes=4) 应该都可以工作。我希望@repeat 运行时不会崩溃,但它实际上不会调用装饰函数。
  • +1 一切都很好,Kevin tx 非常感谢温和的婴儿一步一步的解释。奇怪的是,我确实在每次更改和decorators.py 的vim buffer-write 时重新发出from decorators import repeat,但我的控制台会话没有考虑到这一点。它坚持最初导入错误的装饰器定义。 (刚刚检查了我在控制台历史记录中发出的命令序列,控制台没有重新导入。)不是我预期的行为......
猜你喜欢
  • 2014-10-14
  • 2014-07-21
  • 2023-03-26
  • 2013-07-19
  • 2018-01-07
  • 2022-12-28
  • 2013-02-18
相关资源
最近更新 更多