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

91

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

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

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 этого не хочет: сбой с сообщением: «Нет модуля с именем модуль». Однако …

# 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 ... э-э

Версия 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.

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

(вздох)

-----% <----- редактировать ----->% -----

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

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

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

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

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.
Hibou57
источник
Как дела, когда вы пишете "from. Import module" в "/package/subpackage/__init__.py"?
Маркус Унтервадитцер 01
Ваша «факторизованная версия» кажется совершенно подходящей для того, что вы хотите сделать. Если вы делаете отдельную строку импорта для attribute1 и attribute2 (как вы «предпочитаете»), вы просто намеренно даете себе больше работы. Для этого нет причин.
BrenBarn 01
Извините, но я не понимаю, что вам нужно. Не могли бы вы перефразировать свой вопрос более четко? Что именно вы хотите сделать? Я имею в виду, что бы вы хотели написать, что не работает, и как вы ожидаете, что это будет работать? Судя по тому, что я прочитал, я думаю, вы, какова будет семантика импорта, как в Java или, может быть, в C. И последнее: вы можете сделать модуль «звездообразный импорт» безопасным, добавив __all__переменную, содержащую список имен, которые должны быть экспортированы при звездном импорте. edit: Хорошо, читая ответ BrenBarn, я понял, что вы имели в виду.
Bakuriu 01

Ответы:

69

Похоже, вы неправильно понимаете, как 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 ...». Невозможно «постепенно» создать путь к модулю путем импорта пакетов, которые к нему ведут. При импорте всегда нужно ссылаться на полное имя модуля.

Непонятно, чего вы пытаетесь достичь. Если вы хотите импортировать только конкретный объект attribute1, просто сделайте 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даже дважды, вы можете просто вручную создать локальную ссылку на attribute1:

from package.subpackage import module
attribute1 = module.attribute1
attribute1 # works
module.someOtherAttribute #also works
BrenBarn
источник
Ваши комментарии идут в том же направлении, что и комментарии Игнасио Васкеса-Абрамса (я прокомментировал его сообщение). Вы добавили в конце, об использовании, module.attribute1это то , о чем я думал, но я думал, что есть способ избежать необходимости повсюду префикса. Поэтому мне приходится либо использовать префикс везде, либо создавать локальный псевдоним, повторяя имя. Не в том стиле, которого я ожидал, но если нет возможности (в конце концов, я привык к Аде, которая требует чего-то похожего с декларациями переименования).
Hibou57, 01
@ Hibou57: Мне все еще непонятно, чего вы пытаетесь достичь в своей «Версии 2». Что вы хотите сделать, что невозможно? Вы хотите никогда не вводить повторно какую-либо часть имени пакета / модуля / атрибута, но при этом импортировать и модуль, и его атрибут?
BrenBarn 01
Я хотел иметь ссылку на локальный пакет, так же как и ссылку на локальный объект. Кажется, наконец-то появилась ссылка на локальный модуль, но вы не можете импортировать из нее. Это смесь локального и глобального с забавным вкусом (некоторые вещи могут быть локальными, некоторые другие должны быть глобальными, мне это не нравится, но я в порядке, пока я лучше понимаю, как это работает). Кстати, спасибо за ваше сообщение.
Hibou57, 01
1
Я не уверен, что вы все еще понимаете, как это работает. Или то, что вы сделали в 2012 году, во всяком случае.
Hejazzman
1
Каждый раз, когда я возвращаюсь к Python после 6-месячного увольнения, я оказываюсь здесь. Если бы я только мог голосовать каждый раз, когда захожу на эту страницу! Я собираюсь сделать гигантский плакат с таким предложением: «Невозможно« постепенно »построить путь к модулю путем импорта пакетов, которые к нему ведут».
PatrickT
10

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

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

Также:

from package.subpackage import module
print module.attribute1
Игнасио Васкес-Абрамс
источник
Ваша ссылка, о sys.modules['name']которой я не знал до сих пор, заставила меня подумать, что я боялся именно этого (и БренБарн подтверждает): в Python нет ничего лучше настоящих подпакетов. sys.modules, как следует из названия, является глобальным, и если все ссылки на модули основаны на этом, то нет ничего лучше локальной ссылки на модуль (может быть, в Python 3.x?).
Hibou57, 01
Ваше использование слова «ссылка» неоднозначно; первый importв # 2 генерирует локальную ссылку для package.subpackage.moduleпривязки module.
Игнасио Васкес-Абрамс
Да, но это «модуль», из которого я не могу импортировать ;-)
Hibou57 01
0

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

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

from module import attribute1

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

attribute1 = module.attribute1
Томас Вандер Стихеле
источник
attribute1 = module.attribute1просто повторяет имя без добавленной стоимости. Я знаю, что это работает, но мне не нравится этот стиль (что не означает, что мне не нравится ваш ответ).
Hibou57,
2
Думаю, я, как и все комментирующие здесь люди, не понимаю, что вы действительно хотите делать. Во всех приведенных вами примерах похоже, что вы хотите получить символ из подпакета в вашем пространстве имен. Ваш нерабочий пример (пример 2) хочет сделать это путем импорта подмодуля из пакета, а затем импортирования символа из этого подмодуля. Я не знаю, почему вы хотите сделать это в два этапа вместо одного. Может быть, объясните подробнее, каким было бы ваше идеальное решение и почему.
Thomas Vander Stichele