Относительные проблемы импорта в Python 3

Импорт Python сводит меня с ума (мой опыт с импортом python когда-то совсем не соответствует идиоме "Явный лучше, чем неявный":():

[app]
    start.py
        from package1 import module1
    [package1]
        __init__.py
            print('Init package1')
        module1.py
            print('Init package1.module1')
            from . import module2
        module2.py
            print('Init package1.module2')
            import sys, pprint
            pprint.pprint(sys.modules)
            from . import module1

Я получаю:

[email protected]:~/Desktop/app2$ python3 start.py 
Init package1
Init package1.module1
Init package1.module2
{'__main__': <module '__main__' from 'start.py'>,
 ...
 'package1': <module 'package1' from '/home/vic/Desktop/app2/package1/__init__.py'>,
 'package1.module1': <module 'package1.module1' from '/home/vic/Desktop/app2/package1/module1.py'>,
 'package1.module2': <module 'package1.module2' from '/home/vic/Desktop/app2/package1/module2.py'>,
 ...
Traceback (most recent call last):
  File "start.py", line 3, in <module>
    from package1 import module1
  File "/home/vic/Desktop/app2/package1/module1.py", line 3, in <module>
    from . import module2
  File "/home/vic/Desktop/app2/package1/module2.py", line 5, in <module>
    from . import module1
ImportError: cannot import name module1
[email protected]:~/Desktop/app2$ 

import package1.module1 работает, но я хочу использовать from . import module1, потому что хочу сделать package1 переносимым для других приложений, поэтому я хочу использовать относительные пути.

Я использую python 3.

Мне нужен круговой импорт. Функция в модуле 1 утверждает, что один из ее параметров является экземпляром класса, определенного в модуле 2 и наоборот.

Другими словами:

sys.modules содержит 'package1.module1': <module 'package1.module1' from '/home/vic/Desktop/app2/package1/module1.py'>. Я хочу получить ссылку на него в форме from . import module1, но он пытается получить имя, а не пакет, например, в случае import package1.module1 (который отлично работает). Я пробовал import .module1 as m1 - но это синтаксическая ошибка.

Кроме того, from . import module2 в module1 работает отлично, но from . import module1 в module2 не работает...

UPDATE:

Этот хак работает (но я ищу "официальный" способ):

print('Init package1.module2')

import sys, pprint
pprint.pprint(sys.modules)

#from . import module1
parent_module_name = __name__.rpartition('.')[0]
module1 = sys.modules[parent_module_name + '.module1']

Ответ 1

Лучшим решением для вашей проблемы является размещение пакета1 в отдельном пакете. Конечно, тогда он не может импортировать пакет2, но опять же, если он многократно используется, зачем он?

Ответ 2

Следует избегать циркулярного импорта, см. также этот ответ на соответствующий вопрос или эта статья об effbot.org.

В этом случае проблема заключается в том, что вы импортируете from ., где . - текущий пакет. Таким образом, все ваши импорта from . import X проходят через пакеты __init__.py.

Вы можете сделать вашу проблему немного более заметной, если вы явно импортируете свои модули в __init__.py и даете им другое имя (и настройте другие импорты, чтобы использовать эти имена, конечно):

print('Init package1')
from . import module1 as m1
from . import module2 as m2

Теперь, когда вы импортируете m1 в start.py, пакет сначала инициализирует m1 и переходит в строку from . import m2. В этот момент в __init__.py нет m2, поэтому вы получаете ошибку импорта. Если вы переключаете операторы импорта в __init__.py вокруг (так что сначала загружаете m2), то в m2 он находит строку from . import m1, которая не работает по той же причине, что и раньше.

Если вы явно не импортируете модули в __init__.py, то что-то подобное все еще происходит в фоновом режиме. Разница в том, что вы получаете менее плоскую структуру (поскольку импорт больше не запускается из пакета). Таким образом, оба module1 и module2 получат "начальный", и вы получите соответствующие отпечатки инициализации.

Чтобы он работал, вы можете сделать абсолютный импорт в module2. Таким образом, вы могли бы избежать того, что пакет должен сначала разрешить все, и повторно использовать импорт из start.py (так как он имеет тот же путь импорта).

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

(Надеюсь, мое объяснение имеет какой-то смысл, мне уже было трудно писать его, но общая идея должна быть ясной, надеюсь...)

изменить

В ответ на ваше обновление; то, что вы делаете, заключается в том, что вы используете полное имя пакета, чтобы получить ссылку на модуль. Это эквивалентно (но гораздо более сложному) первому возможному варианту, чтобы заставить его работать; вы используете абсолютный импорт, используя тот же путь импорта, что и в start.py.

Ответ 3

Ваше обновление эмулирует то, что делает абсолютный импорт: import package1.module1, если вы делаете это, пока module1 импортируется. Если вы хотите использовать имя динамического родительского пакета, то для импорта module1 в module2.py:

import importlib
module1 = importlib.import_module('.module1', __package__)

Мне нужен круговой импорт. Функция в модуле 1 утверждает, что одна из ее Параметр - это экземпляр класса, определенного в модуле 2 и наоборот.

Вы можете перенести один класс на отдельный модуль, чтобы разрешить циклическую зависимость или сделать импорт на уровне функции, если вы не хотите использовать абсолютный импорт.

.
├── start.py
#       from package1 import module1
└── package1
    ├── __init__.py
#           print("Init package1")
#           from . import module1, module2
    ├── c1.py
#           print("Init package1.c1")
#           class C1:
#               pass
    ├── module1.py
#           print("Init package1.module1")
#           from .c1 import C1
#           from .module2 import C2
    └── module2.py
#           print("Init package1.module2")
#           from .c1 import C1
#           class C2:
#               pass
#           def f():
#               from .module1 import C1

Выход

Init package1
Init package1.module1
Init package1.c1
Init package1.module2

Другой вариант, который может быть проще, чем рефакторинг c1.py, заключается в объединении module{1,2}.py в один common.py. module{1,2}.py сделать импорт из common в этом случае.

Ответ 4

module2.py
          import module1

Работает тоже.

Ответ 5

Принятый ответ Циркулярная зависимость импорта в Python дает хороший результат:

Если a зависит от c и c зависит от a, они не являются фактически одной и той же единицей, то?

Вы должны действительно изучить, почему вы разделили a и c на два пакета, потому что либо у вас есть код, который вы должны разделить на другой пакет (чтобы они зависели от этого нового пакета, но не друг от друга), или вы должны объединить их в один пакет.
— Лассе В. Карлсен ♦

Возможно, вам стоит рассмотреть возможность размещения их в одном модуле.:)

Ответ 6

Сегодня я столкнулся с этой же проблемой, и, похоже, это действительно сломано в python3.4, но работает в python3.5.

changelog имеет запись:

Циркулярный импорт, включающий относительный импорт, теперь поддерживается. (Бретт Кэннон и Антуан Питроу в bpo-17636).

Просматривая ошибку, кажется, что это не столько фиксированное buf, сколько новая функция в способе импорта. Ссылаясь на poke answer above, он показывает, что from . import foo означает загрузить __init__.py и получить foo от него (возможно, из неявно загруженного списка подмодулей). Поскольку python3.5, from . import foo будет делать то же самое, но если foo недоступен в качестве атрибута, он будет возвращаться к просмотру списков загруженных модулей (sys.modules), чтобы увидеть, если он уже присутствует там, который фиксирует этот конкретный случай. Я не на 100% уверен, что правильно представил, как это работает.

Ответ 7

Убедитесь, что ваш package1 - это папка. Создайте класс в __init__.py - скажем class1. Включите свою логику в метод под class1 - скажем method1.

Теперь напишите следующий код -

from .package1 import class1
class1.method1()

Это был мой способ разрешить это. Подводя итог, ваш корневой каталог ., поэтому напишите инструкцию import, используя обозначения ., например. from .package или from app.package.