importlib.import_module() 支持传入带点号的绝对模块路径(如"os.path")并递归加载,不支持相对路径;需校验输入安全性,优先用getattr+类型检查获取对象;__import__()行为反直觉且易错,应避免使用。

用 importlib.import_module() 导入带点号的包路径
直接传入字符串形式的完整模块路径(如 "os.path" 或 "django.conf")即可,importlib.import_module() 会按点号递归解析并加载。它和 import 语句行为一致,但支持运行时拼接路径。
常见错误是传入不含包层级的纯模块名(比如只传 "json"),这在当前目录无同名文件时看似能工作,但一旦项目里有 json.py 就会意外导入本地文件而非标准库——因为 import_module() 默认从当前命名空间向上查找,不是绝对导入。
- 始终使用绝对路径:传
"json"不如明确写"json"(顶层模块)或"package.submodule"(子模块) - 避免相对导入:该函数不接受
"..utils"这类相对路径,会抛出ValueError: attempted relative import beyond top-level package - 若路径来自用户输入,务必校验是否含非法字符或上级目录穿越(如
"../../malicious"),否则可能触发任意模块加载风险
动态导入后如何安全获取模块内对象?
导入模块只是第一步,真正要用的是里面的类、函数或常量。别用 eval() 或 getattr(module, name) 直接拼接变量名——如果 name 来自外部输入,这是典型的代码注入漏洞。
更稳妥的方式是先检查目标属性是否存在且类型合规:
立即学习“Python免费学习笔记(深入)”;
import importlib
<p>mod = importlib.import_module("datetime")
if hasattr(mod, "datetime") and callable(getattr(mod, "datetime")):
dt_class = getattr(mod, "datetime")
- 优先用
getattr(mod, attr_name, default)提供默认值,避免AttributeError - 对关键对象做类型判断:比如确认是
type或function,而不是仅靠名字猜测 - 不要把模块对象存进全局字典再反复
getattr,模块加载本身有开销,应缓存模块实例而非重复调用import_module()
为什么 __import__() 不推荐用于动态导入?
__import__() 是 import 语句底层调用的函数,但它行为反直觉:当导入 "a.b.c" 时,默认返回的是顶层包 a,不是最末的 c。要拿到 c 得手动走 a.b.c 属性链,极易出错。
而 importlib.import_module() 始终返回你指定路径对应的模块对象,语义清晰、行为稳定。
-
__import__("a.b.c")→ 返回a模块 -
importlib.import_module("a.b.c")→ 返回c模块 -
__import__()的fromlist参数逻辑复杂,容易误用导致导入失败或副作用
模块缓存与重载的边界在哪里?
importlib.import_module() 会复用 sys.modules 中已加载的模块,所以多次导入同一路径不会重复执行模块代码。但这也意味着:修改源码后想热更新,不能只靠再次调用它。
需要重载必须显式调用 importlib.reload(),且只能重载已存在的模块对象(不能对字符串路径直接 reload):
mod = importlib.import_module("my_module")
# 修改 my_module.py 后
importlib.reload(mod) # ✅ 正确
# importlib.reload("my_module") # ❌ 报错:expected a module, not str
- 重载不递归刷新依赖模块,
my_module里import utils,改了utils.py后需单独 reloadutils - 重载后原模块的引用(如已赋值给变量)仍指向旧版本,需重新
getattr或重新导入 - 生产环境慎用 reload,C 扩展模块、线程中正在执行的模块可能引发未定义行为
动态导入本身不难,难的是控制加载时机、验证对象合法性、管理缓存生命周期——这些细节不处理好,代码会在某个看似无关的部署环境里突然失效。


















