2015-02-16 3 views
2

Я играл с примерами, чтобы ответить question posted here on SO и нашел трудным понять механику, через которую python's import * испортил сферу действия.Понимание импорта python * механика под круговой ссылкой

Сначала немного контекста: этот вопрос не касается практической проблемы; Я хорошо понимаю, что from foo import * нахмурился (справедливо так), и я понимаю, что это по причинам глубже, чем ясность в коде. Мой интерес здесь в понимания механики, которая вызывает плохое поведение с кругом import * s. Другими словами, я понимаю , что ожидается наблюдаемое поведение; Я не понимаю почему.

ситуация, я не в состоянии понять те проблемы, которые возникают при наличии, из импортированного модуля (b), ссылка на импортируемом модуль (a), используя *. Мне удалось наблюдать тонкие различия в поведении, когда модуль импорта использует * или нет, но общее (плохое) поведение остается прежним. Я не мог найти ясного объяснения ни в документации, ни в SO.

Изучение поведения через то, что доступно в области видимости, мне удалось создать небольшой пример, иллюстрирующий различия в его содержании на основе вышеупомянутого вопроса и несколько поисков, которые я сделал здесь, в SO и в других местах. Я стараюсь продемонстрировать как можно более лаконично. Все приведенные ниже коды и эксперименты выполнялись с помощью python 2.7.8.


Рабочие сценарии

Первый тривиальный модуль, содержащий тривиальный модуль, содержащий один класс, a.py:

class A: 
    pass 

Первый вариант клиентского кода, импортирование модуля а, b_v1.py:

from pprint import pprint 

def dump_frame(offset=0): 
    import sys 
    frame = sys._getframe(1+offset) 
    d = frame.f_globals 
    d.update(frame.f_locals) 
    return d 

print 'before import v1' 
pprint (dump_frame()) 

import a 

print 'after import v1' 
pprint (dump_frame()) 
print a.A() 

Второй вариант одного и того же кода, импорт * из модуля a, b_v2.py:

from pprint import pprint 

def dump_frame(offset=0): 
    import sys 
    frame = sys._getframe(1+offset) 
    d = frame.f_globals 
    d.update(frame.f_locals) 
    return d 

print 'before import v2' 
pprint (dump_frame()) 

from a import * 

print 'after import v2' 
pprint (dump_frame()) 
print A() 
  • Запуск как b_v1 и b_v2 производят тот же результат, перед импортом, и оба способны создать экземпляр, как и ожидалось. Однако после импорта, как и ожидалось, они различаются. Я подчеркнуть разницу:

b_v1.py, имеет в рамках

'a': <module 'a' from '.../stackoverflow/instance/a.py'> 

в то время как b_v2.py не делает, но имеет

'A': <class a.A at 0x...> 
  • до и после импорта, объем содержит __builtins__ установлен на <module '__builtin__' (built-in)>.

  • Оба варианта преуспевают в создании экземпляра A.


Не рабочие сценарии

Интригующее поведение при изменении a.py содержит циклическую ссылку на b (в обоих b_v1 и b_v2 вариантов).

Скорректированный код a.py:

from b_v1 import * 
class A: 
    pass 

(ради затрудненного, всего один случай a.py показан, очевидно, в случае b_v2.py импорта для этого модуля, не b_v1.py)

В мои наблюдения за содержанием сферы применения в сценарии с круглой ссылкой, я вижу:

  • В обоих вариантах до импорта в a, __builtins__ аналогичен приведенным выше случаям. После импорта, однако, она изменяется и содержит dict из

    'ArithmeticError':, 'AssertionError':, 'AttributeError':, ...

, который неоправданно долго здесь.

  • Изменен __builtins__ присутствует в наличии дважды. Это я понимаю как следствие импорта и, вероятно, не произойдет, если код будет внутри функции.
  • В варианте b_v2 модуль a присутствует в области; он присутствует в варианте b_v1.

  • В обоих вариантах, экземпляр A не работает. Учитывая, что в варианте b_v1 модуль присутствует в области (поэтому, я предполагаю, был успешно импортирован), я ожидал, что сможет создать экземпляр A. Это не тот случай. Однако есть различия: в случае b_v1.py он не работает с AttributeError: 'module' object has no attribute 'A' и, как и для b_v2.py, сбой - NameError. В этом более позднем случае это всегда одна и та же ошибка независимо от того, пытаюсь ли я создать экземпляр как A() (как в рабочем примере) a.A().


Суммируя мои вопросы:

  • через то, что механика циркуляр import * путает область?

  • Почему невозможно создать экземпляр A в случае b_v1, хотя модуль находится в области?

+0

Возможно, вы можете попросить примеры, которые используют 'из импорта A' вместо' from import * '. 'Import *' не проблема; проблема заключается в попытке получить доступ к чему-либо из 'a', когда' a' еще не загружена полностью. – BrenBarn

ответ

4

Модули Python выполнены сверху донизу. Операторы импорта исполняются так же, как и любые другие. Когда оператор импорта запускаются, он делает эти вещи (упрощенные для описательных целей, см language reference для получения полной информации):

  1. Проверьте, является ли модуль, указанные в sys.modules. Если это так, немедленно верните его
  2. Найдите модуль (обычно, но не всегда, используя файловую систему).
  3. Создайте пустую запись для модуля в sys.modules с пустым пространством имен.
  4. Выполнение модуля сверху вниз в пределах вновь созданного пространства имен.

Предположим, что у нас есть такие файлы, как это:

a.py:

from b import * 
foo = object() 

b.py:

from a import * 
print(repr(foo)) 

Предположим далее, что a.py импортируется первым. Давайте перейдем к этому по очереди:

  1. Импорт кого-то еще a. Ссылка на a хранится в sys.modules['a'], прежде чем мы даже начнем ее выполнять.
  2. a.py пробег from b import *. Это переводится как «import b, а затем захватывает все из пространства имен b в пространство имен a».
  3. Python помещает пустой объект модуля в sys.modules['b']
  4. b.py пробегов from a import *. Импорт Python a.
  5. Импорт a сразу же возвращается с sys.modules['a'].
  6. С a.py еще не исполнено foo = object(), a.foo еще не существует, поэтому его нельзя сбрасывать в пространство имен b.
  7. b.py аварии на NameError.
Смежные вопросы