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