Назначение переменной из родительской функции: "Локальная переменная, указанная перед назначением"

Для следующего кода Python 2.7:

#!/usr/bin/python

def funcA():
   print "funcA"
   c = 0 
   def funcB():
      c += 3
      print "funcB", c

   def funcC():
      print "funcC", c

   print "c", c
   funcB()
   c += 2
   funcC()
   c += 2
   funcB()
   c += 2
   funcC()
   print "end"

funcA()

Я получаю следующую ошибку:

File "./a.py", line 9, in funcB
    c += 3
UnboundLocalError: local variable 'c' referenced before assignment

Но когда я прокомментирую строку c += 3 в funcB, я получаю следующий вывод:

funcA
c 0
funcB 0
funcC 2
funcB 4
funcC 6
end

Доступен ли c в обоих случаях += в funcB и = в funcC? Почему он не бросает ошибку для одного, но не для другого?

У меня нет выбора сделать глобальную переменную c, а затем объявить global c в funcB. Во всяком случае, дело не в том, чтобы получить c с приращением в funcB, а за то, почему он бросает ошибку для funcB, а не для funcC, в то время как оба обращаются к переменной, которая является локальной или глобальной.

Ответ 1

То, что вы видите здесь, - это разница между доступом и назначением переменных. В Python 2.x вы можете назначать только переменные во внутренней области или глобальной области (последняя выполняется с помощью глобального оператора). Вы можете обращаться к переменным в любой охватывающей области, но вы не можете получить доступ к переменной в охватывающей области и затем назначить ее в самой внутренней или глобальной области.

Это означает, что если есть какое-либо присвоение имени внутри функции, это имя уже должно быть определено в самой внутренней области до того, как будет доступно обращение к имени (если только не использовался глобальный оператор). В вашем коде строка c += 3 по существу эквивалентна следующему:

tmp = c
c = tmp + 3

Поскольку в функции есть назначение c, любое другое вхождение c в этой функции будет отображаться только в локальной области для funcB. Вот почему вы видите ошибку, вы пытаетесь получить доступ к c, чтобы получить его текущее значение для +=, но в локальной области c еще не определено.

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

Ваш код будет выглядеть примерно так, с аналогичной строкой вверху funcC:

   def funcB():
      nonlocal c
      c += 3
      ...

В Python 2.x это не вариант, и единственный способ изменить значение нелокальной переменной - это изменить ее.

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

def funcA():
   print "funcA"
   c = [0]
   def funcB():
      c[0] += 3
      print "funcB", c[0]

   def funcC():
      c[0] = 5
      print "funcC", c[0]

   print "c", c[0]
   funcB()
   funcC()
   funcB()
   funcC()
   print "end"

funcA()

... и вывод:

funcA
c 0
funcB 3
funcC 5
funcB 8
funcC 5
end

Ответ 2

Не доступен ли "c" в обоих случаях "+ =" в funcB и "=" в funcC?

Нет, funcC создает новую переменную, также называемую c. = в этом отношении отличается от +=.

Чтобы получить поведение, которое вы (возможно) хотите, оберните переменную вверх в одноэлементный список:

def outer():
    c = [0]
    def inner():
        c[0] = 3
    inner()
    print c[0]

напечатает 3.

Изменить. Вы хотите передать c в качестве аргумента. Python 2 не имеет другого способа, AFAIK, чтобы получить желаемое поведение. Python 3 вводит ключевое слово nonlocal для этих случаев.

Ответ 3

1) Не доступен ли c в обоих случаях += в funcB и = в funcC?

Нет, потому что c += 3 совпадает с:

c = c + 3
    ^
    |
and funcB does not know what this c is

2) У меня нет выбора сделать c глобальную переменную, а затем объявить global c в funcB.

Пожалуйста, не делайте этого, просто измените:

def funcB():

с:

def funcB(c):

и вызовите funcB(c) позже в вашем коде.

Примечание.. Вы также должны определить funcB и funcC вне funcA

Ответ 4

Попробуйте следующее:

def funcA():
   print "funcA"
   c = 0
   def funcB(c):
      c += 3
      print "funcB", c

   def funcC(c):
      c = 5
      print "funcC", c

   print "c", c
   funcB(c)
   funcC(c)
   funcB(c)
   funcC(c)
   print "end"

funcA()

И если вы хотите запомнить значение c, то:

def funcA():
   print "funcA"
   c = 0
   def funcB(c):
      c += 3
      print "funcB", c
      return c

   def funcC(c):
      c = 5
      print "funcC", c
      return c

   print "c", c
   c = funcB(c)
   c = funcC(c)
   c = funcB(c)
   c = funcC(c)
   print "end"

funcA()

который будет производить:

funcA
c 0
funcB 3
funcC 5
funcB 8
funcC 5
end

C:\Python26\

Ответ 5

Другое грязное обходное решение, которое, однако, не требует, чтобы вы сделали c глобальным. Все одинаково, но:

def funcB():
    globals()['c'] += 3
    print "funcB", c