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


90

Вже використовуючи плоскі пакети, я не очікував проблеми, з якою я зіткнувся з вкладеними пакетами. Ось…

Розмітка каталогу

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, або використовувати небезпечний спосіб, як у версії No1, і заборонений 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.

Не в моєму першому улюбленому смаку (я віддаю перевагу одній заяві про імпорт на імпортовану організацію), але, можливо, це той, який я особисто підтримую.

Оновлення (14.09.2012):

Нарешті, здається, це нормально на практиці, за винятком коментарів щодо макета. Замість вищезазначеного я використав:

from package.subpackage.module import (

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

Як ідуть справи, коли ви пишете "from. Import module" у "/package/subpackage/__init__.py"?
Markus Unterwaditzer

Ваша "факторизована версія" здається абсолютно правильною для того, що ви хочете зробити. Якщо ви робите окремий рядок імпорту для attribute1 та attribute2 (як вам більше подобається), ви просто навмисно даєте собі більше роботи. Для цього немає причин.
BrenBarn

Вибачте, але я не розумію, що ви хочете. Чи не могли б Ви переформулювати своє питання більш чітко? Що б ти хотів зробити саме? Я маю на увазі, що б ви хотіли написати, що не працює, і як би ви очікували, що це спрацює? Те, що я читав, я думаю, що ви включаєте семантику імпорту як Java або, можливо, C. Останнє: ви можете зробити модуль "star-import" безпечним, додавши __all__змінну, яка містить список імен, які слід експортувати при імпорті зірки. редагувати: Добре, читаючи відповідь BrenBarn, я зрозумів, що ви мали на увазі.
Bakuriu

Відповіді:


68

Здається, ви нерозумієте, як здійснюється 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навіть двічі, ви можете просто створити локальне посилання на attribute1:

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

Ваші коментарі йдуть у тому ж напрямку, що і коментарі Ігнасіо Васкеса-Абрамса (я прокоментував його повідомлення). Ви, крім того, про використання module.attribute1- це те , про що я хоч і думав, але я хоч мав би спосіб уникнути необхідності введення префікса скрізь. Тому я повинен або використовувати префікс скрізь, або створюючи локальний псевдонім, повторюючи ім’я. Не той стиль, якого я очікував, але якщо немає способу (зрештою, я звик до Ади, яка вимагає чогось подібного зі своїми деклараціями про перейменування).
Hibou57,

@ Hibou57: Мені досі незрозуміло, що ти намагаєшся зробити у своїй "Версії 2". Що ви хочете зробити, а це неможливо? Ви ніколи не хочете повторно вводити будь-яку частину імені пакета / модуля / атрибута, але все одно імпортуєте як модуль, так і його атрибут?
BrenBarn

Я хотів отримати посилання на локальний пакет, як і спосіб наведення посилання на локальний об'єкт. Здається, нарешті є справді місцеві посилання на модулі, але ви не можете імпортувати з них. Це суміш місцевого та глобального із кумедним смаком (деякі речі можуть бути місцевими, деякі інші мають бути глобальними, мені це не подобається, але я в порядку, поки я тепер краще розумію, як це працює). До речі, дякую за ваше повідомлення.
Hibou57,

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']яке я досі не знав, змусило мене думати, що цього я боявся (і BrenBarn підтверджує): у Python немає нічого подібного до реальних підпакетів. sys.modules, як випливає з назви, є загальносвітовим, і якщо всі посилання на модулі покладаються на це, тоді немає нічого подібного до локального посилання на модуль (можливо, він поставляється з Python 3.x?).
Hibou57,

Ваше використання "посилання" там неоднозначне; перший importу # 2 генерує локальне посилання на package.subpackage.moduleприв'язане до module.
Ігнасіо Васкес-Абрамс

Так, але це «модуль», з якого я не можу імпортувати ;-)
Hibou57,

0

Якщо все, що ви намагаєтеся зробити, це отримати attribute1 у вашому глобальному просторі імен, версія 3 здається цілком чудовою. Чому це префікс overkill?

У версії 2 замість

from module import attribute1

Ви можете зробити

attribute1 = module.attribute1

attribute1 = module.attribute1просто повторює назву без доданої вартості. Я знаю, що це працює, але мені не подобається цей стиль (що не означає, що мені не подобається ваша відповідь).
Hibou57,

2
Думаю, я, як і всі люди, які коментують тут, не розумію, що ти хочеш робити. У всіх прикладах, які ви подаєте, схоже, ви хочете отримати символ із підпакета у вашому просторі імен. Ваш неробочий приклад (приклад 2) хоче це зробити, імпортуючи підмодуль з пакета, а потім імпортуючи символ із цього підмодуля. Не знаю, чому ви хочете зробити це у два кроки, а не в один. Можливо, поясніть більше, яким би було ваше ідеальне рішення і чому.
Thomas Vander Stichele
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.