为什么一个缺失但并非必需的父级会导致动态模块重新加载失败?
当我在动态导入上使用 importlib.reload 时,出现了一个错误。动态导入成功了,但紧接着的 importlib.reload() 失败,提示没有父包。我到底哪里做错了?
我使用的是Python 3.12.12。
命令提示符:
C:\tmp\bugs\python\importhell> python hmm.py test_in phobia.agoraphobia QQ
Run 0
Importing phobia.agoraphobia from test_in\phobia/agoraphobia.py
QQ from phobia.agoraphobia = 12345
Run 1
Traceback (most recent call last):
File "C:\tmp\bugs\python\importhell\hmm.py", line 32, in <module>
doit(args)
File "C:\tmp\bugs\python\importhell\hmm.py", line 22, in doit
module = get_module(args.module, args.rootdir)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\tmp\bugs\python\importhell\hmm.py", line 16, in get_module
importlib.reload(module)
File "C:\Users\{redacted}\.conda\envs\py3datalab\Lib\importlib\__init__.py", line 121, in reload
raise ImportError(f"parent {parent_name!r} not in sys.modules",
ImportError: parent 'phobia' not in sys.modules
hmm.py:
import argparse
import importlib.util
import os
import sys
def get_module(qualname, rootdir):
module = sys.modules.get(qualname)
if module is None:
modulepath = os.path.join(rootdir, '/'.join(qualname.split('.'))+'.py')
print("Importing %s from %s" % (qualname, modulepath))
spec = importlib.util.spec_from_file_location(qualname, modulepath)
module = importlib.util.module_from_spec(spec)
sys.modules[qualname] = module
spec.loader.exec_module(module)
else:
importlib.reload(module)
return module
def doit(args):
for run in range(2):
print("Run %d" % run)
module = get_module(args.module, args.rootdir)
value = getattr(module, args.name)
print('%s from %s = %s' % (args.name, args.module, value))
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('rootdir')
parser.add_argument('module')
parser.add_argument('name')
args = parser.parse_args()
doit(args)
文件系统包含:
- hmm.py
- test_in
- phobia
- agoraphobia.py
文件agoraphobia.py只包含这一行 QQ = 12345
解决方案
为什么缺失但并非必须的父包会导致动态模块重新加载失败?
如果你查看 importlib 的源代码:
def reload(module):
"""Reload the module and return it.
The module must have been successfully imported before.
"""
try:
name = module.__spec__.name
except AttributeError:
try:
name = module.__name__
except AttributeError:
raise TypeError("reload() argument must be a module") from None
if sys.modules.get(name) is not module:
raise ImportError(f"module {name} not in sys.modules", name=name)
if name in _RELOADING:
return _RELOADING[name]
_RELOADING[name] = module
try:
parent_name = name.rpartition('.')[0]
if parent_name:
try:
parent = sys.modules[parent_name]
except KeyError:
raise ImportError(f"parent {parent_name!r} not in sys.modules",
name=parent_name) from None
else:
pkgpath = parent.__path__
else:
pkgpath = None
target = module
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)
_bootstrap._exec(spec, module)
# The module may have replaced itself in sys.modules!
return sys.modules[name]
finally:
try:
del _RELOADING[name]
except KeyError:
pass
如果模块的名称中包含 .,那么 importlib.reload 会假设父包存在,并试图使用它来找到用于加载该模块的规范,以便重新加载它。
为解决它:
- 创建一个空的
test_in/phobia/__init__.py文件,以使phobia成为一个有效的包。 - 在从
test_in目录重新加载phobia.agoraphobia之前,确保已经加载了phobia包,可以通过加载phobia.__init__来实现。
import argparse
import importlib
import os
import sys
def get_module(name, path, rootdir):
module = sys.modules.get(name)
if module is None:
modulepath = os.path.join(rootdir, os.path.sep.join(path.split('.'))+'.py')
print("Importing %s from %s" % (name, modulepath))
spec = importlib.util.spec_from_file_location(name, modulepath)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
sys.modules[name] = module
else:
module = importlib.reload(module)
return module
def doit(args):
rootdir = args.rootdir
if args.load_ancestor:
parent = args.module.rpartition(".")[0]
if parent:
print(f"Loading parent module {parent}")
get_module(parent, f"{parent}.__init__", rootdir)
for run in range(2):
print(f"Run {run}")
module = get_module(args.module, args.module, rootdir)
value = getattr(module, args.name)
print(f"{args.name} from {args.module} = {value}")
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("-l", "--load_ancestor", action="store_false")
parser.add_argument("rootdir", nargs="?", default="test_in")
parser.add_argument("module", nargs="?", default="phobia.agoraphobia")
parser.add_argument("name", nargs="?", default="QQ")
args = parser.parse_args()
doit(args)
输出如下:
Loading parent module phobia
Importing phobia from test_in\phobia\__init__.py
Run 0
Importing phobia.agoraphobia from test_in\phobia\agoraphobia.py
QQ from phobia.agoraphobia = 12345
Run 1
QQ from phobia.agoraphobia = 12345
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。