PythonLearn

搜索-加载和重载模块

搜索

最简单的可以直接遍历目录,但是也有更方便的库pkgutil(标准库)

import pkgutil
import eebackup

# 内置模块通常由不同的导入器处理
for importer, modname, ispkg in pkgutil.iter_modules(eebackup.__path__):
    ...

importer一般不用管,最常见的是FileFinder,但如果是从zip中导入的模块,则会是zipimporter
modname是模块名,比如core,ispkg是模块是否是包

path 是什么

一个list,比如eebackup这个包的__path__是[''D:\\Python39\\lib\\site-packages\\eebackup'']

为什么是list

这样可以添加额外的搜索路径,比如

import eebackup
import httpx
eebackup.__path__.extend(httpx.__path__)
from eebackup import _client
print(eebackup._client)

输出<module 'eebackup._client' from 'D:\\Python39\\lib\\site-packages\\httpx\\_client.py'>
一般开发不推荐动__path__,因为很容易出错

设置前缀

for importer, modname, ispkg in pkgutil.iter_modules(eebackup.__path__, prefix="eebackup."):
    ...

设置prefix后,则返回的modname会加上prefix,比如eebackup.core
拥有模块的路径后就可以开始导入了

导入

推荐使用importlib.import_module而不是__import__,免得因为缺少属性什么而踩坑

import importlib
name = "eebackup.core"
core = importlib.import_module(name)  # 就和普通import一样

插件系统

pkgutil的模块搜索功能,配合importlib.import_module,就可以实现插件系统了

import pkgutil
import importlib
plugin_pkg_name = "plugins"
plugins = []

for importer, modname, ispkg in pkgutil.iter_modules(["./plugins"], prefix=plugin_pkg_name+"."):
    plugin = importlib.import_module(modname)
    plugins.append(plugin)

重新加载

按照刚才的插件例子,importlib.reload会返回新的模块,我们需要移除旧的模块再添加新的模块

import importlib
plugins = [...]
for model in plugins.copy():
    new = importlib.reload(model)
    plugins.remove(model)
    plugins.append(new)

重载整个包

假如eebackup是个包而不是模块?要怎么做
根据importlib.reload底层,其实就是更改了sys.modules
使用import语句导入模块时,会先检查sys.modules中是否有这个模块,如果有则直接返回,没有则导入
所以,我们可以先删除模块再导入

import sys
import importlib

for key in sys.modules.keys():
    if key.startswith("eebackup"):
        del sys.modules[key]
        sys.modules[key] = importlib.import_module(key)

但是其他模块对旧模块的引用也需要更新,这里需要根据项目自己解决