Глобальные файлы Python, локальные файлы и UnboundLocalError
-
03-07-2019 - |
Вопрос
Я наткнулся на этот случай UnboundLocalError
недавно, что кажется странным:
import pprint
def main():
if 'pprint' in globals(): print 'pprint is in globals()'
pprint.pprint('Spam')
from pprint import pprint
pprint('Eggs')
if __name__ == '__main__': main()
Который производит:
pprint is in globals()
Traceback (most recent call last):
File "weird.py", line 9, in <module>
if __name__ == '__main__': main()
File "weird.py", line 5, in main
pprint.pprint('Spam')
UnboundLocalError: local variable 'pprint' referenced before assignment
pprint
четко связан с globals
, и будет связан в locals
в следующем заявлении.Может ли кто-нибудь предложить объяснение, почему это не приносит счастья? pprint
к привязке в globals
здесь?
Редактировать: Благодаря хорошим ответам я могу прояснить свой вопрос с помощью соответствующей терминологии:
Во время компиляции идентификатор pprint
помечен как локальный для фрейма.Имеет ли модель выполнения какие-либо различия где внутри фрейма привязан локальный идентификатор?Может ли он сказать: "обратитесь к глобальной привязке вплоть до этой инструкции байт-кода, после чего она будет восстановлена до локальной привязки", или модель выполнения этого не учитывает?
Решение
Похоже, Python видит from pprint import pprint
линии и метки pprint
как имя, локальное для main()
до того, как выполнение любого кода.Поскольку Python считает, что pprint должна быть локальной переменной, ссылающейся на нее с помощью pprint.pprint()
прежде чем "назначить" его с помощью from..import
оператор, он выдает эту ошибку.
Это все, что я могу из этого извлечь.
Мораль, конечно, состоит в том, чтобы всегда ставить эти import
утверждения в верхней части области видимости.
Другие советы
Где же сюрприз? Любой переменная, глобальная для области, которую вы переназначаете в этой области, помечается компилятором как локальная для этой области.
Если бы импорт обрабатывался по-другому, это было бы удивительно, имхо.
Однако это может привести к тому, что модули не будут именоваться в честь используемых в них символов, или наоборот.
Что ж, это было достаточно интересно для меня, чтобы немного поэкспериментировать, и я дочитал до конца http://docs.python.org/reference/executionmodel.html
Затем немного повозился с вашим кодом здесь и там, вот что я смог найти:
код:
import pprint
def two():
from pprint import pprint
print globals()['pprint']
pprint('Eggs')
print globals()['pprint']
def main():
if 'pprint' in globals():
print 'pprint is in globals()'
global pprint
print globals()['pprint']
pprint.pprint('Spam')
from pprint import pprint
print globals()['pprint']
pprint('Eggs')
def three():
print globals()['pprint']
pprint.pprint('Spam')
if __name__ == '__main__':
two()
print('\n')
three()
print('\n')
main()
выходной сигнал:
<module 'pprint' from '/usr/lib/python2.5/pprint.pyc'>
'Eggs'
<module 'pprint' from '/usr/lib/python2.5/pprint.pyc'>
<module 'pprint' from '/usr/lib/python2.5/pprint.pyc'>
'Spam'
pprint is in globals()
<module 'pprint' from '/usr/lib/python2.5/pprint.pyc'>
'Spam'
<function pprint at 0xb7d596f4>
'Eggs'
В методе two()
from pprint import pprint
но не переопределяет имя pprint
в globals
, с тех пор как global
ключевое слово - это нет используется в рамках two()
.
В методе three()
поскольку нет объявления о pprint
имя в локальной области по умолчанию используется глобальное имя pprint
который является модулем
Принимая во внимание , что в main()
, сначала ключевое слово global
используется таким образом, все ссылки на pprint
в рамках метода main()
будем ссылаться на global
Имя pprint
.Который, как мы можем видеть, сначала является модулем и переопределяется в global
namespace
с помощью метода, по мере того как мы делаем from pprint import pprint
Хотя это, возможно, и не отвечает на вопрос как таковой, но, тем не менее, я думаю, что это какой-то интересный факт.
=====================
Редактировать Еще одна интересная вещь.
Если у вас есть модуль, скажите:
mod1
from datetime import datetime
def foo():
print "bar"
и другой метод, скажем:
mod2
import datetime
from mod1 import *
if __name__ == '__main__':
print datetime.datetime.now()
что на первый взгляд кажется правильным, поскольку вы импортировали модуль datetime
в mod2
.
теперь, если вы попытаетесь запустить mod2 как скрипт, он выдаст ошибку:
Traceback (most recent call last):
File "mod2.py", line 5, in <module>
print datetime.datetime.now()
AttributeError: type object 'datetime.datetime' has no attribute 'datetime'
потому что второй импорт from mod2 import *
переопределил имя datetime
в пространстве имен, следовательно, первый import datetime
больше не действителен.
Моральный:Таким образом, порядок импорта, характер импорта (из x import *) и осведомленность об импорте в импортируемых модулях - имеет значение.
На этот вопрос был дан ответ несколько недель назад, но я думаю, что могу немного прояснить ответы.Сначала несколько фактов.
1:В Python,
import foo
это почти точно то же самое, что и
foo = __import__("foo", globals(), locals(), [], -1)
2:При выполнении кода в функции, если Python обнаруживает переменную, которая еще не была определена в функции, она просматривается в глобальной области видимости.
3:В Python есть оптимизация, которую он использует для функций, называемых "локальными".Когда Python маркирует функцию, он отслеживает все переменные, которым вы присваиваете значение.Он присваивает каждой из этих переменных число из локального монотонно возрастающего целого числа.Когда Python запускает функцию, она создает массив с таким количеством слотов, сколько существует локальных переменных, и присваивает каждому слоту специальное значение, которое означает "еще не присвоено", и именно там хранятся значения для этих переменных.Если вы ссылаетесь на local, которому еще не было присвоено значение, Python видит это специальное значение и выдает исключение UnboundLocalValue.
Теперь сцена готова.Ваше "from pprint импортировать pprint" на самом деле является формой присвоения.Итак, Python создает локальную переменную с именем "pprint", которая перекрывает глобальную переменную.Затем, когда вы ссылаетесь на "pprint.pprint" в функции, вы нажимаете специальное значение, и Python выдает исключение.Если бы у вас не было этого оператора import в функции, Python использовал бы обычное разрешение look-in-locals-first-then-look-in-globals и нашел бы модуль pprint в globals.
Чтобы устранить эту неоднозначность, вы можете использовать ключевое слово "global".Конечно, к настоящему времени вы уже справились со своей проблемой, и я не знаю, действительно ли вам нужен был "глобальный" подход или требовался какой-то другой.