Python немного странный в том, что он хранит все в словаре для различных областей. Оригинал a, b, c находится в самой верхней области видимости и, следовательно, в этом самом верхнем словаре. Функция имеет свой словарь. Когда вы достигнете print(a)
и print(b)
заявления, нет ничего под этим именем в словаре, поэтому Python просматривает список и находит их в глобальном словаре.
Теперь мы получаем c+=1
, что, конечно, эквивалентно c=c+1
. Когда Python просматривает эту строку, он говорит: «Ага, есть переменная с именем c, я помещу ее в свой локальный словарь области видимости». Затем, когда он ищет значение c для c в правой части присваивания, он находит свою локальную переменную с именем c , которая еще не имеет значения, и поэтому выдает ошибку.
global c
Упомянутое выше утверждение просто говорит парсеру, что он использует c
глобальную область видимости и поэтому не нуждается в новой.
Причина, по которой он говорит, что есть проблема в строке, которую он делает, заключается в том, что он эффективно ищет имена, прежде чем попытается сгенерировать код, и поэтому в некотором смысле не думает, что он действительно делает эту строку еще. Я бы сказал, что это ошибка юзабилити, но, как правило, полезно просто учиться не воспринимать сообщения компилятора слишком серьезно.
Если это утешит, я провел, вероятно, целый день, копая и экспериментируя с этой же проблемой, прежде чем нашел что-то, что Гвидо написал о словарях, которые объяснили все.
Обновление, смотрите комментарии:
Он не сканирует код дважды, но он сканирует код в два этапа: лексирование и анализ.
Рассмотрим, как работает синтаксический анализ этой строки кода. Лексер читает исходный текст и разбивает его на лексемы, «самые маленькие компоненты» грамматики. Поэтому, когда он попадает в линию
c+=1
это разбивает его на что-то вроде
SYMBOL(c) OPERATOR(+=) DIGIT(1)
Парсер в конечном итоге хочет превратить это в дерево разбора и выполнить его, но, поскольку это присваивание, до этого он ищет имя c в локальном словаре, не видит его и вставляет его в словарь, отмечая это как неинициализированный. На полностью скомпилированном языке он просто заходил бы в таблицу символов и ждал разбора, но, поскольку у него не было бы роскоши второго прохода, лексер проделал небольшую дополнительную работу, чтобы облегчить жизнь в дальнейшем. Только тогда он видит ОПЕРАТОРА, видит, что в правилах написано «если у вас есть оператор + = левая сторона должна быть инициализирована», и говорит «упс!»
Дело в том, что он еще не начал анализ строки . Все это происходит как бы перед подготовкой к фактическому анализу, поэтому счетчик строк не перешел на следующую строку. Таким образом, когда он сигнализирует об ошибке, он все еще думает, что на предыдущей строке.
Как я уже сказал, вы можете утверждать, что это ошибка юзабилити, но на самом деле это довольно распространенная вещь. Некоторые компиляторы более честны по этому поводу и говорят «ошибка в строке XXX или около нее», но это не так.