Python: импорт подпакета или подмодуля

Уже имея плоские пакеты, я не ожидал проблемы, с которой я столкнулся с вложенными пакетами. Вот...

Макет каталога

dir
 |
 +-- test.py
 |
 +-- package
      |
      +-- __init__.py
      |
      +-- subpackage
           |
           +-- __init__.py
           |
           +-- module.py

Содержимое init.py

Оба package/__init__.py и package/subpackage/__init__.py пусты.

Содержимое module.py

# file `package/subpackage/module.py`
attribute1 = "value 1"
attribute2 = "value 2"
attribute3 = "value 3"
# and as many more as you want...

Содержимое test.py (3 версии)

Версия 1

# file test.py
from package.subpackage.module import *
print attribute1 # OK

Это плохой и небезопасный способ импорта вещей (импортировать все в большой объем), но он работает.

Версия 2

# file test.py
import package.subpackage.module
from package.subpackage import module # Alternative
from module import attribute1

Более безопасный способ импорта, элемент за пунктом, но он терпит неудачу, Python этого не хочет: сбой: сообщение "No module named module" отсутствует. Однако...

# file test.py
import package.subpackage.module
from package.subpackage import module # Alternative
print module # Surprise here

... говорит <module 'package.subpackage.module' from '...'>. Так что модуль, но не модуль /-P 8-O... uh

Версия 3

# file test.py v3
from package.subpackage.module import attribute1
print attribute1 # OK

Это работает. Таким образом, вы либо вынуждены использовать префикс overkill все время, либо использовать небезопасный способ, как в версии №1, и запретить Python использовать безопасный способ? Лучший способ, который безопасен и избегает ненужного длинного префикса, является единственным, который отвергает Python? Это потому, что он любит import * или потому, что он любит чересчур префиксы (что не помогает обеспечить соблюдение этой практики)?

Извините за трудные слова, но за два дня я пытаюсь обойти это глупое поведение. Если бы я был где-то совершенно не прав, это оставит меня с чувством, что что-то действительно сломано в модели пакета и подпакетов Python.

Примечания

  • Я не хочу полагаться на sys.path, чтобы избежать глобальных побочных эффектов или файлов *.pth, которые являются еще одним способом игры с sys.path с теми же глобальными эффектами. Чтобы решение было чистым, оно должно быть только локальным. Либо Python способен обрабатывать подпакет, либо нет, но он не должен требовать играть с глобальной конфигурацией, чтобы иметь возможность обрабатывать локальные файлы.
  • Я также попытался использовать импорт в package/subpackage/__init__.py, но он ничего не решил, он делает то же самое и жалуется, что subpackage не является известным модулем, а print subpackage говорит об этом модуле (странное поведение, опять же).

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

Какой-либо другой известный способ, помимо трех, которые я пробовал? Что-то, о чем я не знаю?

(Вздох)

-----% < ----- edit ----- > % -----

Заключение до сих пор (после комментариев пользователей)

В Python нет ничего похожего на настоящий подпакет, так как все ссылки на пакеты ссылаются только на глобальную dictionnary, что означает, что нет локального словаря, что означает, что нет способа управлять ссылкой локального пакета.

Вам нужно либо использовать полный префикс, либо короткий префикс или псевдоним. Как в:

Полная префиксная версия

from package.subpackage.module import attribute1
# An repeat it again an again
# But after that, you can simply:
use_of (attribute1)

Короткая префиксная версия (но повторяющийся префикс)

from package.subpackage import module
# Short but then you have to do:
use_of (module.attribute1)
# and repeat the prefix at every use place

Или иначе, вариант выше.

from package.subpackage import module as m
use_of (m.attribute1)
# `m` is a shorter prefix, but you could as well
# define a more meaningful name after the context

Факторизованная версия

Если вы не возражаете об импорте нескольких объектов одновременно в пакетном режиме, вы можете:

from package.subpackage.module import attribute1, attribute2
# and etc.

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

Обновление (2012-09-14):

Наконец, на практике это выглядит нормально, за исключением комментариев о макете. Вместо этого я использовал:

from package.subpackage.module import (

    attribute1, 
    attribute2,
    attribute3,
    ...)  # and etc.

Ответ 1

Кажется, вы неправильно понимаете, как import выполняет поиск модулей. Когда вы используете оператор импорта, он всегда ищет фактический путь к модулю (и/или sys.modules); он не использует объекты модуля в локальном пространстве имен, которые существуют из-за предыдущего импорта. Когда вы выполните:

import package.subpackage.module
from package.subpackage import module
from module import attribute1

Вторая строка ищет пакет под названием package.subpackage и импортирует module из этого пакета. Эта строка не влияет на третью строку. Третья строка просто ищет модуль под названием module и не находит его. Он не "повторно использует" объект с именем module, который вы получили из строки выше.

Другими словами, from someModule import ... не означает "из модуля, называемого someModule, который я импортировал ранее...", это означает "из модуля с именем someModule, который вы найдете на sys.path...". Невозможно "поэтапно" создать путь к модулю путем импорта пакетов, которые приводят к нему. При импорте всегда нужно ссылаться на все имя модуля.

Не ясно, чего вы пытаетесь достичь. Если вы хотите импортировать конкретный атрибут объекта1, просто сделайте from package.subpackage.module import attribute1 и сделайте с ним. Вам не нужно беспокоиться о длинном package.subpackage.module после импортирования имени, которое вы хотите от него.

Если вы хотите получить доступ к модулю для доступа к другим именам позже, вы можете сделать from package.subpackage import module, и, как вы видели, вы можете сделать module.attribute1 и так далее сколько угодно.

Если вы хотите, чтобы оба --- то есть, если вы хотите, чтобы attribute1 был доступен напрямую, и вы хотите, чтобы module был доступен, просто выполните оба вышеуказанных действия:

from package.subpackage import module
from package.subpackage.module import attribute1
attribute1 # works
module.someOtherAttribute # also works

Если вам не нравится вводить package.subpackage еще дважды, вы можете просто создать локальную ссылку на атрибут1:

from package.subpackage import module
attribute1 = module.attribute1
attribute1 # works
module.someOtherAttribute #also works

Ответ 2

Причина # 2 не удалась из-за того, что sys.modules['module'] не существует (процедура импорта имеет свою собственную область видимости и не может видеть локальное имя module), а там нет модуля module или пакета на диске. Обратите внимание, что вы можете отделить несколько импортированных имен запятыми.

from package.subpackage.module import attribute1, attribute2, attribute3

также:

from package.subpackage import module
print module.attribute1

Ответ 3

Если все, что вы пытаетесь сделать, это получить атрибут1 в вашем глобальном пространстве имен, версия 3 выглядит просто замечательно. Почему это префикс overkill?

В версии 2 вместо

from module import attribute1

вы можете сделать

attribute1 = module.attribute1