Подтвердить что ты не робот

Объем переменных в декораторе python

У меня очень странная проблема в декораторе Python 3.

Если я это сделаю:

def rounds(nr_of_rounds):
    def wrapper(func):
        @wraps(func)
        def inner(*args, **kwargs):
            return nr_of_rounds
        return inner
    return wrapper

он работает отлично. Однако, если я это сделаю:

def rounds(nr_of_rounds):
    def wrapper(func):
        @wraps(func)
        def inner(*args, **kwargs):
            lst = []
            while nr_of_rounds > 0:
                lst.append(func(*args, **kwargs))
                nr_of_rounds -= 1
            return max(lst)
        return inner
    return wrapper

Я получаю:

while nr_of_rounds > 0:
UnboundLocalError: local variable 'nr_of_rounds' referenced before assignment

Другими словами, я могу использовать nr_of_rounds во внутренней функции, если я использую его в возврате, но я не могу с ним ничего сделать. Почему это?

4b9b3361

Ответ 1

Так как nr_of_rounds подхвачен замыканием, вы можете считать его переменной "только для чтения". Если вы хотите записать его (например, чтобы уменьшить его), вам нужно явно указать python - в этом случае ключевое слово python3.x nonlocal будет работать.

В качестве краткого объяснения, что делает Cpython, когда он сталкивается с определением функции, он просматривает код и решает, являются ли все переменные локальными или нелокальными. Локальные переменные (по умолчанию) - это все, что появляется в левой части оператора присваивания, переменных цикла и входных аргументов. Каждое другое имя является нелокальным. Это позволяет оптимизировать оптимизацию 1. Чтобы использовать нелокальную переменную так же, как и локальную, вам нужно явно указать python либо с помощью инструкции global или nonlocal. Когда python сталкивается с тем, что, по его мнению, должно быть локальным, но на самом деле это не так, вы получаете UnboundLocalError.

1 Генератор байт-кода Cpython превращает локальные имена в индексы в массиве, так что поиск по локальному имени (команда байт-кода LOAD_FAST) выполняется так же быстро, как индексирование массива плюс нормальные служебные данные байт-кода.

Ответ 2

В настоящее время нет способа сделать то же самое для переменных в приложении областей действия, но Python 3 вводит новое ключевое слово "нелокальное", которое будет действовать аналогично глобальному, но для вложенных областей функций.
поэтому в вашем случае просто используйте как:
def inner(*args, **kwargs): nonlocal nr_of_rounds lst = [] while nr_of_rounds > 0: lst.append(func(*args, **kwargs)) nr_of_rounds -= 1 return max(lst) return inner
Для получения дополнительной информации Краткое описание правил определения области видимости?