3、Python基础速通(下):函数定义、面向对象基础、文件读写、异常处理
好,咱们接着往下聊。上一章我们把 Python 的基本数据类型和流程控制捋了一遍,算是把「地基」打好了。这一章,我们要开始搭「框架」了——函数、类、文件操作、异常处理,这四个东西在 OpenCV 和 YOLO 的实战里,几乎每一行代码都会用到。
我个人习惯把这一章叫做「实战前的最后一块拼图」。你想想看,没有函数,你的检测代码会变成一坨无法复用的面条;没有类,你没法优雅地管理模型和摄像头;不会文件读写,你连图片都存不下来;不处理异常,程序一崩就全完了。
所以,这一章很重要。咱们一个一个来。
3.1 函数定义:把逻辑装进「黑盒子」
函数是什么?说白了,就是把一段重复用的代码包起来,给它起个名字。以后想用,直接喊名字就行。
在 OpenCV 里,我经常把「读取图片 → 预处理 → 检测 → 画框 → 显示」这五步封装成一个函数。这样主程序就干净多了。
3.1.1 基本定义与调用
def detect_and_show(image_path):
img = cv2.imread(image_path)
# 这里放检测逻辑
cv2.imshow('Result', img)
cv2.waitKey(0)
嗯,这里要注意:Python 用 def 关键字定义函数,后面跟函数名和冒号。函数体要缩进。调用的时候直接写函数名加括号。
3.1.2 参数与返回值
我在项目中遇到过一个问题:同一个检测函数,有时候要处理图片文件,有时候要处理视频帧。怎么办?用参数控制。
| 参数类型 | 写法 | 说明 |
|---|---|---|
| 位置参数 | def func(a, b) | 按顺序传参 |
| 默认参数 | def func(a, b=10) | 不传就用默认值 |
| 关键字参数 | func(a=1, b=2) | 明确指定参数名 |
| 可变参数 | def func(*args, **kwargs) | 接收任意数量参数 |
3.1.3 作用域与 global
函数内部的变量是局部变量,外面看不到。但有时候你需要修改全局变量——比如一个全局的帧计数器。这时候用 global 声明一下。
frame_count = 0
def process_frame():
global frame_count
frame_count += 1
print(f'处理了第 {frame_count} 帧')
3.2 面向对象基础:用「类」管理复杂逻辑
面向对象编程(OOP)听起来高大上,其实核心就一句话:把数据和操作数据的方法打包在一起。
在 YOLO 检测系统里,我会把检测器设计成一个类。这个类里有模型加载、预处理、推理、后处理等方法,还有模型路径、置信度阈值等属性。用起来特别顺手。
3.2.1 类与对象
class YOLODetector:
def __init__(self, model_path, conf_thresh=0.5):
self.model_path = model_path
self.conf_thresh = conf_thresh
self.net = cv2.dnn.readNet(model_path)
def detect(self, image):
# 检测逻辑
pass
# 使用
detector = YOLODetector('yolov8n.onnx', 0.5)
result = detector.detect(frame)
__init__ 是构造方法,创建对象时自动调用。self 代表实例本身,所有实例方法第一个参数都是 self。
3.2.2 封装、继承、多态
这三个概念是 OOP 的基石。我简单说一下在实战中的用法:
- 封装:把检测器的内部实现藏起来,外部只调用
detect()方法。改内部逻辑不影响外部代码。 - 继承:比如我写了一个
BaseDetector基类,然后YOLODetector和SSDDetector都继承它,共用接口。 - 多态:同一个
detect()方法,不同子类有不同的实现。调用者不用关心具体是哪个检测器。
3.3 文件读写:让数据「持久化」
检测结果要保存吧?模型配置要读取吧?日志要写吧?这些都离不开文件操作。
3.3.1 文本文件读写
# 写文件
with open('results.txt', 'w') as f:
f.write(f'检测到 {count} 个目标\n')
# 读文件
with open('config.yaml', 'r') as f:
config = f.read()
with 语句会自动关闭文件,不用手动 f.close()。模式 'w' 是写入(覆盖),'r' 是读取,'a' 是追加。
3.3.2 图片与视频文件
OpenCV 有自己的文件读写方式:
| 操作 | 代码 | 说明 |
|---|---|---|
| 读图片 | cv2.imread('image.jpg') | 返回 numpy 数组 |
| 写图片 | cv2.imwrite('out.jpg', img) | 支持 jpg/png 等格式 |
| 读视频 | cv2.VideoCapture('video.mp4') | 返回 VideoCapture 对象 |
| 写视频 | cv2.VideoWriter('out.avi', fourcc, fps, size) | 需要指定编码器 |
cv2.VideoWriter_fourcc(*'XVID'),兼容性最好。
3.4 异常处理:让程序「摔倒了能爬起来」
程序总会出错。文件找不到、模型加载失败、摄像头断开……如果不处理异常,程序直接崩溃,用户体验极差。
3.4.1 try-except 结构
try:
img = cv2.imread('nonexistent.jpg')
if img is None:
raise ValueError('图片读取失败')
except FileNotFoundError:
print('文件不存在,请检查路径')
except ValueError as e:
print(f'值错误:{e}')
except Exception as e:
print(f'未知错误:{e}')
else:
print('一切正常')
finally:
print('无论如何都会执行')
这里有几个要点:
try里放可能出错的代码except捕获特定异常,可以写多个else在没异常时执行finally不管有没有异常都执行——我常用它来释放摄像头资源
3.4.2 自定义异常
有时候内置异常不够用。比如检测器加载失败,我想抛一个 ModelLoadError:
class ModelLoadError(Exception):
pass
def load_model(path):
if not os.path.exists(path):
raise ModelLoadError(f'模型文件 {path} 不存在')
pass 忽略异常。结果程序不报错也不干活,查了半天才发现异常被吞了。至少打个日志,别静默处理。
3.5 本章知识体系
下面这张图把本章四个核心知识点串起来了。你可以看到它们如何在 YOLO 检测系统中协同工作:
嗯,这一章的内容就这些。函数、类、文件、异常——这四个东西是 Python 实战的四大支柱。你可能会觉得概念有点多,但别急,后面每一章都会反复用到它们。用着用着就熟了。
记住:写代码不是背语法,而是用语法解决问题。把这一章的例子自己敲一遍,比看十遍都管用。