Область переменных в Python decorator

у меня очень странная проблема в декораторе 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во внутренней функции, если я использую его в возврате, но я не могу сделать с ним ничего другого. Почему так?

2 ответов


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

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

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


В настоящее время нет способа сделать то же самое для переменных в охватывающих областях функций, но 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
Для получения дополнительной информации краткое описание правил определения области?