我正在开发一个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/
安装位置,但这会使开发和部署的命名空间不同。
是否有更好的方法可以完成此操作,或者可能采用不同的方法完成上述分发问题?
答案 0 :(得分:6)
请参阅命名空间包:
http://www.python.org/dev/peps/pep-0382/
或在setuptools中:
http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
答案 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 文件将子模块的导入调用重定向到您希望它接收的子模块,但这有点乱。
有关导入挂钩的更多信息,请访问: