如何动态加载包含相对导入的 /foo.py?
数据小许
结论
可以动态加载 /foo.py,并让其中的 from . import bar 正常解析。先在 sys.modules 中注册一个临时包,将包的 __path__ 设为 /,再把 /foo.py 作为该包的子模块加载。这样,相对导入就会在 / 下查找 /bar.py。
相对导入根据模块的包名和包搜索路径解析。下面的代码假设文件分别为 /foo.py 和 /bar.py,且 /foo.py 中包含 from . import bar。
示例
import importlib.util
import sys
from importlib.machinery import ModuleSpec
from pathlib import Path
root = Path("/")
foo_path = root / "foo.py"
pkg_name = "_dynamic_root_import_pkg"
module_name = f"{pkg_name}.foo"
# 创建一个搜索路径为 / 的临时包。
package_spec = ModuleSpec(pkg_name, loader=None, is_package=True)
package = importlib.util.module_from_spec(package_spec)
package.__path__ = [str(root)]
sys.modules[pkg_name] = package
# 将 /foo.py 作为该包的子模块加载。
spec = importlib.util.spec_from_file_location(module_name, foo_path)
if spec is None or spec.loader is None:
raise ImportError(f"无法为 {foo_path} 创建模块加载器")
mod = importlib.util.module_from_spec(spec)
sys.modules[module_name] = mod
try:
spec.loader.exec_module(mod)
except Exception:
# 执行失败时清理未完成的模块注册。
sys.modules.pop(module_name, None)
raise
执行 foo.py 时,from . import bar 会根据包名解析为 _dynamic_root_import_pkg.bar,并通过包的 __path__ 在 / 下查找 bar.py。
注意事项
- 临时包名应避免与应用中的包名冲突。若可能重复加载,可为每次加载使用唯一名称,也可在加载完成后按需清理
sys.modules中的临时包及其子模块。 - 将
/设为包搜索路径后,导入机制会在根目录查找子模块。请确认目标文件和依赖可信,并避免不必要地扩大搜索范围。 - 加载并执行模块会运行文件中的顶层代码,动态导入不提供安全隔离。
- 这种方式只提供相对导入所需的包上下文。若模块还依赖其他包或路径设置,仍需单独确保这些依赖可以被找到。
备注:内容仅供参考。
