一、动态导入**import_module**
语法:
importlib.import_module(name, package=None)
name (必填):要导入的模块名称,可以是绝对名称(如 'os.path'),也可以是相对名称(以点开头,如 '.submodule')。
package (选填):仅当 name 是相对名称时必须提供,用于指定"锚点"包。通常传入当前模块的 __package__ 属性(或者当前包的名字)。
返回值:成功则返回导入的模块对象,失败则抛出 ImportError
示例代码(一)
module/info.py
python
def student(name,age):
print(f"姓名:{name}\n年龄:{age}岁")
if __name__ == '__main__':
student("张三一",26)
01import_module.py
python
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运行结果:
python
姓名:张三一
年龄:26岁
-------------------
姓名:张三一
年龄:26岁
示例代码(二):
module/company.py
python
class worker():
def name(self):
print("姓名:李靖")
def age(self):
print("年龄:66岁")
def address(self):
print("地址:大唐长安")
02importlib_module2.py
python
import importlib
company=importlib.import_module(name="module.company")
worker=company.worker()
# 下面输入worker.后,函数名称是可以自动提示的
worker.name()
worker.age()
worker.address()
02importlib_module2.py运行结果
python
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
python
# calc.py
x = 10
def add(a, b):
return a + b
python
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运行结果
python
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() 配合循环。
python
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() 不接受字节流,只认文件路径,用这个:
python
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(命令)
在终端运行:
python
# 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 文件(一劳永逸)
如果资源很多,每次敲命令太麻烦,生成一个配置文件:
python
pyi-makespec 04.py
然后在生成的 04.spec 文件中找到 datas 列表,添加上你的资源包:
python
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。只读不写,改动副本,内存安全,打包无忧。