Python глобальные, локальные и UnboundLocalError - PullRequest
8 голосов
/ 01 января 2009

Я недавно столкнулся с этим случаем 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 помечается как локальный для фрейма. Разве в модели исполнения нет различия , где в кадре связан локальный идентификатор? Может ли он сказать: «ссылаться на глобальную привязку вплоть до этой инструкции байт-кода, после чего она была восстановлена ​​до локальной привязки», или модель выполнения не учитывает это?

Ответы [ 4 ]

6 голосов
/ 01 января 2009

Где сюрприз? Любая переменная , глобальная для области, которую вы переназначаете в этой области, помечается компилятором как локальная для этой области.

Если импорт будет обрабатываться по-другому, , что было бы удивительно imho.

Это может привести к тому, что модули не будут именоваться после используемых в них символов, или наоборот.

5 голосов
/ 01 января 2009

Ну, это было достаточно интересно для меня, чтобы немного поэкспериментировать, и я прочитал 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 name, по умолчанию используется глобальное имя pprint, являющееся модулем

Принимая во внимание, что в main() сначала используется ключевое слово global , поэтому все ссылки на pprint в области действия метода main() будут ссылаться на global name 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 *) и осведомленность об импорте в импортируемых модулях - имеет значение .

4 голосов
/ 27 января 2010

На этот вопрос ответили несколько недель назад, но я думаю, что могу немного уточнить ответы. Сначала несколько фактов.

1: в Python,

import foo

почти так же, как

foo = __import__("foo", globals(), locals(), [], -1)

2: При выполнении кода в функции, если Python обнаруживает переменную, которая еще не была определена в функции, он выглядит в глобальной области видимости.

3: Python имеет оптимизацию, которую он использует для функций, называемых «локальными». Когда Python токенизирует функцию, он отслеживает все переменные, которые вы назначаете. Каждой из этих переменных присваивается число из локального монотонно возрастающего целого числа. Когда Python запускает функцию, он создает массив с таким количеством слотов, сколько есть локальных переменных, и назначает каждому слоту специальное значение, которое означает, что «еще не было назначено», и именно там хранятся значения для этих переменных. Если вы ссылаетесь на локальный объект, который еще не был назначен, Python видит это специальное значение и выдает исключение UnboundLocalValue.

Сцена теперь установлена. Ваше «из pprint import pprint» действительно является формой назначения. Таким образом, Python создает локальную переменную с именем «pprint», которая закрывает глобальную переменную. Затем, когда вы ссылаетесь на «pprint.pprint» в функции, вы нажимаете специальное значение, и Python выдает исключение. Если у вас не было этого оператора import в функции, Python использовал бы обычное разрешение look-in-locals-first-then-look-in-globals и нашел бы модуль pprint в глобальных переменных.

Чтобы устранить неоднозначность, вы можете использовать ключевое слово "global". Конечно, к настоящему моменту вы уже преодолели свою проблему, и я не знаю, действительно ли вам нужен был «глобальный» или нужен какой-то другой подход.

4 голосов
/ 01 января 2009

Похоже, что Python видит строку from pprint import pprint и помечает pprint как имя локальное для main() до выполнения любого кода. Поскольку Python считает, что pprint должна быть локальной переменной, ссылка на нее с pprint.pprint() перед «назначением» с помощью оператора from..import выдает эту ошибку.

В этом столько смысла, сколько я могу себе представить.

Мораль, конечно, всегда ставить эти import утверждения наверху сферы действия.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...