Почему UnboundLocalError возникает, когда внутренняя функция присваивает значение локальным переменным

#python

#python

Вопрос:

Я пишу внутреннюю функцию во внешней функции, затем произошло что-то связанное, то есть при присвоении значения локальной переменной во внутренней функции произошла ошибка UnboundLocalError. И наоборот, если я просто распечатываю локальную переменную во внутренней функции, она работает довольно хорошо. Позвольте мне показать вам упрощенный код. Я знаю, что это имеет отношение к правилам LEGB в Python, но я все еще не мог понять, почему это произошло. Большое спасибо, если кто-нибудь может дать мне некоторые идеи. Позвольте мне показать вам упрощенный код.

 
def outer1():
    number = 10
    def inner():
        print(number)
    inner()

def outer2():
    number = 20
    def inner():
        if number >= 20:
            number  = 1
    inner()
 

outer1() функция работает хорошо, но outer2() функция выдает a UnboundLocalError: local variable 'number' referenced before assignment .

Я знаю, что использование nonlocal ключевого слова может решить проблему. но меня все еще что-то смущает. Почему if оператор в outer2() функции не искал переменную number во внешней функции, как это outer1() сделала функция. Кто-нибудь может дать мне некоторые объяснения по этому поводу?

Комментарии:

1. number Во втором случае вы присвоили значение inner() within . Это делает его локальной переменной inner() . Но вы не присвоили ему никакого значения внутри inner() , прежде чем пытаться его использовать. В этом случае он не наследует значение number from outer() .

Ответ №1:

nonlocal В таких случаях необходимо использовать оператор:

 def outer2():
    number = 20
    def inner():
        nonlocal number
        if number >= 20:
            number  = 1
    inner()
 

Чтобы понять, почему, давайте посмотрим на байт-код:

 import dis


def outer2():
    number = 20
    def inner():
        if number >= 20:
            number
    inner()



def outer3():
    number = 20
    def inner():
        if number >= 20:
            number = number   1
    inner()


def outer4():
    number = 20
    def inner():
        nonlocal number
        if number >= 20:
            number = number   1
    inner()

>>> dis.dis(outer2)
  2           0 LOAD_CONST               1 (20)
              3 STORE_DEREF              0 (number)

  3           6 LOAD_CLOSURE             0 (number)
              9 BUILD_TUPLE              1
             12 LOAD_CONST               2 (<code object inner at 0x7ff003b56b70, file "<stdin>", line 3>)
             15 LOAD_CONST               3 ('outer2.<locals>.inner')
             18 MAKE_CLOSURE             0
             21 STORE_FAST               0 (inner)

  6          24 LOAD_FAST                0 (inner)
             27 CALL_FUNCTION            0 (0 positional, 0 keyword pair)
             30 POP_TOP
             31 LOAD_CONST               0 (None)
             34 RETURN_VALUE


>>> dis.dis(outer3)
  2           0 LOAD_CONST               1 (20)
              3 STORE_FAST               0 (number)

  3           6 LOAD_CONST               2 (<code object inner at 0x7ff003b56ae0, file "<stdin>", line 3>)
              9 LOAD_CONST               3 ('outer3.<locals>.inner')
             12 MAKE_FUNCTION            0
             15 STORE_FAST               1 (inner)

  6          18 LOAD_FAST                1 (inner)
             21 CALL_FUNCTION            0 (0 positional, 0 keyword pair)
             24 POP_TOP
             25 LOAD_CONST               0 (None)
             28 RETURN_VALUE


>>> dis.dis(outer4)
  2           0 LOAD_CONST               1 (20)
              3 STORE_DEREF              0 (number)

  3           6 LOAD_CLOSURE             0 (number)
              9 BUILD_TUPLE              1
             12 LOAD_CONST               2 (<code object inner at 0x7ff003af7e40, file "<stdin>", line 3>)
             15 LOAD_CONST               3 ('outer4.<locals>.inner')
             18 MAKE_CLOSURE             0
             21 STORE_FAST               0 (inner)

  7          24 LOAD_FAST                0 (inner)
             27 CALL_FUNCTION            0 (0 positional, 0 keyword pair)
             30 POP_TOP
             31 LOAD_CONST               0 (None)
             34 RETURN_VALUE
 

Из этих примеров видно, что попытка присвоить переменной (которая не была объявлена как нелокальная)
уступает MAKE_FUNCTION коду операции. Но только замыкания позволяют получить доступ к переменным из текущей области.
Вы также можете прочитать больше о замыканиях здесь .

Примечание: Это было протестировано в Python 3.5; Также читайте об изменениях MAKE_FUNCTION и MAKE_CLOSURE кодах операций в python 3.6

Комментарии:

1. Я знаю, что использование нелокального ключевого слова может решить проблему. но меня все еще что-то смущает. Почему оператор if в функции outer2() не искал переменную number во внешней функции, как это делала функция outer1() . Не могли бы вы дать мне несколько объяснений по этому поводу?

2. Большое спасибо. Вы заставляете меня полностью понять это.

Ответ №2:

 def outer2():
  number = 20
  def inner():
     nonlocal number # this is the additional change required
     if number >= 20:
         number  = 1
  inner()
 

В функциях python можно получить доступ к «глобальным» переменным внутри функций и даже их методов, но не присваивать им значения, потому что тогда функция ищет свою локальную копию переменной.
Ошибка заключается в том, что функция ищет локальную переменную ‘number’ в записи таблицы символов и не находит ее.

Обычно это можно решить с помощью ключевого слова global, чтобы указать, что редактируемая переменная имеет глобальную область видимости, но поскольку функция определена в другой функции, вам нужно будет использовать ключевое слово ‘nonloc’, которое является другим способом сказать, что переменная, которую вы хотите изменить, не является локальной дляэтой функции, но функции, в которой она определена, которая в данном случае является outer2 .