0,0
рейтинг
21 января 2013 в 13:56

Разработка → О порядке поиска пакетов и модулей для импорта в Python из песочницы

Начать, видимо, следует с того, что речь пойдет об интерпретаторе CPython версии 2.7.x (примеры проверялись на версии 2.7.3).

На официальном сайте имеются описания инструкции import и модулей в Python:

Из них следует, что в Python имеются пакеты (package), модули (module) и имена, определенные в модулях (names). Также следует отметить, что в некоторых частях документации модули называются подмодулями (submodule), если они размещены внутри пакета.

В языке Python инструкция import позволяет импортировать пакеты, модули и имена в пространство имен, в котором инструкция import выполняется. При это существует две интересные особенности:
  1. Из синтаксиса инструкции import не всегда явно следует, что именно должно быть импортированно: пакет, модуль или имя
  2. Синтаксисом инструкции import невозможно явно указать, что путь к модулю является абсолютным путем (хотя явно указать, что путь является относительным можно, а также возможно изменение семантики инструкции, в части использования абсолютного пути по умолчанию, см. www.python.org/dev/peps/pep-0328 )

Из этих двух особенностей следуют такие неоднозначности для записи import abcd:
  1. Импортировать ПАКЕТ abcd, либо импортировать МОДУЛЬ abcd
  2. Импортировать пакет/модуль abcd из ТЕКУЩЕГО ПАКЕТА (из пакета того модуля, в котором исполняется import abcd), либо ИЗ ПАКЕТА в соответствии с перечнем каталогов, указанных в sys.path

Еще примеры неоднозначностей:
  • from abcd import defg: (импортировать модуль defg из пакета abcd, либо импортировать пакет defg из пакета abcd, либо импортировать имя defg из пакета abcd, либо импортировать имя defg из модуля abcd) X (из того же пакета, либо из пакета в соответствии с sys.path)
  • import abcd.defg: (импортировать пакет defg из пакета abcd, импортировать модуль defg из пакета abcd) X (из того же пакета, либо из пакета в соответствии с sys.path)

Для разрешения эти декларативных неоднозначностей должен существовать императивный алгоритм. Такой алгоритм в некотором виде описан в официальной документации Python.


Алгоритм поиска имен для import abcd


Поиск имени abcd для импорта происходит по следующему алгоритму:
Что ищем Где ищем* Комментарий
1 пакет abcd в пакете текущего модуля (модуля, в котором исполняется import abcd) только, если текущий модуль сам содержится в пакете**
2 модуль abcd в пакете текущего модуля (модуля, в котором исполняется import abcd) только, если текущий модуль сам содержится в пакете**
3 модуль abcd во встроенных (built-in) модулях ссылка на документацию указана в ***
4 пакет abcd в каталогах, указанных в sys.path ссылка на документацию указана в ****
5 модуль abcd в каталогах, указанных в sys.path ссылка на документацию указана в ****


Дальнейший поиск прекращается в случае успешного поиска пакета на одном из выше перечисленных шагов.

* Информация о приоритете поиска пакета над модулем установленая опытным путем, в документации это явно не указано.

** В этом случае переменная __package__ этого модуля равна названию пакета, иначе она равна None.
Ссылка на документцию:
docs.python.org/2/tutorial/modules.html#intra-package-references
“In fact, such references are so common that the import statement first looks in the containing package before looking in the standard module search path.”

(!!!) Здесь стоит заметить отсутствие упоминания данного факта в другом месте того же документа (http://docs.python.org/2/tutorial/modules.html#the-module-search-path), что вводит в заблуждение (см. bugs.python.org/issue16891).

(!!!) Второе, что следует отметить — это что данный шаг поиска присутствует только в случае, если модуль, в котором исполняется import abcd сам импортирован из пакета (т.е. с помощью инструкции import <имя пакета>.<имя модуля>). В случаях импорта этого модуля без указания пакета, либо выполнения модуля как скрипта данный шаг будет пропущен. Это отражено в документе www.python.org/dev/peps/pep-0302/#id23:
“The built-in __import__ function (known as PyImport_ImportModuleEx() in import.c) will then check to see whether the module doing the import is a package or a submodule of a package. If it is indeed a (submodule of a) package, it first tries to do the import relative to the package (the parent package for a submodule). For example if a package named «spam» does «import eggs», it will first look for a module named «spam.eggs». If that fails, the import continues as an absolute import: it will look for a module named «eggs».”

*** docs.python.org/2/tutorial/modules.html#the-module-search-path
“When a module named spam is imported, the interpreter first searches for a built-in module with that name.”

**** docs.python.org/2/tutorial/modules.html#the-module-search-path
“If not found, it then searches for a file named spam.py in a list of directories given by the variable sys.path.”

Алгоритм поиска имен для import abcd.defg



Сначала производится поиск пакета или модуля abcd в соответствии с алгоритмом, описанным для import abcd.

Если поиск успешный, то выполняется поиск пакета или модуля defg в соответствии со следующим алгоритмом:
Что ищем Где ищем* Комментарий
1 пакет defg в пакете abcd ссылка на документацию указана в **
2 модуль defg в пакете abcd ссылка на документацию указана в **

* В случае, если в результате поиска abcd, последний оказался модулем, то импорт закончится ошибкой ImportError: No module named defg, т.к. модуль не может содержать другие модули, либо пакеты:
docs.python.org/2/reference/simple_stmts.html#import: “A package can contain other packages and modules while modules cannot contain other modules or packages.”
** www.python.org/dev/peps/pep-0302/#id23
“Deeper down in the mechanism, a dotted name import is split up by its components. For «import spam.ham», first an «import spam» is done, and only when that succeeds is «ham» imported as a submodule of «spam».”

Алгоритм поиска имен для from abcd.defg import ghi



Сначала производится поиск пакета или модуля abcd.defg в соответствии с алгоритмом, описанным для import abcd.defg:
Что ищем Где ищем
1 имя ghi в пакете или модуле defg
2 пакет ghi в пакете defg
3 модуль ghi в пакете defg

Перекрытие имен



Хочу отметить одну интересную особенность, которая следует из последовательного применения перечисленных выше алгоритмов. Представьте следующую ситуацию: существует модуль с именем abcd и пакет с именем abcd, содержащий, в свою очередь, модуль defg, модуль abcd и пакет abcd размещены в разных каталогах, при этом модуль abcd размещен в том же пакете, что и модуль, в котором выполняется инструкция import abcd.defg. В этом случае выполнение импорта завершится с ошибкой. Это связано с тем, что интерпретатор Python сначала найдет модуль abcd, потом попытается искать в нем модуль defg, что невозможно.

Разумнее было бы определить из синтаксиса инструкции import, что abcd может быть только пакетом (т.к. все элементы до точки могут являться только пакетами) и искать abcd только как пакет. В этом случае пакет abcd был бы импортирован из другого каталога, а в нем был бы обнаружен модуль defg и выполнение программы продолжилось бы без ошибок.

К сожалению, такое поведение Python не реализовано. См. bugs.python.org/issue16891#msg179353.

Автор статьи столкнулся с данной проблемой, но в связи разразненностью описания в официальной документации Python потребовалось некорое время для выяснения причин такого поведения интерпретатора. В результате возникли следующие обсуждения stackoverflow.com/questions/14183541/why-python-finds-module-instead-of-package-if-they-have-the-same-name и bugs.python.org/issue16891, а также написана данная статья.

В случае возникновения подобных коллизий имен возможны следующие решения:
  1. Переименовать модуль, либо пакет таким образом, чтобы имя пакета не совпадало с именем модуля, т.е. исключить совпадение имен
  2. Иногда может помочь включение абсолютных путей по умолчанию для импорта с помощью инструкции from __future__ import absolute_import (на самом деле в этом случае, лишь повышается контроль за последовательностью поиска пакетов и модулей путем внесения изменений в sys.path)


Приложение: исходный код


В этом репозитории находится исходный код, демонстрирующий описанные выше алгоритмы: bitbucket.org/dmugtasimov/python_import.
Дмитрий Мугтасимов @dmugtasimov
карма
13,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (14)

  • 0
    Интересно, никогда не сталкивался с перекрытием имён.
  • +2
    Дальше вопрос логически следует — а как избежать перекрытия имен?

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

    Ну т.е. так вопрос решить можно, и его решают: например, если в какой-нибудь django-библиотеке foo шаблонные теги нужно загружать как {% load "foo_tags" %}, то вероятность 92.64%, что боролись с семантикой импортов (либо это cargo-cult, оставшийся от какой-то библиотеки, боровшейся с семантикой импортов).

    Мне кажется, что все-же лучше проблему решить тем, что всегда писать

    from __future__ import absolute_imports
    

    в коде на 2.х (например, засунув эту строчку в шаблон для .py файла в IDE) и/или переходить на Python 3.x, где этот алгоритм импортирования уже по умолчанию.

    Этот future-импорт убивает двух зайцев: код по семантике импортов становится как 3.х (а значит переход будет проще), и устраняется возможность сломать питоний код, просто создав рядом в папочке файл с неудачным именем. Кроме того, явное ведь лучше неявного — с absolute_imports относительные импорты нужно указывать явно.

    С другими future-импортами есть проблема — их нужно писать сверху файла, и когда читаешь код, чтоб его правильно понять, нужно держать в уме, какие future-импорты действуют. С absolute_imports этой проблемы нет, т.к. во-первых, он влияет на импорты (а их тоже пишут сверху файла), а во-вторых, он меняет поведение, котрое в большинстве случаев и так было неявным или ошибочным.
    • 0
      Спасибо огромное за комментарий. Он помог обнаружить, что часть статьи оказалась неопубликованной в результате ошибки во время форматирования. А именно вот это:
      В случае возникновения подобных коллизий имен возможны следующие решения:
      1. Переименовать модуль, либо пакет таким образом, чтобы имя пакета не совпадало с именем модуля, т.е. исключить совпадение имен
      2. Иногда может помочь включение абсолютных путей по умолчанию для импорта с помощью инструкции from __future__ import absolute_import (на самом деле в этом случае, лишь повышается контроль за последовательностью поиска пакетов и модулей путем внесения изменений в sys.path)

      • 0
        Можно играть с sys.path перед импортами.
        • 0
          Сегодня потратил пару часов на подобную проблему, надо было читать хабру раньше :(

          За статью спасибо!
  • +1
    Еще добавлю про импорты.
    Неоднозначное поведение когда вызывается код из модуля из пэкэджа, в котором вызывается сабпэкэдж с модулями.
    Как оказалось функция __import__ не выполняет магию и путь не добавляется в paths. Долго промучался, потом увидел, что во всех сабмодулях импорт идет напрямую и работает, заменил на путь от корня программы (был например пэкэдж admin внутри модуль show внутри этого модуля, для загрузки admin.submodules надо было писать именно так, а не как ожидаемо (просто submodules)).
    Структура
    plugins.py  - файл_с__import__
    admin/__init__.py - ясно
    admin/show.py - файл с импортом плагинов
    admin/submodules/__init__.py - пэкэдж
    admin/submodules/billing.py  - файл что надо импортнуть
    
    • 0
      Выходило примерно так
      plugins.py
      for i in ['admin.show','test.test']:
       __import__(i)
      


      admin/show.py
      import submodules
      for i in submodules.__all__:
       __import__('submodules.'+i)
      

      Результат был что-то вроде ImportError No module named billing
      • 0
        Не мучайте __import__, используйте docs.python.org/2/library/importlib.html
        Вопросов станет поменьше
        • 0
          Importlib тоже подвержен этой проблеме.
          • 0
            Параметр package вниманием не обошли?
            • 0
              И так и так пробовал, нужно прописывать точно путь, тогда работает.
              • 0
                >>> import ctypes
                >>> ctypes.__package__
                'ctypes'
                >>> import importlib
                >>> m = importlib.import_module('util', package=ctypes.__package__)
                Traceback (most recent call last):
                File "", line 1, in File "/usr/lib/python2.7/importlib/__init__.py", line 37, in import_module
                __import__(name)
                ImportError: No module named util
                >>> m = importlib.import_module('.util', package=ctypes.__package__)
                >>> m.__name__
                'ctypes.util'
                >>>
  • +2
    Отличная статья, но для полноты было бы неплохо добавить про PYTHONHOME, PYTHONPATH и модули в зипах.

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