5

我希望能够导入一个实际上位于另一个模块的子目录中的 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
        ...

由于我不想给其他插件的开发人员或不需要像我一样多的人带来负担,我想将每个插件放在“framework.plugins”命名空间中。这样,添加一堆私有插件的人只需将它们添加到文件夹 framework.plugins 中即可,并提供一个包含以下内容的__init__.py文件:

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

然而,目前这种设置迫使他们也使用 az 子目录。有时一个插件正在扩展另一个插件,所以现在我有一个

from framework.plugins.a import atlas

我想拥有

from framework.pugins import atlas

有没有办法声明一个名称空间,其中完整的名称空间名称实际上不映射到文件夹结构?

我知道 pkg_resources 包,但这只能通过 setuptools 获得,我宁愿没有额外的依赖。

import pkg_resources
pkg_resources.declare_namespace(__name__)

该解决方案应适用于 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.' ) ]

我不确定我是否在正确的轨道上,或者只是让事情变得过于复杂并更好地编写我自己的 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__])
4

4 回答 4

1

一个简单的解决方案是将您的a, b... 模块导入plugins.__init__,例如:

from a import atlas
from b import binary
...
于 2012-08-20T15:53:43.197 回答
1

我建议通过子类化和按需types.ModuleType转换modname来延迟加载模块;modname[0] + '.' + modname这应该比实现模块加载器少。

您可以查看aipkg以了解如何执行此操作的示例。

于 2012-08-21T09:19:57.800 回答
1

Don't use the pkgutil.extend_path function here, it tries to do the opposite of what you're trying to accomplish:

This will add to the package’s __path__ all subdirectories of directories on sys.path named after the package. This is useful if one wants to distribute different parts of a single logical package as multiple directories.

Just extending __path__ with the subdirectories in your framework.plugins.__init__.py works just fine.

So the solution to this problem is: put this in your __init__.py:

__path__.extend([os.path.join(__path__[0],chr(y)) for y in range(ord('a'),ord('z')+1)])
于 2012-08-21T16:10:14.057 回答
0

这不是一个特别快速的解决方案(启动开销),但是让 plugins.__init__ 抓取文件系统并将每个找到的文件导入本地命名空间呢?

import glob
import sys

thismodule = sys.modules[__name__]

for plugin in glob.glob("?/*"):
    _temp = __import__(plugin.split("/")[0],
                   globals(),
                   locals(),
                   [plugin.split("/")[1]],
                   -1)
   setattr(thismodule, plugin.split("/")[1], getattr(_temp, plugin.split("/")[1]))
于 2012-08-20T16:26:04.237 回答