imp.find_module的替代方案有哪些?
背景
我对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 个回答
警告 + 免责声明:还没有测试过!
之前的代码:
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
自从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上的模块;有关详细信息,请查看上面链接的文档。
我对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__
不过,这个方法并不是很完美,只能处理顶层的命名空间包。