29 importlib动态导入
有时候你需要根据配置文件、用户输入或者数据库里的字符串来导入模块——比如插件系统加载插件、框架按需加载组件。这时候import语句就不够用了,importlib模块让你在运行时动态导入模块。
一、import_module():动态导入
1.1 基本用法
python
import importlib
# 动态导入模块
itertools = importlib.import_module('itertools')
print(list(itertools.chain([1, 2], [3, 4]))) # [1, 2, 3, 4]
# 动态导入子模块
email_parser = importlib.import_module('email.parser')1.2 相对导入
python
import importlib
# 相对导入需要指定package参数
# 假设在pkg.subpkg中运行
# import_module('..mod', 'pkg.subpkg') 会导入 pkg.mod1.3 与__import__()的区别
python
import importlib
# import_module()返回指定的模块
mod = importlib.import_module('email.parser')
print(type(mod)) # <class 'module'> -- 就是email.parser模块
# __import__()返回顶层包
mod = __import__('email.parser')
print(type(mod)) # <class 'module'> -- 是email模块,不是email.parser
print(hasattr(mod, 'parser')) # True二、检查模块是否存在
2.1 find_spec()
python
import importlib.util
def check_module(name):
"""检查模块是否存在,不实际导入"""
spec = importlib.util.find_spec(name)
if spec is None:
print(f"模块 {name} 不存在")
else:
print(f"模块 {name} 存在,位置: {spec.origin}")
check_module('json') # 存在
check_module('nonexistent') # 不存在2.2 安全导入
python
import importlib
import importlib.util
import sys
def safe_import(name):
"""安全导入模块,不存在则返回None"""
if name in sys.modules:
return sys.modules[name]
spec = importlib.util.find_spec(name)
if spec is None:
return None
return importlib.import_module(name)
json_module = safe_import('json')
if json_module:
print("json模块可用")三、reload():重新加载模块
3.1 基本用法
python
import importlib
import my_module # 假设有个自定义模块
# 修改了my_module.py后,重新加载
importlib.reload(my_module)3.2 开发时热重载
python
import importlib
import sys
def reload_module(module_name):
"""重新加载指定模块"""
if module_name in sys.modules:
module = sys.modules[module_name]
importlib.reload(module)
print(f"重新加载 {module_name}")
else:
print(f"模块 {module_name} 未加载")
# 使用
reload_module('my_plugin')3.3 reload的注意事项
python
import importlib
# 注意:reload不会更新已有实例的引用
# 比如:
# from my_module import MyClass
# obj = MyClass()
# importlib.reload(my_module) # obj仍然用旧的MyClass
# 推荐使用模块名引用
import my_module
obj = my_module.MyClass()
importlib.reload(my_module) # 现在my_module.MyClass是新的四、invalidate_caches():刷新缓存
python
import importlib
# 如果在程序运行时安装了新模块,需要刷新缓存
importlib.invalidate_caches()
# 现在可以导入新安装的模块
new_module = importlib.import_module('newly_installed')五、从文件路径导入
5.1 spec_from_file_location()
python
import importlib.util
import sys
def import_from_path(module_name, file_path):
"""从指定文件路径导入模块"""
spec = importlib.util.spec_from_file_location(module_name, file_path)
if spec is None:
raise ImportError(f"无法从 {file_path} 加载模块")
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
return module
# 使用
# my_module = import_from_path('my_module', '/path/to/my_module.py')5.2 加载配置文件
python
import importlib.util
import sys
def load_config(config_path):
"""把Python文件当配置加载"""
spec = importlib.util.spec_from_file_location('config', config_path)
config = importlib.util.module_from_spec(spec)
sys.modules['config'] = config
spec.loader.exec_module(config)
return config
# config.py内容:
# DEBUG = True
# DATABASE_URL = "sqlite:///db.sqlite"
# config = load_config('config.py')
# print(config.DEBUG) # True六、延迟导入
6.1 LazyLoader
python
import importlib.util
import sys
def lazy_import(name):
"""延迟导入,第一次访问属性时才真正加载"""
spec = importlib.util.find_spec(name)
loader = importlib.util.LazyLoader(spec.loader)
spec.loader = loader
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
loader.exec_module(module)
return module
# 模块对象已创建,但代码未执行
lazy_typing = lazy_import("typing")
# 第一次访问时才真正加载
print(lazy_typing.TYPE_CHECKING) # 此时才执行typing模块的代码6.2 按需导入
python
import importlib
# 简单的按需导入模式
def get_module(name):
"""按需导入,已加载则直接返回"""
import sys
if name in sys.modules:
return sys.modules[name]
return importlib.import_module(name)
# 用到时才导入
data = get_module('json').dumps({"key": "value"})七、插件系统
7.1 基本插件框架
python
import importlib
import importlib.util
from pathlib import Path
class PluginManager:
def __init__(self, plugin_dir):
self.plugin_dir = Path(plugin_dir)
self.plugins = {}
def discover_plugins(self):
"""发现插件目录下的所有模块"""
for py_file in self.plugin_dir.glob("*.py"):
if py_file.name.startswith("_"):
continue
module_name = py_file.stem
spec = importlib.util.spec_from_file_location(
module_name, py_file
)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
if hasattr(module, 'register'):
self.plugins[module_name] = module.register()
def get_plugin(self, name):
return self.plugins.get(name)
# 使用
# pm = PluginManager("./plugins")
# pm.discover_plugins()7.2 带钩子的插件系统
python
import importlib
from abc import ABC, abstractmethod
class Plugin(ABC):
@abstractmethod
def name(self):
pass
@abstractmethod
def execute(self, data):
pass
class PluginLoader:
def __init__(self):
self.plugins = {}
def register(self, plugin_class):
"""注册插件类"""
plugin = plugin_class()
self.plugins[plugin.name()] = plugin
def load_from_module(self, module_name):
"""从模块加载插件"""
module = importlib.import_module(module_name)
for attr_name in dir(module):
attr = getattr(module, attr_name)
if (isinstance(attr, type) and
issubclass(attr, Plugin) and
attr is not Plugin):
self.register(attr)
def execute(self, plugin_name, data):
plugin = self.plugins.get(plugin_name)
if plugin:
return plugin.execute(data)
raise ValueError(f"插件 {plugin_name} 不存在")八、实用工具函数
8.1 获取模块信息
python
import importlib.util
def get_module_info(name):
"""获取模块的详细信息"""
spec = importlib.util.find_spec(name)
if spec is None:
return None
return {
'name': name,
'origin': spec.origin,
'submodule_search_locations': spec.submodule_search_locations,
'cached': spec.cached,
'has_location': spec.has_location,
}
info = get_module_info('json')
print(info)8.2 列出已加载的模块
python
import sys
def list_loaded_modules():
"""列出所有已加载的模块"""
for name, module in sorted(sys.modules.items()):
if hasattr(module, '__file__'):
print(f"{name}: {module.__file__}")九、总结
importlib的核心:
| 函数 | 用途 |
|---|---|
import_module() | 动态导入模块 |
reload() | 重新加载已导入的模块 |
invalidate_caches() | 刷新查找器缓存 |
find_spec() | 查找模块规格,不实际导入 |
spec_from_file_location() | 从文件路径创建模块规格 |
module_from_spec() | 从规格创建模块对象 |
LazyLoader | 延迟加载模块 |
使用场景:
- 插件系统
- 按需加载模块
- 热重载代码
- 从文件路径加载模块
- 检查模块是否存在
import_module()是最常用的,记住它和__import__()的区别:前者返回目标模块,后者返回顶层包。