Импорт в __init__.py и `import as`.
Я столкнулся с проблемой наличия импорта в __init__.py
и использования import as
с абсолютным импортом в модулях пакета.
В моем проекте есть подпакет, и в нем __init__.py
я "поднимаю" один из классов из модуля на уровень подпакета с помощью оператора from import as
. Модуль импортирует другие модули из этого подпакета с абсолютным импортом. Я получаю эту ошибку AttributeError: 'module' object has no attribute 'subpkg'
.
пример
Структура:
pkg/
├── __init__.py
├── subpkg
│ ├── __init__.py
│ ├── one.py
│ └── two_longname.py
└── tst.py
pkg/__ init__.py пуст.
pkg/subpkg/__ init__.py:
from pkg.subpkg.one import One
pkg/subpkg/one.py:
import pkg.subpkg.two_longname as two
class One(two.Two):
pass
pkg/subpkg/two_longname.py:
class Two:
pass
pkg/tst.py:
from pkg.subpkg import One
print(One)
Выход:
$ python3.4 -m pkg.tst
Traceback (most recent call last):
File "/usr/lib/python3.4/runpy.py", line 170, in _run_module_as_main
"__main__", mod_spec)
File "/usr/lib/python3.4/runpy.py", line 85, in _run_code
exec(code, run_globals)
File "/home/and/dev/test/python/imptest2/pkg/tst.py", line 1, in <module>
from pkg.subpkg import One
File "/home/and/dev/test/python/imptest2/pkg/subpkg/__init__.py", line 1, in <module>
from pkg.subpkg.one import One
File "/home/and/dev/test/python/imptest2/pkg/subpkg/one.py", line 1, in <module>
import pkg.subpkg.two_longname as two
AttributeError: 'module' object has no attribute 'subpkg'
обходные
Есть изменения, которые заставляют это работать:
-
pkg/subpkg/__init__.py
и импортируйте напрямую из pkg.subpkg.one
.
Я не рассматриваю это как вариант, потому что AFAIK "поднимает" вещи на уровень пакета в порядке. Вот цитата из статьи:
В __init__.py
обычно нужно импортировать выбранные классы, функции и т.д. На уровень пакета, чтобы их можно было легко импортировать из пакета.
-
Изменение import as
по one.py
from import
в one.py
:
from pkg.subpkg import two_longname
class One(two_longname.Two):
pass
Единственный минус в том, что я не могу создать короткий псевдоним для модуля. Я понял эту идею из ответа @begueradj.
Также можно использовать относительный импорт в one.py
для решения проблемы. Но я думаю, что это всего лишь вариант обходного пути № 2.
Вопросы
-
Может кто-нибудь объяснить, что на самом деле здесь происходит? Почему сочетание импорта в __init__.py
и использования import as
приводит к таким проблемам?
-
Есть ли лучшие обходные пути?
Оригинальный пример
Это мой оригинальный пример. Это не очень реалистично, но я не удаляю его, поэтому ответ @begueradj все еще имеет смысл.
pkg/__ init__.py пуст.
pkg/subpkg/__ init__.py:
from pkg.subpkg.one import ONE
pkg/subpkg/one.py:
import pkg.subpkg.two
ONE = pkg.subpkg.two.TWO
pkg/subpkg/two.py:
TWO = 2
pkg/tst.py:
from pkg.subpkg import ONE
Выход:
$ python3.4 -m pkg.tst
Traceback (most recent call last):
File "/usr/lib/python3.4/runpy.py", line 170, in _run_module_as_main
"__main__", mod_spec)
File "/usr/lib/python3.4/runpy.py", line 85, in _run_code
exec(code, run_globals)
File "/home/and/dev/test/python/imptest/pkg/tst.py", line 1, in <module>
from pkg.subpkg import ONE
File "/home/and/dev/test/python/imptest/pkg/subpkg/__init__.py", line 2, in <module>
from pkg.subpkg.one import ONE
File "/home/and/dev/test/python/imptest/pkg/subpkg/one.py", line 6, in <module>
ONE = pkg.subpkg.two.TWO
AttributeError: 'module' object has no attribute 'subpkg'
Первоначально у меня было это в one.py:
import pkg.subpkg.two as two
ONE = two.TWO
В этом случае я получаю ошибку при импорте (как и в моем первоначальном проекте, который тоже использует import as
).
Ответы
Ответ 1
Вы неправильно предполагаете, что нельзя иметь псевдоним с from ... import
, поскольку from ... import ... as
существует там с Python 2.0. import ... as
- это неясный синтаксис, о котором мало кто знает, но который вы случайно использовали в своем коде.
PEP 0221 утверждает, что следующие 2 "фактически" одинаковы:
import foo.bar.bazaar as baz
from foo.bar import bazaar as baz
Это утверждение не совсем верно в версиях Python вплоть до 3.6.x включительно, о чем свидетельствует angular случай, с которым вы встречались, а именно, если необходимые модули уже существуют в sys.modules
, но еще не инициализированы. import ... as
требует, чтобы модуль foo.bar
был внедрен в пространство имен foo
в качестве атрибута bar
, в дополнение к тому, что он находится в sys.modules
, тогда как from ... import ... as
ищет foo.bar
в sys.modules
.
(Обратите также внимание, что import foo.bar
только гарантирует, что модуль foo.bar
находится в sys.modules
и доступен как foo.bar
, но может быть еще не полностью инициализирован.)
Изменение кода следующим образом помогло мне:
# import pkg.subpkg.two_longname as two
from pkg.subpkg import two_longname as two
И код отлично работает как на Python 2, так и на Python 3.
Кроме того, в one.py
вы не можете сделать from pkg import subpkg
по той же причине.
Чтобы продемонстрировать эту ошибку далее, исправьте one.py
, как указано выше, и добавьте следующий код в tst.py
:
import pkg
import pkg.subpkg.two_longname as two
del pkg.subpkg
from pkg.subpkg import two_longname as two
import pkg.subpkg.two_longname as two
Сбой только в последней строке, потому что from ... import
обращается к sys.modules
для pkg.subpkg
и находит его там, тогда как import ... as
обращается к sys.modules
для pkg
и пытается найти subpkg
как атрибут в модуль pkg
. Поскольку мы только что удалили этот атрибут, последняя строка завершается ошибкой с AttributeError: 'module' object has no attribute 'subpkg'
.
Поскольку синтаксис import foo.bar as baz
немного неясен и добавляет больше angular случаев, и я редко, если когда-либо видел его использование, я бы рекомендовал избегать его полностью и отдавать предпочтение from .. import ... as
.
Ответ 2
Вот теория о том, что происходит.
Когда вы используете зарезервированное слово as
, например:
import pkg.subpkg.two_longname as two
Python должен полностью инициализировать и разрешить все зависимости, которые связаны с pkg.subpkg
. Но есть проблема, чтобы полностью загрузить subpkg
вам нужно полностью загрузить one.py
, а также правильно? который в то же время импортирует two_longname.py
с помощью ключевого слова as
... Вы можете увидеть рекурсию здесь? Вот почему на момент совершения:
import pkg.subpkg.two_longname as two
вы получите сообщение об ошибке subpkg
не существует.
Чтобы выполнить тест, перейдите к one.py и измените его на это:
#import pkg.subpkg.two_longname as two
from pkg.subpkg import two_longname
#class One(two.Two):
class One(two_longname.Two):
pass
Я предполагаю, что это все о производительности, Python загружает модуль частично, когда это возможно. И ключевым словом as
является одно из исключений. Я не знаю, есть ли другие, но было бы интересно узнать о них.
Ответ 3
Как утверждает принятый ответ, это проблема с поведением Python.
Я подал ошибку: http://bugs.python.org/issue30024
Исправление Сергея Сторчака было объединено и ожидалось в Python 3.7
Ответ 4
Структура вашего проекта относительно способа вызывать модули должна быть такой:
pkg/
├── __init__.py
├── subpkg
│ ├── __init__.py
│ ├── one.py
│ └── two.py
tst.py
Определите свой two.py следующим образом:
class TWO:
def functionTwo(self):
print("2")
Определите свой one.py следующим образом:
from pkg.subpkg import two
class ONE:
def functionOne(self):
print("1")
self.T=two.TWO()
print("Calling TWO from ONE: ")
self.T.functionTwo()
Определите test.py
from pkg.subpkg import one
class TEST:
def functionTest(self):
O=one.ONE()
O.functionOne()
if __name__=='__main__':
T=TEST()
T.functionTest()
Когда вы выполните, вы получите следующее:
1
Calling TWO from ONE:
2
Надеюсь, что это поможет.