将单独的python包放入相同的命名空间?

时间:2009-01-18 05:17:19

标签: python namespaces distribution extend

我正在开发一个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/安装位置,但这会使开发和部署的命名空间不同。

是否有更好的方法可以完成此操作,或者可能采用不同的方法完成上述分发问题?

5 个答案:

答案 0 :(得分:6)

答案 1 :(得分: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()。当然,您还可以为插件定义协议,以便与基本框架行为相互作用。您还可以执行内部类的操作,如框架的子类可以覆盖以进行自定义,而不必使用可能影响其他应用程序的monkey-patch类,如果您需要这种复杂程度。

这样的封装行为可能很有用,但通常很烦人的工作是调整你已经适合这个模型的模块级代码。

答案 2 :(得分:4)

Setuptools能够按名称查找包“入口点”(函数,对象等)。 Trac将此机制用于load its plugins,并且效果很好。

答案 3 :(得分:1)

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

答案 4 :(得分:0)

听起来你可以用导入钩子完成整齐的工作。

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

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

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

有关导入挂钩的更多信息,请访问:

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