Помещение отдельных пакетов Python в одно и то же пространство имен?

Я разрабатываю фреймворк Python, в котором «надстройки» будут написаны как отдельные пакеты. То есть:

import myframework
from myframework.addons import foo, bar

Теперь я пытаюсь организовать так, чтобы эти дополнения можно было распространять отдельно от основной структуры и вводить в пространство имен myframework.addons.

На данный момент мое лучшее решение - следующее. Надстройка будет развернута (скорее всего, в {python_version}/site-packages/ вот так:

fooext/
fooext/__init__.py
fooext/myframework/
fooext/myframework/__init__.py
fooext/myframework/addons/
fooext/myframework/addons/__init__.py
fooext/myframework/addons/foo.py

fooext/myframework/addons/__init__.py будет иметь код расширения пути pkgutil:

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

Проблема в том, что для того, чтобы это работало, в PYTHONPATH должен быть fooext/, однако единственное, что он должен иметь, - это родительский установочный каталог (скорее всего, упомянутый выше site-packages).

Решение этой проблемы - иметь дополнительный код в myframework/addons/__init__.py, который будет проходить через sys.path и искать любые модули с подпакетом myframework, и в этом случае он добавляет его в sys.path, и все работает.

Другая идея, которая у меня возникла, - это записать файлы аддона непосредственно в место установки myframework/addons/, но тогда это приведет к различию между разработкой и развернутым пространством имен.

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

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
17
0
8 036
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Похоже, что то, что вам нужно, можно довольно аккуратно выполнить с помощью хуков импорта.

Это способ написания пользовательского кода загрузки, который может быть связан с пакетом (или в вашем случае с фреймворком) для выполнения загрузки всех подпакетов и модулей, а не с использованием механизма загрузки по умолчанию в Python. Затем вы можете установить загрузчик в site-packages как базовый пакет или под свой фреймворк.

Когда обнаруживается, что пакет связан с загрузчиком (который при необходимости может быть просто жестко задан относительным путем), он всегда будет использовать загрузчик, например, для загрузки всех надстроек. Это имеет то преимущество, что не требуется возиться с PYTHONPATH, что обычно стоит делать как можно короче.

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

Более подробную информацию о перехватчиках импорта можно найти здесь:

http://www.python.org/dev/peps/pep-0302/

Ответ принят как подходящий

Is there a better way to accomplish this or perhaps a different approach to the above distribution problem altogether?

Возможно. Настройку модуля / пакета Python обычно сложно изменить динамически, как это, но его система объектов / классов открыта и расширяема четко определенным образом. Когда модули и пакеты не обладают функциями, необходимыми для красивой инкапсуляции вашего проекта, вы можете вместо этого использовать классы.

Например, вы могли бы иметь функциональные возможности расширения в совершенно другом пакете, но позволить ему внедрять классы в вашу базовую структуру через определенный интерфейс. например. myframework / _ _ init _ _.py, содержащий базовую оболочку приложения:

class MyFramework(object):
    """A bare MyFramework, I only hold a person's name
    """
    _addons= {}
    @staticmethod
    def addAddon(name, addon):
        MyFramework._addons[name]= addon

    def __init__(self, person):
        self.person= person
        for name, addon in MyFramework._addons.items():
            setattr(self, name, addon(self))

Тогда у вас может быть функция расширения в myexts / helloer.py, которая хранит ссылку на своего «владельца» или «внешний» экземпляр класса MyFramework:

class Helloer(object):
    def __init__(self, owner):
        self.owner= owner
    def hello(self):
        print 'hello '+self.owner.person

import myframework
myframework.MyFramework.addAddon('helloer', Helloer)

Так что теперь, если вы просто «импортируете myframework», вы получите только базовую функциональность. Но если вы также «импортируете myexts.helloer», вы также получаете возможность вызывать MyFramework.helloer.hello (). Естественно, вы также можете определить протоколы для аддонов для взаимодействия с базовым поведением фреймворка и друг с другом. Вы также можете делать такие вещи, как внутренние классы, которые подкласс фреймворка может переопределить для настройки без необходимости исправлять классы, которые могут повлиять на другие приложения, если вам нужен такой уровень сложности.

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

См. Ответ Алека ниже. В Setuptools есть что-то именно для этого, называемое entry_points.

Jason Antman 29.12.2014 01:33

Setuptools имеет возможность искать «точки входа» (функции, объекты и т. д.) Пакета по имени. Trac использует этот механизм для загрузить его плагины, и он хорошо работает.

Я бы проголосовал за рекомендацию Алека выше всех остальных. точки входа были разработаны именно для этого, и в документации есть хороший пример динамической загрузки плагина: pythonhosted.org/setuptools/…

Jason Antman 29.12.2014 01:32

См. Пакеты пространства имен:

http://www.python.org/dev/peps/pep-0382/

или в setuptools:

http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages

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

Другие вопросы по теме