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