Подтвердить что ты не робот

Импорт ведет себя по-другому, когда в __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

Затем мы получаем ошибку.

4b9b3361

Ответ 1

Ситуация такова: у вас есть script (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.


Сторона node: ваш код не будет работать с Python 3, потому что относительный импорт удален, Чтобы заставить ваш код работать с Python 3, вам нужно написать:

from test.b import B

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


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

Когда подмодуль загружается с использованием любого механизма (например, 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 для первого.