【问题标题】:Putting separate python packages into same namespace?将单独的python包放入同一个命名空间?
【发布时间】:2010-10-02 01:35:32
【问题描述】:

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

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

【问题讨论】:

    标签: python namespaces distribution extend


    【解决方案1】:
    【解决方案2】:

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

    可能。 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。
    【解决方案3】:

    Setuptools 能够按名称查找包“入口点”(函数、对象等)。 Trac使用这个机制来load its plugins,效果很好。

    【讨论】:

    • 我会投票支持 Alec 的建议,而不是其他任何人。入口点正是为此而设计的,文档中有一个很好的动态插件加载示例:pythonhosted.org/setuptools/…
    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

      听起来你所追求的可以用导入钩子很好地完成。

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

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

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

      更多关于导入钩子的信息可以在这里找到:

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

      【讨论】:

        猜你喜欢
        • 2019-04-20
        • 2018-01-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-12
        • 1970-01-01
        • 2023-03-15
        • 1970-01-01
        相关资源
        最近更新 更多