在CLI中实现重新加载功能时,我偶然发现了这个奇怪的错误,我似乎无法解决。要重新创建它,首先,在与 CWD 不同的目录中创建一个虚拟文件。对于此示例,我将其称为test.py
。
使用此处的配方,以编程方式导入它,但请确保 CWD 是独立的,并且无法使用 CWD 作为"茎"到达test.py
路径。
spec = importlib.util.spec_from_file_location('test', <path>)
module = importlib.util.module_from_spec(spec)
sys.modules['test'] = module
spec.loader.exec_module(module)
尝试使用importlib.reload
重新加载模块时发生错误:
>>> importlib.reload(module)
File "C:Program FilesPython38libimportlib__init__.py", line 168, in reload
raise ModuleNotFoundError(f"spec not found for the module {name!r}", name=name)
ModuleNotFoundError: spec not found for the module 'test'
我对importlib.__init__.reload
进行了一些检查,这似乎叫做_frozen_importlib._bootstrap._find_spec
.引发此错误的原因是未找到模块规范,但在重新加载之前存在属性__spec__
。由于代码的性质,规范设置为None
:
spec = module.__spec__ = _bootstrap._find_spec(name, pkgpath, target)
if spec is None:
raise ModuleNotFoundError(f"spec not found for the module {name!r}", name=name)
有没有办法重新加载以编程方式导入的模块?我确实需要使用文件路径保留导入的文件,因为它所在的程序允许用户编写可以放置在文件系统中任何位置的"插件"。这是我的代码导入机制中主要发生的事情的简化版本。
使用适当最新版本的 python (>=3.4(,您可以在sys.meta_path
中添加一个MetaPathFinder
,以告诉 python 在哪里可以找到模块规范。 如果这样做,则可以避免显式模块加载配方,并且importlib.reload
应该正常工作。 例:
class ModuleFinder(importlib.abc.MetaPathFinder):
def __init__(self, path_map:dict):
self.path_map = path_map
def find_spec(self, fullname, path, target=None):
if not fullname in self.path_map:
return None
return importlib.util.spec_from_file_location(fullname, self.path_map[fullname])
def find_module(self, fullname, path):
return None # No need to implement, backward compatibility only
sys.meta_path.append(ModuleFinder(my_module_paths))
其中my_module_paths
是一些字典将模块名称映射到位置。
有关更多详细信息:https://docs.python.org/3/library/importlib.html#setting-up-an-importer (实际上,就在您找到食谱的位置下方(
我想澄清一下情况。
发生的情况是importlib.reload(module)
呼叫_bootstrap._find_spec(name, pkgpath, target)
. 引导程序 导入库的一部分是一个冻结的模块,所以,除非我们运行 Python 从源代码来看,我们不能使用调试器单步执行它。 我们能够 但是,要逐步完成reload()
并执行失败的呼叫。 请参阅c:/my/python/lib/importlib/__init__.py(166)reload()
和c:/my/python/Lib/importlib/_bootstrap.py(890)_find_spec()
.
比较从reload()
传递到_find_spec()
的输入,我们 看到传入的name
是"测试",path
是pkgpath
是None
,target
是test
的模块对象。name
在sys.modules
中找到,所以is_reload
为真。 然后 对于meta_path
中的每个查找器,其find_spec()
函数为 叫。具体来说,它的调用与path
对应于pkgpath
且等于 None。 因此,规范始终None
。pkgpath
最终由包装s parent. The module
名称is simply
测试"决定,并且没有 父母。 由于测试模块位于当前工作之外 目录,这将始终失败。
当我们将模块查找器添加到meta_path时,它的find_spec得到 在_bootstrap._find_spec(name, pkgpath, target)
行中调用。 然后,它调用importlib.util.spec_from_file_location
这是模块第一次加载的方式。