19

我正在开发一个 python 框架,它将“插件”编写为单独的包。IE:

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具有 pkg​​util 路径扩展代码:

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

问题是,要让它工作,PYTHONPATH 需要fooext/在其中,但是它唯一的东西是父安装目录(很可能是上面提到的site-packages)。

对此的解决方案是有额外的代码在myframework/addons/__init__.py其中遍历sys.path并查找带有 myframework 子包的任何模块,在这种情况下,它将它添加到sys.path并且一切正常。

我的另一个想法是直接将插件文件写入myframework/addons/安装位置,但这会使开发和部署的命名空间有所不同。

有没有更好的方法来实现这一点,或者完全可以用不同的方法来解决上述分布问题?

4

5 回答 5

7

查看命名空间包:

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

或在 setuptools 中:

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

于 2009-11-12T02:27:51.733 回答
4

有没有更好的方法来实现这一点,或者完全可以用不同的方法来解决上述分布问题?

可能。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()。当然,您还可以为插件定义协议以与基本框架行为以及彼此交互。如果您需要这种复杂程度,您还可以执行诸如框架的子类可以覆盖的内部类之类的事情以进行自定义,而不必对可能影响其他应用程序的类进行猴子补丁。

封装这样的行为可能很有用,但调整您已经必须适合此模型的模块级代码通常是令人讨厌的工作。

于 2009-01-18T15:01:30.703 回答
4

Setuptools 能够按名称查找包“入口点”(函数、对象等)。Trac 使用这种机制来加载它的插件,并且运行良好。

于 2009-01-19T05:35:28.273 回答
2

命名空间有一个全新的设置。看看Packaging namespace packages。简而言之,您有三个选项,具体取决于您希望代码向后兼容的程度。还有一个相关的 PEP,它取代了其他答案中提到的那些:PEP 420

于 2017-10-24T18:23:01.177 回答
0

听起来你所追求的可以通过导入钩子巧妙地完成。

这是一种编写自定义加载代码的方式,它可以与一个包(或者在你的情况下是一个框架)相关联,以执行所有子包和模块的加载,而不是使用 python 的默认加载机制。然后,您可以将加载程序作为基础包安装在站点包中或在您的框架下。

当发现一个包与加载器相关联时(如果需要,可以简单地将其硬编码到相对路径),然后它将始终使用加载器来加载所有附加组件。这样做的好处是不需要对 PYTHONPATH 进行任何摆弄,这通常值得保持尽可能短。

另一种方法是使用init文件将子模块的导入调用重定向到您希望它接收的那个,但这有点混乱。

可以在此处找到有关导入挂钩的更多信息:

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

于 2009-01-18T12:06:18.770 回答