第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部署的“数学引擎”,到时候见。