Следующий код работает как положено в Python 2.5 и 3.0:
a, b, c = (1, 2, 3)
print(a, b, c)
def test():
print(a)
print(b)
print(c) # (A)
#c+=1 # (B)
test()
Однако, когда я раскомментирую строку (B) , я получаю UnboundLocalError: 'c' not assigned
строку (A) . Значения a
и b
напечатаны правильно. Это полностью сбило меня с толку по двум причинам:
Почему в строке (A) возникает ошибка времени выполнения из-за более позднего оператора в строке (B) ?
Почему переменные
a
иb
печатаются как положено, аc
выдает ошибку?
Единственное объяснение, которое я могу придумать, заключается в том , что присваивание создает локальную переменную , которая имеет прецедент над «глобальной» переменной еще до создания локальной переменной. Конечно, для переменной не имеет смысла «красть» область видимости до того, как она существует.c
c+=1
c
Может ли кто-нибудь объяснить это поведение?
Ответы:
Python обрабатывает переменные в функциях по-разному, в зависимости от того, назначаете ли вы значения изнутри или снаружи функции. Если переменная назначена внутри функции, она по умолчанию обрабатывается как локальная переменная. Поэтому, когда вы раскомментируете строку, вы пытаетесь сослаться на локальную переменную
c
до того, как ей будет присвоено какое-либо значение.Если вы хотите, чтобы переменная
c
ссылалась на глобальное значение,c = 3
назначенное перед функцией, поместитев первой строке функции.
Что касается питона 3, то сейчас
что вы можете использовать для ссылки на ближайшую область действия функции, которая имеет
c
переменную.источник
global
илиnonlocal
для принудительного назначения глобального или нелокального назначения)Python немного странный в том, что он хранит все в словаре для различных областей. Оригинал a, b, c находится в самой верхней области видимости и, следовательно, в этом самом верхнем словаре. Функция имеет свой словарь. Когда вы достигнете
print(a)
иprint(b)
заявления, нет ничего под этим именем в словаре, поэтому Python просматривает список и находит их в глобальном словаре.Теперь мы получаем
c+=1
, что, конечно, эквивалентноc=c+1
. Когда Python просматривает эту строку, он говорит: «Ага, есть переменная с именем c, я помещу ее в свой локальный словарь области видимости». Затем, когда он ищет значение c для c в правой части присваивания, он находит свою локальную переменную с именем c , которая еще не имеет значения, и поэтому выдает ошибку.global c
Упомянутое выше утверждение просто говорит парсеру, что он используетc
глобальную область видимости и поэтому не нуждается в новой.Причина, по которой он говорит, что есть проблема в строке, которую он делает, заключается в том, что он эффективно ищет имена, прежде чем попытается сгенерировать код, и поэтому в некотором смысле не думает, что он действительно делает эту строку еще. Я бы сказал, что это ошибка юзабилити, но, как правило, полезно просто учиться не воспринимать сообщения компилятора слишком серьезно.
Если это утешит, я провел, вероятно, целый день, копая и экспериментируя с этой же проблемой, прежде чем нашел что-то, что Гвидо написал о словарях, которые объяснили все.
Обновление, смотрите комментарии:
Он не сканирует код дважды, но он сканирует код в два этапа: лексирование и анализ.
Рассмотрим, как работает синтаксический анализ этой строки кода. Лексер читает исходный текст и разбивает его на лексемы, «самые маленькие компоненты» грамматики. Поэтому, когда он попадает в линию
это разбивает его на что-то вроде
Парсер в конечном итоге хочет превратить это в дерево разбора и выполнить его, но, поскольку это присваивание, до этого он ищет имя c в локальном словаре, не видит его и вставляет его в словарь, отмечая это как неинициализированный. На полностью скомпилированном языке он просто заходил бы в таблицу символов и ждал разбора, но, поскольку у него не было бы роскоши второго прохода, лексер проделал небольшую дополнительную работу, чтобы облегчить жизнь в дальнейшем. Только тогда он видит ОПЕРАТОРА, видит, что в правилах написано «если у вас есть оператор + = левая сторона должна быть инициализирована», и говорит «упс!»
Дело в том, что он еще не начал анализ строки . Все это происходит как бы перед подготовкой к фактическому анализу, поэтому счетчик строк не перешел на следующую строку. Таким образом, когда он сигнализирует об ошибке, он все еще думает, что на предыдущей строке.
Как я уже сказал, вы можете утверждать, что это ошибка юзабилити, но на самом деле это довольно распространенная вещь. Некоторые компиляторы более честны по этому поводу и говорят «ошибка в строке XXX или около нее», но это не так.
источник
dict
, а внутренне является просто массивом (locals()
заполняет adict
для возврата, но изменения в нем не создают новогоlocals
). Фаза синтаксического анализа заключается в нахождении каждого назначения для локального и преобразовании из имени в позицию в этом массиве, и использовании этой позиции всякий раз, когда ссылается на имя. При входе в функцию локальныеUnboundLocalError
переменные без аргументов инициализируются заполнителем, и s происходят, когда переменная читается, а связанный с ней индекс по-прежнему имеет значение заполнителя.Взглянув на разборку, можно уточнить, что происходит:
Как вы можете видеть, байткод для доступа к это
LOAD_FAST
, и для ЬLOAD_GLOBAL
. Это связано с тем, что компилятор определил, что a назначен внутри функции, и классифицировал его как локальную переменную. Механизм доступа для локальных пользователей принципиально отличается для глобальных переменных - им статически назначается смещение в таблице переменных фрейма, что означает, что поиск является быстрым индексом, а не более дорогим поиском разборчивости, как для глобальных. Из-за этого Python читаетprint a
строку как «получить значение локальной переменной« a », хранящейся в слоте 0, и распечатать его», и, когда он обнаруживает, что эта переменная все еще не инициализирована, вызывает исключение.источник
У Python довольно интересное поведение, когда вы используете традиционную семантику глобальных переменных. Я не помню деталей, но вы можете просто прочитать значение переменной, объявленной в «глобальной» области видимости, но если вы хотите изменить ее, вам нужно использовать
global
ключевое слово. Попробуйте перейтиtest()
на это:Кроме того, причина того, что вы получаете эту ошибку, заключается в том, что вы также можете объявить новую переменную внутри этой функции с тем же именем, что и у «глобальной», и она будет полностью отдельной. Интерпретатор считает, что вы пытаетесь создать новую переменную в этой области
c
и вызвать ее изменение за одну операцию, что не разрешено в Python, потому что эта новаяc
не была инициализирована.источник
Лучший пример, который проясняет это:
при вызове
foo()
это также повышается,UnboundLocalError
хотя мы никогда не достигнем строкиbar=0
, поэтому логически локальная переменная никогда не должна создаваться.Тайна кроется в « Python - это интерпретируемый язык », и объявление функции
foo
интерпретируется как одно утверждение (т. Е. Составное утверждение), оно просто тупо интерпретирует его и создает локальные и глобальные области видимости. Такbar
признается в локальной области до исполнения.Для большего количества примеров как это Прочитайте этот пост: http://blog.amir.rachum.com/blog/2013/07/09/python-common-newbie-mistakes-part-2/
Этот пост содержит полное описание и анализ Python Scoping переменных:
источник
Вот две ссылки, которые могут помочь
1: docs.python.org/3.1/faq/programming.html?highlight=nonlocal#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value
2: docs.python.org/3.1/faq/programming.html?highlight=nonlocal#how-do-i-write-a-function-with-output-parameters-call-by-reference
Первая ссылка описывает ошибку UnboundLocalError. Вторая ссылка может помочь с переписыванием вашей тестовой функции. Исходя из второй ссылки, исходную проблему можно переписать так:
источник
Это не прямой ответ на ваш вопрос, но он тесно связан, так как это еще один недостаток, вызванный взаимосвязью между расширенным назначением и областями функций.
В большинстве случаев вы склонны думать о расширенном назначении (
a += b
) как о точном эквиваленте простого назначения (a = a + b
). Впрочем, с этим можно столкнуться и в одном случае. Позволь мне объяснить:То, как работает простое присваивание Python, означает, что если
a
он передается в функцию (напримерfunc(a)
, обратите внимание, что Python всегда передается по ссылке), онa = a + b
не будет изменять переданный объектa
. Вместо этого он просто изменит локальный указатель наa
.Но если вы используете
a += b
, то это иногда реализуется как:или иногда (если метод существует) как:
В первом случае (до тех пор, пока
a
он не объявлен глобальным), за пределами локальной области видимости нет никаких побочных эффектов, поскольку присваиваниеa
является просто обновлением указателя.Во втором случае
a
фактически изменится сам, поэтому все ссылки наa
будут указывать на измененную версию. Это демонстрируется следующим кодом:Так что хитрость заключается в том, чтобы избежать расширенного присваивания аргументов функции (я стараюсь использовать его только для локальных переменных / переменных цикла). Используйте простое задание, и вы будете защищены от неоднозначного поведения.
источник
Интерпретатор Python прочитает функцию как единое целое. Я думаю об этом как о чтении его в два прохода, один раз, чтобы собрать его замыкание (локальные переменные), затем снова превратить его в байт-код.
Как я уверен, вы уже знали, что любое имя слева от «=» неявно является локальной переменной. Не раз меня ловили, меняя доступ к переменной на + =, и вдруг это была другая переменная.
Я также хотел отметить, что это не имеет ничего общего с глобальным охватом. Вы получаете то же самое поведение с вложенными функциями.
источник
c+=1
назначаетc
, python предполагает, что назначенные переменные являются локальными, но в этом случае он не был объявлен локально.Или используйте ключевые слова
global
илиnonlocal
.nonlocal
работает только в Python 3, поэтому, если вы используете Python 2 и не хотите делать переменную глобальной, вы можете использовать изменяемый объект:источник
Лучший способ получить доступ к переменной класса - это прямой доступ по имени класса.
источник
В Python у нас есть аналогичное объявление для всех типов переменных: локальные, переменные класса и глобальные переменные. когда вы ссылаетесь на глобальную переменную из метода, python думает, что вы на самом деле ссылаетесь на переменную из самого метода, который еще не определен, и, следовательно, выдает ошибку. Чтобы ссылаться на глобальную переменную, мы должны использовать globals () ['variableName'].
в вашем случае используйте globals () ['a], globals () [' b '] и globals () [' c '] вместо a, b и c соответственно.
источник
Та же проблема беспокоит меня. Использование
nonlocal
иglobal
может решить проблему.Тем не менее, внимание, необходимое для использования
nonlocal
, это работает для вложенных функций. Однако на уровне модуля это не работает. Смотрите примеры здесь.источник