Я разрабатываю фреймворк 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. Затем вы можете установить загрузчик в site-packages как базовый пакет или под свой фреймворк.
Когда обнаруживается, что пакет связан с загрузчиком (который при необходимости может быть просто жестко задан относительным путем), он всегда будет использовать загрузчик, например, для загрузки всех надстроек. Это имеет то преимущество, что не требуется возиться с PYTHONPATH, что обычно стоит делать как можно короче.
Альтернативой этому является использование файлов в этом для перенаправления вызова импорта для подмодуля на тот, который вы хотите, чтобы он принял, но это немного беспорядочно.
Более подробную информацию о перехватчиках импорта можно найти здесь:
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 имеет возможность искать «точки входа» (функции, объекты и т. д.) Пакета по имени. Trac использует этот механизм для загрузить его плагины, и он хорошо работает.
Я бы проголосовал за рекомендацию Алека выше всех остальных. точки входа были разработаны именно для этого, и в документации есть хороший пример динамической загрузки плагина: pythonhosted.org/setuptools/…
См. Пакеты пространства имен:
http://www.python.org/dev/peps/pep-0382/
или в setuptools:
http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
Существует совершенно новая настройка пространств имен. Взгляните на Упаковка пакетов пространства имен. Короче говоря, у вас есть три варианта, в зависимости от того, насколько обратной совместимостью вы хотите, чтобы ваш код был. Существует также соответствующий PEP, который заменяет упомянутые в других ответах: PEP 420.
См. Ответ Алека ниже. В Setuptools есть что-то именно для этого, называемое entry_points.