第3章:Python基础速通——变量与数据类型、列表与字典、函数与类、文件读写、异常处理
各位同学,欢迎来到Python基础速通。这一章,咱们不讲那些花里胡哨的语法糖,只讲AI部署真正用得上的东西。
我见过太多人,Python写得飞起,一到嵌入式设备上就翻车。为什么?因为部署环境资源有限,你的代码必须精打细算。说白了,Python基础不牢,部署时处处是坑。
3.1 变量与数据类型——内存里的“小盒子”
变量是什么?就是内存里一个带名字的盒子。你往里放什么,它装什么。
Python是动态类型语言,这点和C/C++完全不同。我刚开始做嵌入式时,总觉得不写类型浑身难受。但后来发现,动态类型在快速原型验证时确实方便。
# 变量赋值,不需要声明类型
model_path = "yolov5s.tflite" # 字符串
confidence = 0.85 # 浮点数
num_classes = 80 # 整数
is_quantized = True # 布尔值
print(type(model_path)) # <class 'str'>
print(type(confidence)) # <class 'float'>
避坑指南:我曾经在树莓派上部署模型时,因为变量类型隐式转换导致推理结果异常。比如 1/3 在Python3里是浮点数,但在Python2里是整数0。部署时一定要确认Python版本。
数据类型这块,我建议你重点掌握这几种:
- int:整数,注意Python3的int没有大小限制,但嵌入式设备内存有限
- float:浮点数,部署时经常要转成float16或int8量化
- str:字符串,模型路径、标签文件都靠它
- bool:布尔值,控制流程的开关
- None:空值,表示“什么都没有”
个人经验:在边缘设备上,我习惯用 sys.getsizeof() 检查变量占用的内存。你想想看,一个不小心,列表里塞了几万个浮点数,内存就爆了。
3.2 列表与字典——最常用的数据结构
列表和字典,是AI部署中使用频率最高的两种数据结构。没有之一。
3.2.1 列表——有序的“货架”
列表就像超市里的货架,东西按顺序摆放。你可以从任意位置拿取或放回。
# 创建一个列表
detections = ["person", "car", "dog", 0.95, 0.87] # 可以混装不同类型
# 常用操作
detections.append("bicycle") # 末尾添加
detections.insert(0, "truck") # 指定位置插入
detections.pop() # 弹出最后一个
detections[2] # 索引访问,从0开始
# 列表推导式——我特别喜欢用
scores = [0.95, 0.87, 0.76, 0.64]
filtered = [s for s in scores if s > 0.8] # 只保留大于0.8的
print(filtered) # [0.95, 0.87]
重点:列表是引用类型。你赋值给另一个变量时,改一个两个都变。我踩过这个坑——在预处理图像时,不小心改了原始列表,结果后面推理全错。
3.2.2 字典——键值对的“通讯录”
字典就是键值对。你报一个名字(键),它给你一个号码(值)。
# 模型配置信息,用字典存最合适
model_config = {
"model_name": "yolov5s",
"input_shape": [1, 3, 640, 640],
"confidence_threshold": 0.5,
"iou_threshold": 0.45,
"classes": ["person", "car", "dog"]
}
# 访问和修改
print(model_config["model_name"]) # yolov5s
model_config["confidence_threshold"] = 0.6 # 修改阈值
# 遍历字典
for key, value in model_config.items():
print(f"{key}: {value}")
我的习惯:在部署项目中,我会把所有可调参数放在一个字典里。这样改参数时不用翻代码,直接改配置文件就行。省心。
3.3 函数与类——代码的“积木块”
函数和类,说白了就是让代码可复用、可维护。在AI部署中,你写的每个函数都可能被反复调用几百上千次。
3.3.1 函数——一次定义,多次调用
def preprocess_image(image_path, target_size=(640, 640)):
"""
图像预处理函数
Args:
image_path: 图像路径
target_size: 目标尺寸 (H, W)
Returns:
预处理后的numpy数组
"""
import cv2
import numpy as np
img = cv2.imread(image_path)
img = cv2.resize(img, target_size)
img = img.astype(np.float32) / 255.0 # 归一化
img = np.expand_dims(img, axis=0) # 增加batch维度
return img
# 调用函数
input_data = preprocess_image("test.jpg")
print(input_data.shape) # (1, 640, 640, 3)
注意:函数参数默认值只在定义时计算一次。如果默认值是可变对象(比如列表),多次调用会共享同一个对象。我曾经因为这个bug排查了一整天。
3.3.2 类——把数据和操作打包
类把数据和操作它的函数打包在一起。在部署中,我常用类来封装模型推理器。
class YOLOv5Inferencer:
"""YOLOv5模型推理器"""
def __init__(self, model_path, conf_threshold=0.5):
self.model_path = model_path
self.conf_threshold = conf_threshold
self.model = self._load_model()
self.class_names = ["person", "car", "dog"]
def _load_model(self):
"""加载模型(私有方法)"""
import tflite_runtime.interpreter as tflite
interpreter = tflite.Interpreter(model_path=self.model_path)
interpreter.allocate_tensors()
return interpreter
def infer(self, input_data):
"""执行推理"""
self.model.set_tensor(self.model.get_input_details()[0]['index'], input_data)
self.model.invoke()
output = self.model.get_tensor(self.model.get_output_details()[0]['index'])
return output
def postprocess(self, output):
"""后处理:过滤低置信度检测框"""
# 实际项目中这里会有NMS等操作
return [det for det in output if det[4] > self.conf_threshold]
# 使用示例
detector = YOLOv5Inferencer("yolov5s.tflite", conf_threshold=0.6)
result = detector.infer(input_data)
filtered_result = detector.postprocess(result)
个人建议:在嵌入式设备上,类的方法不要太多。我一般一个类不超过5个方法,多了反而难维护。保持简单。
3.4 文件读写——模型和数据的“搬运工”
AI部署离不开文件读写。模型文件、配置文件、日志文件、结果文件……你想想看,哪个环节能少?
3.4.1 文本文件读写
# 读取配置文件
with open("config.txt", "r", encoding="utf-8") as f:
content = f.read()
print(content)
# 写入推理结果
with open("results.txt", "w", encoding="utf-8") as f:
f.write("检测结果:\n")
f.write(f"目标: person, 置信度: 0.95\n")
f.write(f"目标: car, 置信度: 0.87\n")
避坑指南:我曾经在Windows上写的路径分隔符是反斜杠 \,部署到Linux设备上就报错。后来我改用 os.path.join() 或者直接正斜杠 /,再也没出过问题。
3.4.2 二进制文件读写——模型文件的关键
# 读取模型文件(二进制模式)
with open("model.tflite", "rb") as f:
model_data = f.read()
print(f"模型大小: {len(model_data)} 字节")
# 写入二进制数据
with open("output.bin", "wb") as f:
f.write(model_data)
重点:模型文件通常很大(几MB到几百MB)。在嵌入式设备上,我建议用 shutil.copyfileobj() 分块拷贝,避免一次性加载到内存。
3.5 异常处理——程序的“安全气囊”
部署环境千奇百怪。模型加载失败、文件不存在、内存不足……没有异常处理,程序一崩就是灾难。
def safe_load_model(model_path):
"""安全加载模型,带异常处理"""
try:
import tflite_runtime.interpreter as tflite
interpreter = tflite.Interpreter(model_path=model_path)
interpreter.allocate_tensors()
print("模型加载成功")
return interpreter
except FileNotFoundError:
print(f"错误:模型文件 {model_path} 不存在")
return None
except Exception as e:
print(f"未知错误:{e}")
return None
# 使用
model = safe_load_model("yolov5s.tflite")
if model is None:
print("模型加载失败,程序退出")
exit(1)
我的经验:异常处理不要滥用。我见过有人把整个推理循环包在try里,结果异常被吞掉,程序默默跑错。正确的做法是:只在可能出错的地方加try,并且要记录日志。
常用的异常类型:
| 异常类型 | 触发场景 | 部署中常见 |
|---|---|---|
| FileNotFoundError | 文件不存在 | 模型文件路径错误 |
| ValueError | 参数值不合法 | 输入张量形状不匹配 |
| TypeError | 类型不匹配 | numpy数组转tensor时类型错误 |
| MemoryError | 内存不足 | 嵌入式设备上加载大模型 |
| RuntimeError | 运行时错误 | 推理引擎初始化失败 |
最后提醒:在嵌入式设备上,异常处理要轻量。不要在每个函数里都写try-except,那样会拖慢性能。我一般只在关键路径(模型加载、推理调用、文件读写)上加异常处理。
好了,这一章的内容就到这里。Python基础说难不难,说简单也不简单。关键是你在实际项目中用起来,踩过坑,才能真正掌握。下一章我们讲numpy——AI部署的“数学引擎”,到时候见。