Python3 «повторяющийся» декоратор с аргументом: @repeat (n) - PullRequest
0 голосов
/ 22 апреля 2019

Я видел (большое) множество учебных пособий и фрагментов декораторов с / и без аргументов, включая те два, которые я бы выглядел, рассматривая как канонические ответы: Декораторы с аргументами , python decoratorаргументы с @ синтаксисом , но я не понимаю, почему я получаю ошибку в своем коде.

Код ниже живет в файле 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) Не совсем понятно, какой ОП в вашемякобы дубликат хотел добиться.Я могу только предположить, что он / она намеревался иметь доступ к счетчику, определенному внутри оболочки декоратора, из глобальной области видимости и не смог объявить его как nonlocal.Факт в том, что мы даже не знаем, имел ли OP дело с Python 2 или 3, хотя здесь это в значительной степени не имеет значения.Я признаю вам, что сообщения об ошибках были очень похожи, если не эквивалентны, если не совпадают.Однако мое намерение не состояло в том, чтобы получить доступ к определенному в обертке счетчику из глобальной области видимости.Я намеревался сделать этот счетчик чисто локальным, и сделал.Мои ошибки кодирования были в другом месте вообще.Оказывается, превосходное обсуждение и решение, предоставленные Кевином (ниже), имеют природу, совершенно отличную от простого добавления nonlocal <var> внутри блока определения оболочки (в случае Python 3.x).Я не буду повторять аргументы Кевина.Они прозрачны и доступны всем.

Наконец я выхожу на конечность и скажу, что сообщение об ошибке, пожалуй, наименее важно из всех здесь, хотя оно явно является следствием моего плохого кода.За это я поправляюсь, но этот пост определенно не перефразирует предложенный «дубликат».

1 Ответ

3 голосов
/ 22 апреля 2019

Предлагаемый повторяющийся вопрос, Область действия переменных в декораторах Python - изменение параметров дает полезную информацию, объясняющую, почему 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")
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...