我希望能够导入一个实际上位于另一个模块子目录中的 python 模块。
我正在开发一个带有插件的框架。由于我希望有几千个(目前已经有 >250 个)并且我不想要一个包含 >1000 个文件的大目录,我将它们排序在这样的目录中,它们按名称的第一个字母分组:
framework\
__init__.py
framework.py
tools.py
plugins\
__init__.py
a\
__init__.py
atlas.py
...
b\
__init__.py
binary.py
...
c\
__init__.py
cmake.py
...
Run Code Online (Sandbox Code Playgroud)
由于我不想给其他插件的开发人员或不需要那么多插件的人带来负担,我想将每个插件放在 'framework.plugins' 命名空间中。这样,添加一堆私有插件的人可以通过将它们添加到文件夹 framework.plugins 中来实现,并提供一个__init__.py包含以下内容的文件:
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
Run Code Online (Sandbox Code Playgroud)
然而,目前这个设置迫使他们也使用 az 子目录。有时一个插件会扩展另一个插件,所以现在我有一个
from framework.plugins.a import atlas
Run Code Online (Sandbox Code Playgroud)
我想要
from framework.pugins import atlas
Run Code Online (Sandbox Code Playgroud)
有什么方法可以声明一个命名空间,其中完整的命名空间名称实际上没有映射到文件夹结构?
我知道 pkg_resources 包,但这只能通过 setuptools 获得,我不想有额外的依赖。
import pkg_resources
pkg_resources.declare_namespace(__name__)
Run Code Online (Sandbox Code Playgroud)
该解决方案应该适用于 python 2.4-2.7.3
更新:结合提供的答案,我尝试获取__init__.py从插件中导入的所有插件的列表。但是,这由于依赖关系而失败。由于 'c' 文件夹中的插件尝试导入以 't' 开头的插件,而这个插件尚未添加。
plugins = [ x[0].find_module(x[1]).load_module(x[1]) for x in pkgutil.walk_packages([ os.path.join(framework.plugins.__path__[0], chr(y)) for y in xrange(ord('a'), ord('z') + 1) ],'framework.plugins.' ) ]
Run Code Online (Sandbox Code Playgroud)
我不确定我是否在正确的轨道上,或者只是让事情过于复杂,最好编写我自己的 PEP302 导入器。但是,我似乎找不到任何关于这些应该如何工作的体面例子。
更新:我试图按照__getattr__在 my中包装函数的建议进行操作__init__.py,但这似乎无济于事。
import pkgutil
import os
import sys
plugins = [x[1] for x in pkgutil.walk_packages([ os.path.join(__path__[0], chr(y)) for y in xrange(ord('a'), ord('z') + 1) ] )]
import types
class MyWrapper(types.ModuleType):
def __init__(self, wrapped):
self.wrapped = wrapped
def __getattr__(self, name):
if name in plugins:
askedattr = name[0] + '.' + name
else:
askedattr = name
attr = getattr(self.wrapped, askedattr)
return attr
sys.modules[__name__] = MyWrapper(sys.modules[__name__])
Run Code Online (Sandbox Code Playgroud)
不要pkgutil.extend_path在此处使用该函数,它会尝试执行与您想要完成的操作相反的操作:
\n\n\n这会将 sys.path 上以包命名的目录的所有子目录添加到包\xe2\x80\x99s 中
\n__path__。\n 如果想要将单个逻辑包的不同部分分发为多个目录,这会很有用。
只需扩展__path__您作品中的子目录framework.plugins.__init__.py就可以了。
所以这个问题的解决方案是:将其放入您的__init__.py:
__path__.extend([os.path.join(__path__[0],chr(y)) for y in range(ord('a'),ord('z')+1)])\nRun Code Online (Sandbox Code Playgroud)\n