Python 动态加载

  1. 按包名加载
  2. 按文件路径加载
  3. 参考

动态加载通常有两种场景:模块已经位于可导入的 Python 包中,或者只有一个任意路径下的 .py 文件。前者使用 import_module(),后者使用 spec_from_file_location()

按包名加载

目录结构如下。modules/__init__.py 不能省略,它让 modules 成为普通包。

.
├── main.py
└── modules
    ├── __init__.py
    ├── module_1.py
    └── module_2.py

两个模块都提供统一入口:

# modules/module_1.py
def echo():
    print("This is module 1")

main.py 发现包内模块并逐个调用:

from importlib import import_module
from pathlib import Path

PACKAGE = "modules"
PACKAGE_DIR = Path(__file__).parent / PACKAGE

for module_file in sorted(PACKAGE_DIR.glob("*.py")):
    if module_file.name == "__init__.py":
        continue

    module = import_module(f"{PACKAGE}.{module_file.stem}")
    module.echo()

这里应使用 Path.stem 获取不带扩展名的文件名。filename.rstrip(".py") 会按字符集合删除末尾的 py.,并不等同于删除扩展名。

按文件路径加载

插件文件不在当前包或 sys.path 中时,可以直接从路径创建模块规范:

from importlib.util import module_from_spec, spec_from_file_location
from pathlib import Path


def load_module(file_path: Path):
    module_name = f"plugin_{file_path.stem}"
    spec = spec_from_file_location(module_name, file_path)
    if spec is None or spec.loader is None:
        raise ImportError(f"cannot load module from {file_path}")

    module = module_from_spec(spec)
    spec.loader.exec_module(module)
    return module


for file_path in sorted(Path("/opt/my-app/plugins").glob("*.py")):
    load_module(file_path).echo()

加载模块会执行模块顶层代码,因此路径和文件内容必须来自可信来源。不要用字符串拼接配合 exec() 实现导入;它既绕过正常导入语义,也会把不可信模块名变成代码执行入口。


参考

内置函数 — Python 3 文档
5. 导入系统 — Python 3 文档
importlib — import 的实现


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。

文章标题:Python 动态加载

字数:392

本文作者:

发布时间:2024-09-09, 11:38:00

最后更新:2026-08-26, 21:18:00

原始链接:https://cnlnn.pages.dev/posts/python-dynamic-import/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。