Следующий код вызывает UnboundLocalError
:
def foo():
i = 0
def incr():
i += 1
incr()
print(i)
foo()
Есть ли способ сделать это?
Следующий код вызывает UnboundLocalError
:
def foo():
i = 0
def incr():
i += 1
incr()
print(i)
foo()
Есть ли способ сделать это?
Используйте оператор nonlocal
def foo():
i = 0
def incr():
nonlocal i
i += 1
incr()
print(i)
foo()
Для получения дополнительной информации об этом новом заявлении, добавленном в python 3.x, перейдите в https://docs.python.org/3/reference/simple_stmts.html#the-nonlocal-statement
Вы можете использовать i
в качестве аргумента следующим образом:
def foo():
i = 0
def incr(i):
return i + 1
i = incr(i)
print(i)
foo()
См. 9.2. Области и пространства имен Python:
если нет инструкции
global
- присваивания имен всегда входят в самую внутреннюю область.
также:
Оператор
global
может использоваться для указания того, что определенные переменные живут в глобальной области действия и должны быть отскочены туда; операторnonlocal
указывает, что конкретные переменные живут в закрывающей области и должны быть отскакированы там.
У вас много решений:
i
в качестве аргумента ✓ (я бы пошел с этим)nonlocal
Обратите внимание, что в Python2.x вы можете получить доступ к нелокальным переменным, но вы не можете изменить их.
Люди ответили на ваш вопрос, но никто, кажется, не спрашивает, почему именно это происходит.
Следующий код вызывает
UnboundLocalError
Итак, почему? Возьмем цитату из FAQ:
Когда вы назначаете переменную в области видимости, эта переменная становится локальной для этой области и тени любой аналогичной именованной переменной во внешней области.
Внутри вашей вложенной функции выполняется назначение с помощью оператора +=
. Это означает, что i += 1
будет приблизительно выполнять i = i + 1
(с точки зрения привязки). В результате i
в выражении i + 1
будет поиск в локальной области (потому что он используется в операторе присваивания) для функции incr
, где он не будет найден, в результате получится UnboundLocalError: reference before assignment
.
Есть много способов решить эту проблему, и python 3 более изящна в подходе, который вы можете использовать, чем python 2.
Операторы nonlocal
указывают Python на поиск имени в охватывающей области (так что в этом случае в области функции foo()
) для ссылок на имена:
def foo():
i = 0
def incr():
nonlocal i
i +=1
incr()
print(i)
Атрибуты функции Python 2.x
и 3.x
:
Помните, что функции являются объектами первого класса, поэтому они могут сохранять состояние. Используйте атрибуты функции для доступа и изменения состояния функции, хорошо с этим подходом, она работает на пиках all и не требует инструкций global, nonlocal
.
def foo():
foo.i = 0
def incr():
foo.i +=1
incr()
print(foo.i)
Оператор global
(Python 2.x
, 3.x
):
Действительно самый уродливый из группы, но выполняет свою работу:
i = 0
def foo():
def incr():
global i
i += 1
incr()
print(i)
foo()
Опция передачи аргумента функции получает тот же результат, но не относится к мутированию охватывающей области в смысле nonlocal
и global
и больше похожа на представленные атрибуты функции. Он создает новую локальную переменную в функции inc
, а затем повторно привязывает имя к i
с помощью i = incr(i)
, но он действительно выполняет задание.
В Python int являются неизменяемыми. Таким образом, вы можете поместить свой int в изменяемый объект.
def foo():
i = 0
obj = [i]
def incr(obj):
obj[0]+=1
incr(obj)
print(obj[0])
foo()
Вы можете сделать i
глобальным и использовать его.
i = 0
def foo():
def incr():
global i
i += 1
incr()
print(i)
foo()
но наиболее предпочтительным способом является передача i
в качестве параметра в incr
def foo():
i = 0
def incr(arg):
arg += 1
return arg
i = incr(i)
print(i)
foo()
вы также можете использовать лямбда-функцию:
def foo():
i=0
incr = lambda x: x+1
print incr(i)
foo()
Я думаю, что этот код более чист.
В этом случае не будут работать простые атрибуты функции.
>>> def foo():
... foo.i = 0
... def incr():
... foo.i +=1
... incr()
... print(foo.i)
...
>>>
>>>
>>> foo()
1
>>> foo()
1
>>> foo()
1
Вы назначаете свой foo.i 0
для каждого вызова foo
.
Лучше использовать hassattr
. Но код становится более сложным.
>>> def foo():
... if not hasattr(foo, 'i'):
... foo.i = 0
... def incr():
... foo.i += 1
... return foo.i
... i = incr()
... print(i)
...
>>>
>>>
>>> foo()
1
>>> foo()
2
>>> foo()
3
Также вы можете попробовать эту идею:
>>> def foo():
... def incr():
... incr.i += 1
... return incr.i
... incr.i = 0
... return incr
...
>>> a = foo()
>>> a()
1
>>> a()
2
>>> a()
3
>>> a()
4
Возможно, вам удобнее обернуть вас в декоратор.