Глобальные и локальные переменные Python и UnboundLocalError

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


person cdleary    schedule 01.01.2009    source источник
comment
Отвечает ли это на ваш вопрос? UnboundLocalError для локальной переменной при переназначении после первого использования   -  person wjandrea    schedule 11.08.2020


Ответы (4)


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

В этом столько смысла, сколько я могу понять.

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

person Triptych    schedule 01.01.2009
comment
Таким образом, вывод, который мы должны сделать из этого анализа, состоит в том, что глобальные идентификаторы нельзя использовать ранее в локальной области видимости, если они будут привязаны позже в локальной области видимости? - person cdleary; 01.01.2009
comment
@cdleary: если в одном и том же методе вы сначала используете имя из глобальной области, а затем для локальной, компилятор не может неявно определить, что первое вхождение относится к глобальному пространству имен, а второе к локальному. Следовательно, он рассматривается как UnboundLocalError. - person JV.; 01.01.2009

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

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

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

person Albert Visser    schedule 01.01.2009
comment
Вы определенно правы - то же самое происходит с любой переменной, переназначенной в области видимости. +1 - person rob; 01.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, по умолчанию используется глобальное имя 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 *) и осведомленность об импорте в импортированных модулях - имеет значение.

person JV.    schedule 01.01.2009
comment
+1 за отличную ссылку (и спасибо за ваш комментарий в ответе @Triptych - это помогло мне прояснить вопрос). - person cdleary; 01.01.2009

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

1: В Питоне

import foo

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

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

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

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

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

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

person Larry Hastings    schedule 27.01.2010