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

Изменение переменной внутри метода с помощью другого метода внутри него

Следующий код вызывает UnboundLocalError:

def foo():
    i = 0
    def incr():
        i += 1
    incr()
    print(i)

foo()

Есть ли способ сделать это?

4b9b3361

Ответ 1

Используйте оператор 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

Ответ 2

Вы можете использовать i в качестве аргумента следующим образом:

def foo():
    i = 0
    def incr(i):
        return i + 1
    i = incr(i)
    print(i)

foo()

Ответ 3

См. 9.2. Области и пространства имен Python:

если нет инструкции global - присваивания имен всегда входят в самую внутреннюю область.

также:

Оператор global может использоваться для указания того, что определенные переменные живут в глобальной области действия и должны быть отскочены туда; оператор nonlocal указывает, что конкретные переменные живут в закрывающей области и должны быть отскакированы там.

У вас много решений:

  • Передать i в качестве аргумента ✓ (я бы пошел с этим)
  • Использовать ключевое слово nonlocal

Обратите внимание, что в Python2.x вы можете получить доступ к нелокальным переменным, но вы не можете изменить их.

Ответ 4

Люди ответили на ваш вопрос, но никто, кажется, не спрашивает, почему именно это происходит.

Следующий код вызывает UnboundLocalError

Итак, почему? Возьмем цитату из FAQ:

Когда вы назначаете переменную в области видимости, эта переменная становится локальной для этой области и тени любой аналогичной именованной переменной во внешней области.

Внутри вашей вложенной функции выполняется назначение с помощью оператора +=. Это означает, что i += 1 будет приблизительно выполнять i = i + 1 (с точки зрения привязки). В результате i в выражении i + 1 будет поиск в локальной области (потому что он используется в операторе присваивания) для функции incr, где он не будет найден, в результате получится UnboundLocalError: reference before assignment.

Есть много способов решить эту проблему, и python 3 более изящна в подходе, который вы можете использовать, чем python 2.

Python 3 nonlocal:

Операторы 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), но он действительно выполняет задание.

Ответ 5

В Python int являются неизменяемыми. Таким образом, вы можете поместить свой int в изменяемый объект.

def foo():
    i = 0
    obj = [i]
    def incr(obj):
        obj[0]+=1
    incr(obj)
    print(obj[0])

foo()

Ответ 6

Вы можете сделать 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()

Ответ 7

вы также можете использовать лямбда-функцию:

def foo():
  i=0
  incr = lambda x: x+1
  print incr(i)

foo()

Я думаю, что этот код более чист.

Ответ 8

В этом случае не будут работать простые атрибуты функции.

>>> 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

Возможно, вам удобнее обернуть вас в декоратор.