2015-12-23 7 views
12

Импорт в __init__.py, похоже, ведет себя по-разному, когда файл запущен, когда он импортируется.Импорт ведет себя по-другому, когда в __init__.py импортируется

Если у нас есть следующие файлы:

run.py:

import test 

test/b.py:

class B(object): 
    pass 

test/__init__.py:

from b import B 
print B 
print b 

Если мы запустим __init__.py мы получаем ошибку, как я ожидал:

% python test/__init__.py 

<class 'b.B'> 
Traceback (most recent call last): 
    File "test/__init__.py", line 6, in <module> 
    print b 
NameError: name 'b' is not defined 

Но если мы run.py то мы делаем не так:

% python run.py 
<class 'test.b.B'> 
<module 'test.b' from '~/temp/test/b.py'> 

Я бы ожидать, что поведение будет то же самое. Почему это работает?

Это работает только в том случае, если мы делаем это в __init__.py. Если мы:

mv __init__.py a.py 
touch __init__.py 

и сделать run.py:

import test.a 

Тогда мы получаем ошибку.

+1

извинения за неправильный ответ ... просто удалили его, чтобы не создавать путаницу. @ Poke спасибо за указание на это ... не заметили изначально то, что вы упомянули. –

ответ

5

Ситуация такова: у вас есть сценарий (run.py), пакет test и его подмодуль test.b.

Всякий раз, когда вы импортируете подмодуль в Python, имя этого подмодуля автоматически сохраняется в родительском пакете. Так что, когда вы делаете import collections.abc (или from collections.abc import Iterable, или аналогичный), пакет collections автоматически получает атрибут abc.

Это то, что происходит здесь. Когда вы делаете:

from b import B 

имя b автоматически загружается в test, потому что b является подмодуль test пакета.

Даже если вы не делаете import b явно, всякий раз, когда вы импортируете этот модуль, имя помещается в test. Потому что b является подмодулем test и относится к test.


Боковой узел: ваш код не будет работать с Python 3, потому что relative imports have been removed.Для того, чтобы сделать код работы с Python 3, вы должны написать:

from test.b import B 

Этот синтаксис совершенно идентичен from b import B, но гораздо более явным, и поможет вам понять, что происходит.


Ссылка:Python reference documentation также объясняет такое поведение, и включает в себя полезный пример, очень похожий на эту ситуацию (разница только что используется абсолютный импорт вместо относительного импорта).

Когда подмодуль загружаются с помощью какого-либо механизма (например, importlib API, то import или import-from отчетности, или встроенного в __import__()) связывание помещаются в пространстве имен родительского модуля к объекту подмодуля. Например, если пакет spam имеет подмодуль foo, после импорта spam.foo, spam будет иметь атрибут foo, который привязан к подмодулю.

Допустим, у вас есть следующая структура каталогов:

spam/ 
    __init__.py 
    foo.py 
    bar.py 

и spam/__init__.py имеют следующие строки в нем:

from .foo import Foo 
from .bar import Bar 

затем выполняешь следующее ставит имя связывания foo и bar в spam модуль:

>>> import spam 
>>> spam.foo 
<module 'spam.foo' from '/tmp/imports/spam/foo.py'> 
>>> spam.bar 
<module 'spam.bar' from '/tmp/imports/spam/bar.py'> 

Учитывая знакомые правила привязки имени Python, это может показаться удивительным, но на самом деле это фундаментальная особенность системы импорта. Инвариантный холдинг состоит в том, что если у вас есть sys.modules['spam'] и sys.modules['spam.foo'] (как и после импорта), последний должен отображаться как атрибут foo первого.

+0

Спасибо за ваш ответ. Я вижу, что это происходит, но я не понимаю, почему. Я нашел это в некотором рабочем коде prod и хотел понять, почему он работает таким образом, прежде чем я избавился от него. – DeTeReR

+0

@DeTeReR: что именно вы не понимаете? –

+0

Имеет смысл, что модуль 'test.b' существует (так же, как' os.path' существует, когда вы импортируете 'os', так как пакет' os' также импортирует из 'os.path'). Это объясняет, почему вы можете ссылаться на 'test.b.B' из' run.py'. Но это не объяснение того, почему модуль '__init__' пакета' test' неожиданно имеет 'b' * как имя *. В 'from b import B' нет ничего, что должно было бы сделать имя' b' доступным на местном уровне. – poke

 Смежные вопросы

  • Нет связанных вопросов^_^