Шаг 187.
Python: сборник рецептов. Модули и пакеты. Создание отдельных каталогов с кодом для импорта под общим пространством имен

    На этом шаге мы рассмотрим способ решения указанной задачи.

Задача

    У вас большая кодовая база с частями, которые могут поддерживаться и распространяться разными людьми. Каждая часть организована в виде каталога файлов, как пакет. Однако, вместо того чтобы устанавливать каждую часть отдельно, как пакет с отдельным именем, вы хотели бы объединить все части под общим префиксом пакета.

Решение

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

    Чтобы объединить отдельные каталоги под общим пространством имен, вы должны организовать код так же, как и в обычном пакете Python, но опустить файлы __init__.py в каталогах, где компоненты будут объединяться. Предположим, у вас есть два различных каталога с кодом Python:

foo-package/
    spam/
        blah.py

bar-package/
    spam/
        grok.py

    В этих каталогах имя spam используется в качестве общего пространства имен. Обратите внимание, что файл __init__.py отсутствует в обоих каталогах.

    Теперь посмотрим, что будет, если вы добавите оба пакета foo-package и bar-package к пути поиска модулей Python и попробуете импортировать:

>>> import sys
>>> sys.path.extend(['foo-package', 'bar-package'])
>>> import spam.blah 
>>> import spam.grok 
>>>

    Волшебным образом два разных каталога пакетов слились вместе, и вы можете импортировать либо spam.blah, либо spam.grok. Все работает.

Обсуждение

    Механизм, который здесь работает, известен под названием "пакет пространства имен". По сути, пакет пространства имен - это специальный пакет, разработанный для слияния различных каталогов с кодом под общим пространством имен. Для крупных фреймворков это может быть весьма полезно, поскольку позволяет разбить части фреймворка на отдельно устанавливаемые скачиваемые файлы. Это также позволяет людям проще делать сторонние дополнения и другие расширения для фреймворков.

    Ключ к созданию пакета пространства имен - отсутствие файлов __init__.py в каталоге высшего уровня, который служит общим пространством имен. Отсутствие __init__.py вызывает интересный эффект при импортировании пакета. Вместо того чтобы выкинуть ошибку, интерпретатор начинает создавать список всех каталогов, которые содержат совпадающее имя пакета. Затем создается специальный модуль-пакет пространства имен, и в его переменной __path__ сохраняется доступная только для чтения копия списка каталогов. Например:

>>> import spam 
>>> spam.__path__
_NamespacePath(['foo-package/spam', 'bar-package/spam'])
>>>

    Каталоги из __path__ используются при определении следующих подкомпонентов пакета (например, при импортировании spam.grok или spam.blah).

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

my-package/
    spam/
        custom.py

    Если вы добавите ваш каталог в sys.path вместе с другими пакетами, он бесшовно сольется с другими каталогами пакета spam:

>>> import spam.custom 
>>> import spam.grok 
>>> import spam.blah 
>>>

    Для целей отладки важно знать, что основной способ сказать, является ли пакет пакетом пространства имен, - это проверить его атрибут __file__. Если он отсутствует, пакет является пространством имен. Это также будет отражено в строковом представлении по слову "namespace":

>>> spam.__file__	
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute '__file__'
>>> spam
<module 'spam' (namespace)>
>>>

    Дополнительную информацию о пакетах пространств имен вы можете найти в PEP 420 https://peps.python.org/pep-0420/.

    На следующем шаге мы рассмотрим перезагрузку модулей.




Предыдущий шаг Содержание