imp.find_module的替代方案有哪些?

5 投票
3 回答
2444 浏览
提问于 2025-04-16 16:09

背景

我对pylint在使用命名空间包时无法导入文件的问题感到厌烦,因为这会把我的代码分散到不同的文件夹里。于是我开始研究astNG的源代码,发现这就是问题的根源(可以查看bug报告8796)。问题的核心似乎是Python自己用的imp.find_module在寻找导入时出现了问题。

具体来说,当你尝试导入一个模块,比如import a.b.c时,首先会把a这个子包传给find_module,而路径则传入None。接下来,find_module返回的路径会在查找循环中用于寻找下一个模块b

以下是来自logilab.common.modutils的伪代码:

path = None
while import_as_list:
      try:
           _, found_path, etc = find_module(import_as_list[0], path)
      #exception handling and checking for a better version in the .egg files
      path = [found_path]
      import_as_list.pop(0)

问题

问题在于:你只能从find_module中得到第一个最好的结果,但这个结果可能包含也可能不包含你的子包。如果没有找到子包,你就没有办法退回去尝试下一个。

我尝试明确使用sys.path而不是None,这样可以把结果从路径列表中移除,然后再尝试一次,但Python的模块查找器足够聪明,路径不需要完全匹配,这让我的方法变得不可行——至少在我所知的范围内是这样。

恳求帮助

有没有其他方法可以替代find_modules,能返回所有可能的匹配项,或者可以提供一个排除列表?我也愿意尝试完全不同的解决方案。最好不要手动修补Python,但如果是本地解决方案,那也不是不可能。

(注意:我正在使用Python 2.6,由于公司政策原因无法升级,所以关于Python 3.x的建议不会被标记为接受,除非这是唯一的答案。)

3 个回答

0

警告 + 免责声明:还没有测试过!

之前的代码:

for part in parts:
    modpath.append(part)
    curname = '.'.join(modpath)
    # ...
    if module is None:
        mp_file, mp_filename, mp_desc = imp.find_module(part, path)
        module = imp.load_module(curname, mp_file, mp_filename, mp_desc)

之后的代码: - 感谢 pjeby 提到的 pkgutil

for part in parts:
    modpath.append(part)
    curname = '.'.join(modpath)
    # ...
    if module is None:
        # + https://stackoverflow.com/a/14820895/611007
        # # mp_file, mp_filename, mp_desc = imp.find_module(part, path)
        # # module = imp.load_module(curname, mp_file, mp_filename, mp_desc)
        import pkgutil
        mp_file = None
        for loadr,name,ispkg in pkgutil.iter_modules(path=path,prefix='.'.join(modpath[:-1])+'.'):
            if name.split('.')[-1] == part:
                if not hasattr(loadr,'path') and hasattr(loadr,'archive'):
                    # with zips `name` was like '.somemodule'
                    # it gives `RuntimeWarning: Parent module '' not found while handling absolute import`
                    # I expect the name I need to be 'somemodule'
                    # TODO: I don't know why python does this or what the correct usage is.
                    # https://stackoverflow.com/questions/2267984/
                    if name and name[0] == '.':
                        name = name[1:]
                    ldr= loadr.find_module(name,loadr.archive)
                    module = ldr.load_module(name)
                    break
                imploader= loadr.find_module(name,loadr.path)
                mp_file,mp_filename,mp_desc= imploader.file,imploader.filename,imploader.etc
                module = imploader.load_module(imploader.fullname)
                break
        if module is None:
            raise ImportError
4

自从Python 2.5开始,正确的方法是使用 pkgutil.iter_modules()(用于获取一个简单的模块列表)或者 pkgutil.walk_packages()(用于获取子包的树状结构)。这两种方法都可以很好地与命名空间包配合使用。

举个例子,如果我想找到'jmb'的子包或子模块,我可以这样做:

import jmb, pkgutil
for (module_loader, name, ispkg) in pkgutil.iter_modules(jmb.__path__, 'jmb.'):
    # 'name' will be 'jmb.foo', 'jmb.bar', etc.
    # 'ispkg' will be true if 'jmb.foo' is a package, false if it's a module

你也可以使用iter_modules或walk_packages来遍历 所有 在sys.path上的模块;有关详细信息,请查看上面链接的文档。

2

我对PyLint的这个限制感到有些厌烦。

我不知道有什么可以替代imp.find_modules()的东西,但我想我找到了一种处理PyLint中命名空间包的新方法。你可以看看我在你提到的bug报告上的评论(http://www.logilab.org/ticket/8796)。

我的想法是使用pkg_resources来查找命名空间包。以下是我对logilab.common.modutils._module_file()的补充,放在while modpath之后:

  while modpath:
      if modpath[0] in pkg_resources._namespace_packages and len(modpath) > 1:
          module = sys.modules[modpath.pop(0)]
          path = module.__path__

不过,这个方法并不是很完美,只能处理顶层的命名空间包。

撰写回答