Объединение Нескольких Пакетов В Одно Пространство Имен Python

Иногда возникает необходимость разделить несколько пакетов, расположенных в одном пространстве имен по разным физическим путям.

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

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



Объединение нескольких пакетов в одно пространство имен Python

Рассмотрим этот пример: Мы хотим получить структуру пакета:

  
  
  
  
  
  
  
  
  
   

namespace1 package1 module1 package2 module2

Содержимое файла mod1

print('package 1') var1 = 1

Содержимое файла Module2

print('package 2') var2 = 2

Пакеты распространяются в следующей структуре папок:

path1 namespace1 package1 module1 path2 namespace1 package2 module2

Предположим, что пути path1 и path2 каким-то образом уже добавлены в sys.path. Нам нужен доступ к модулю1 и модулю2:

from namespace1.package1 import module1 from namespace1.package2 import module2

Что произойдет в Питон 3.7 при выполнении этого кода? Все работает чудесно:

package 1 package 2

В PEP-420 в Python 3.3 появилась поддержка неявных пространств имен.

Кроме того, при импорте пакета из версии py33 вам не нужно создавать файлы __init__.py. А при импорте пространства имен это просто _запрещено_. Если файл __init__.py существует в одном или обоих каталогах и пространстве имен 1, при импорте второго пакета произойдет ошибка.



ModuleNotFoundError: No module named 'namespace1.package2'

Таким образом, наличие инициализатора четко определяет пакет, и пакеты нельзя объединять, это единая сущность.

Если вы начинаете новый проект и пакеты, которые не зависят от старых разработок и будут установлены с помощью pip, вам следует следовать этому методу.

Однако иногда мы наследуем старый код, который тоже необходимо поддерживать, хотя бы какое-то время, или переводить в новую версию.

Давайте перейдем к Питон 2.7 .

С этой версией уже интереснее, нужно сначала добавить __init__.py в каждую директорию для создания пакетов, иначе интерпретатор просто не распознает пакет в этом наборе файлов.

Затем напишите явное объявление пространства имен в файлах __init__, относящихся к пространству имен 1, иначе будет импортирован только первый пакет.

from pkgutil import extend_path __path__ = extend_path(__path__, __name__)

Что происходит? Когда интерпретатор достигает первого импорта, он ищет в sys.path пакет с таким именем, который находится в пути1/пространстве имен1, и интерпретатор выполняет путь1/пространство имен1/__init__.py. Дальнейшего поиска нет. Однако сама функция расширения_path просматривает весь sys.path, находит все пакеты с именем namespace1 и инициализатором и добавляет их в переменную __path__ пакета namespace1, которая используется для поиска дочерних пакетов в этом пространстве имен.

Официальные руководства рекомендуют, чтобы инициалы были одинаковыми при каждом размещении пространства имен1. На самом деле все они могут быть пустыми, кроме первого, который находится при поиске в sys.path, в котором должен быть вызов pkgutil.extend_path, поскольку остальные не выполняются.

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

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

Если в версии 3 вы поместите в некоторые пакеты исходный файл, вызвав pkgutil.extend_path, а некоторые оставите без исходного файла, то это не сработает. Кроме того, этот вариант подойдет и для случая, когда вы планируете установку с помощью python setup.py install. Еще один метод, который сейчас считается несколько устаревшим, но все еще можно найти во многих местах:

#namespace1/__init__.py __import__('pkg_resources').

declare_namespace(__name__)

Модуль pkg_resources поставляется с пакетом setuptools. Смысл здесь тот же, что и в pkgutil — каждый файл __init__ должен содержать одно и то же объявление пространства имен и никакого другого кода для каждого размещения пространства имен1. В этом случае пространство имен namespace_packages=['namespace1'] должно быть зарегистрировано в setup.py. Более подробное описание создания пакетов выходит за рамки данной статьи.

Кроме того, часто можно встретить такой код

try:

Теги: #python #программирование #python3 #python2 #python2

Вместе с данным постом часто просматривают: