一、动态导入import_module
语法:
importlib.import_module(name, package=None) name (必填):要导入的模块名称,可以是绝对名称(如 'os.path'),也可以是相对名称(以点开头,如 '.submodule')。 package (选填):仅当 name 是相对名称时必须提供,用于指定“锚点”包。通常传入当前模块的 __package__ 属性(或者当前包的名字)。 返回值:成功则返回导入的模块对象,失败则抛出 ImportError示例代码(一)
module/info.py
def student(name,age): print(f"姓名:{name}\n年龄:{age}岁") if __name__ == '__main__': student("张三一",26)01import_module.py
import importlib info1=importlib.import_module(name="module.info") # 切记参数name=".info",info前有一个点 info2 = importlib.import_module(name=".info", package="module") """ info1与info2等价 info1中name="module.info"写的是绝对路径,因此不需要传入package参数 而 info2中name=".info"传入的是相对路径,因此需要传入package参数 """ info1.student("张三一",26) print("-"*19) info2.student("张三一",26)01import_module.py运行结果:
姓名:张三一 年龄:26岁 ------------------- 姓名:张三一 年龄:26岁示例代码(二):
module/company.py
class worker(): def name(self): print("姓名:李靖") def age(self): print("年龄:66岁") def address(self): print("地址:大唐长安")02importlib_module2.py
import importlib company=importlib.import_module(name="module.company") worker=company.worker() # 下面输入worker.后,函数名称是可以自动提示的 worker.name() worker.age() worker.address()02importlib_module2.py运行结果
C:\Users\asus\anaconda3\python.exe "D:\My Program\study_project\06importlib\01.py" 姓名:李靖 年龄:66岁 地址:大唐长安 进程已结束,退出代码0二、 模块重载reload
语法:
import importlib importlib.reload(module) 参数:必须是一个已经导入过的模块对象(不能是字符串,也不能是未导入的模块)。 返回值:返回重新加载后的模块对象(其实就是传进去的那个对象,但在内存中被刷新了)。 作用:强制 Python 重新执行该模块的顶层代码,并用新的内容更新 sys.modules 缓存。示例代码:
module/calc.py
# calc.py x = 10 def add(a, b): return a + b03reload.py
import importlib import time from module import calc print(calc.x) # 输出 10 print(calc.add(2, 3)) # 输出 5 for i in range(10): time.sleep(1) print(f"等待{i + 1}秒...") # ---在这个等待的时间,如果修改了 calc.py,把 x 改成 20,add 改成 a - b ---,并按Ctrl+S保存module/calc.py # 修改后,不重启 Python,直接调用 reload importlib.reload(calc) # 重新加载 print(calc.x) # 输出 20 ✅ print(calc.add(2, 3)) # 输出 -1 ✅03reload.py运行结果
C:\Users\asus\anaconda3\python.exe "D:\My Program\study_project\06importlib\03reload.py" 10 5 等待1秒... 等待2秒... 等待3秒... 等待4秒... 等待5秒... 等待6秒... 等待7秒... 等待8秒... 等待9秒... 等待10秒... 20 -1 进程已结束,退出代码0三、现代化资源管理resources(⭐⭐⭐⭐⭐ 重点)
(一)为什么要用它?(解决两大噩梦)
告别
__file__拼路径:不会因为打包成 EXE 或运行在临时目录而找不到文件。告别手动复制资源:打包时自动将文件嵌入 EXE 内部,无需手动拷贝。
(二)核心前提(第一个坑:__init__.py)
铁律:被读取的文件夹,必须是一个“Python 包”,而不是普通文件夹。
错误结构(导致TypeError: ... NoneType):
loadfile/ <-- 只是个普通文件夹,没有 __init__.py └── config.txt正确结构(必须加空文件):
loadfile/ <-- 这是一个“包” ├── __init__.py <-- 必须存在(可以为空) └── config.txt(三)读取文件 API(区分大小和场景)
1. 小文件(几 KB ~ 几 MB)—— 一次性读取
读取文本:
files("包名").joinpath("文件名").read_text(encoding="utf-8")读取二进制:
files("包名").joinpath("文件名").read_bytes()
2. 大文件(避免内存溢出)—— 流式/分批读取(第二个坑)
不能用read_text(),改用.open()配合循环。
from importlib.resources import files resource = files("loadfile").joinpath("big_log.txt") # 方式一:按行读取(最省内存,适合文本) with resource.open('r', encoding='utf-8') as f: for line in f: process(line) # 逐行处理 # 方式二:按字节块读取(适合二进制或超大文件) with resource.open('rb') as f: while chunk := f.read(4096): # 每次读 4KB process(chunk)3. 第三方库需要“真实路径”时 —— 使用as_file()
如果pandas.read_csv()或PIL.Image.open()不接受字节流,只认文件路径,用这个:
from importlib.resources import files, as_file resource = files("loadfile").joinpath("data.csv") with as_file(resource) as path: # 自动生成临时真实文件路径 df = pd.read_csv(path) # 完美兼容(四)打包部署(解决手动复制文件的噩梦)
前提:你的代码必须使用importlib.resources,而不是__file__。
1. 用 PyInstaller 打包成单个 EXE(命令)
在终端运行:
# Windows 系统(注意分号 ;) pyinstaller -F --add-data "loadfile;loadfile" 04.py # Mac/Linux 系统(注意冒号 :) pyinstaller -F --add-data "loadfile:loadfile" 04.py -F:打包成单文件 EXE。 --add-data "本地文件夹;打包后文件夹":将整个资源包打进 EXE 内部。2. 使用.spec文件(一劳永逸)
如果资源很多,每次敲命令太麻烦,生成一个配置文件:
pyi-makespec 04.py然后在生成的04.spec文件中找到datas列表,添加上你的资源包:
a = Analysis( ['04.py'], datas=[('loadfile', 'loadfile'), ('images', 'images')], # 自动打包所有资源 ... )以后只需要运行pyinstaller 04.spec即可,无需反复敲参数。
(五)重要提醒:关于“写入”文件(第三个坑)
importlib.resources只能“读取”,不能“写入”。EXE 内部的资源文件是只读的。
正确做法:程序首次运行时,用
resources读取默认配置,然后用普通的open()在当前用户目录(如os.getcwd()或AppData)下生成副本。以后修改和写入都针对副本操作。这样既保留了原版配置,又支持用户自定义修改。
(六)常见错误清单(帮你快速排雷)
| 错误现象 | 根本原因 | 解决方案 |
|---|---|---|
TypeError: expected str, bytes ... not NoneType | 资源文件夹没有__init__.py | 在文件夹里新建一个空的__init__.py |
| 读取大文件时内存爆满 / 程序卡死 | 错误使用了read_text()一次性读入 | 改用.open()+for line in f逐行读取 |
| 打包后 EXE 运行报错“文件找不到” | 代码用了__file__拼接路径 | 换成importlib.resources写法 |
| 打包后资源文件不存在 | 打包时忘了加--add-data参数 | 打包命令加上--add-data "包名;包名" |
as_file()报错或无法释放 | 忘记用with上下文管理器 | 必须写成with as_file(resource) as path: |
(七)总结(你的“心法口诀”)
资源文件夹必须加
__init__.py,这是包的身份证。
小文件read_text,大文件open循环。
传给第三方库用as_file,打包时带上--add-data。
只读不写,改动副本,内存安全,打包无忧。