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

Импорт в __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'

обходные

Есть изменения, которые заставляют это работать:

  1. pkg/subpkg/__init__.py и импортируйте напрямую из pkg.subpkg.one.

    Я не рассматриваю это как вариант, потому что AFAIK "поднимает" вещи на уровень пакета в порядке. Вот цитата из статьи:

    В __init__.py обычно нужно импортировать выбранные классы, функции и т.д. На уровень пакета, чтобы их можно было легко импортировать из пакета.

  2. Изменение import as по one.py from import в one.py:

    from pkg.subpkg import two_longname
    
    class One(two_longname.Two):
        pass
    

    Единственный минус в том, что я не могу создать короткий псевдоним для модуля. Я понял эту идею из ответа @begueradj.

Также можно использовать относительный импорт в one.py для решения проблемы. Но я думаю, что это всего лишь вариант обходного пути № 2.

Вопросы

  1. Может кто-нибудь объяснить, что на самом деле здесь происходит? Почему сочетание импорта в __init__.py и использования import as приводит к таким проблемам?

  2. Есть ли лучшие обходные пути?


Оригинальный пример

Это мой оригинальный пример. Это не очень реалистично, но я не удаляю его, поэтому ответ @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).

4b9b3361

Ответ 1

Вы неправильно предполагаете, что нельзя иметь псевдоним с from ... import, поскольку from ... import ... as существует там с Python 2.0. import ... as - это неясный синтаксис, о котором мало кто знает, но который вы случайно использовали в своем коде.

PEP 0221 утверждает, что следующие 2 "фактически" одинаковы:

  1. import foo.bar.bazaar as baz
  2. 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

Надеюсь, что это поможет.