PAYATHON 2026

如何动态加载包含相对导入的 /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 中的临时包及其子模块。
  • 将 / 设为包搜索路径后,导入机制会在根目录查找子模块。请确认目标文件和依赖可信,并避免不必要地扩大搜索范围。
  • 加载并执行模块会运行文件中的顶层代码,动态导入不提供安全隔离。
  • 这种方式只提供相对导入所需的包上下文。若模块还依赖其他包或路径设置,仍需单独确保这些依赖可以被找到。

备注:内容仅供参考。