3、Python基础速通(下):文件读写(CSV/JSON)、异常处理、面向对象编程(类与继承)、模块与包管理
好,咱们接着聊。上一节我们把Python的基础语法和数据结构捋了一遍,算是把工具箱里的螺丝刀、扳手都认全了。这一节,咱们要开始真正干活了——怎么从文件里读数据、怎么处理程序跑飞的情况、怎么用面向对象的方式组织代码,还有怎么管理你的代码模块。
这些内容,说白了就是让你从一个「写脚本的」变成一个「写系统的」。我在镜头测试系统里,每天都要跟CSV、JSON打交道,异常处理更是家常便饭。你想想看,一台几万块的测试设备在跑,突然因为一个文件格式不对就崩了,那多尴尬。
3.1 文件读写:CSV与JSON
文件读写是基本功。我习惯把文件操作分成三步:打开、读写、关闭。Python里用open()函数,配合with语句,能省不少心。
3.1.1 基础文件操作
# 写文件
with open('test.txt', 'w', encoding='utf-8') as f:
f.write('MTF值: 0.85\n')
f.write('畸变: 1.2%\n')
# 读文件
with open('test.txt', 'r', encoding='utf-8') as f:
content = f.read()
print(content)
这里有个坑,我刚开始学的时候踩过——编码问题。Windows默认是GBK,Linux是UTF-8。你写代码时最好统一用encoding='utf-8',不然换个环境就乱码了。
3.1.2 CSV文件读写
CSV是镜头测试数据最常用的格式。每个镜头一条记录,字段包括镜头ID、MTF值、畸变、场曲等等。
import csv
# 写入CSV
data = [
['Lens_ID', 'MTF_Sag', 'MTF_Tan', 'Distortion'],
['L001', 0.85, 0.82, 1.2],
['L002', 0.88, 0.86, 0.9],
['L003', 0.83, 0.80, 1.5]
]
with open('lens_data.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerows(data)
# 读取CSV
with open('lens_data.csv', 'r', encoding='utf-8') as f:
reader = csv.reader(f)
for row in reader:
print(row)
小技巧:用csv.DictReader和csv.DictWriter,可以直接用列名操作数据,代码可读性高很多。我在做测试报告自动生成时,就喜欢用这种方式。
3.1.3 JSON文件读写
JSON在配置文件和API交互中很常见。比如镜头测试系统的参数配置,我就喜欢用JSON存。
import json
# 写入JSON
config = {
'camera': {
'resolution': '1920x1080',
'fps': 30,
'exposure': 100
},
'test_items': ['MTF', 'Distortion', 'Field_Curvature'],
'threshold': {
'MTF_min': 0.8,
'Distortion_max': 2.0
}
}
with open('config.json', 'w', encoding='utf-8') as f:
json.dump(config, f, indent=4, ensure_ascii=False)
# 读取JSON
with open('config.json', 'r', encoding='utf-8') as f:
loaded_config = json.load(f)
print(loaded_config['test_items'])
注意:ensure_ascii=False这个参数很重要。如果不加,中文会被转成\uXXXX的编码,看着头大。我曾经因为这个被同事吐槽过。
3.2 异常处理
程序跑飞是常态,关键是怎么优雅地处理。Python的异常处理机制,说白了就是给程序穿上防弹衣。
3.2.1 try-except 基础
try:
# 可能出错的代码
with open('data.csv', 'r') as f:
data = f.read()
result = 10 / 0 # 故意制造错误
except FileNotFoundError:
print('文件没找到,检查路径')
except ZeroDivisionError:
print('除数不能为0')
except Exception as e:
print(f'未知错误: {e}')
else:
print('一切正常,没有异常')
finally:
print('不管有没有异常,我都会执行')
我个人习惯把else和finally都用上。else里放正常逻辑,finally里放资源清理。这样代码结构清晰,不容易漏掉收尾工作。
3.2.2 自定义异常
在镜头测试系统里,我经常自定义异常,比如测试数据不合格、设备连接超时等。
class MTFOutOfRangeError(Exception):
"""MTF值超出范围异常"""
def __init__(self, mtf_value, message='MTF值不在合格范围内'):
self.mtf_value = mtf_value
self.message = f'{message}: {mtf_value}'
super().__init__(self.message)
def check_mtf(mtf_value):
if mtf_value < 0.8:
raise MTFOutOfRangeError(mtf_value)
return True
try:
check_mtf(0.75)
except MTFOutOfRangeError as e:
print(f'测试失败: {e}')
避坑指南:我曾经在异常处理里直接pass,结果程序跑了一整天,所有错误都被静默吞掉了。后来排查问题,发现数据全是错的。记住:异常要么处理,要么往上抛,千万别静默。
3.3 面向对象编程:类与继承
面向对象编程,说白了就是把数据和操作数据的方法打包在一起。在镜头测试系统里,每个镜头、每台设备、每个测试项目,都可以抽象成一个对象。
3.3.1 类的定义与使用
class Lens:
"""镜头类"""
def __init__(self, lens_id, focal_length, aperture):
self.lens_id = lens_id
self.focal_length = focal_length
self.aperture = aperture
self.mtf_values = []
def add_mtf(self, mtf_sag, mtf_tan):
"""添加MTF测试数据"""
self.mtf_values.append({
'sagittal': mtf_sag,
'tangential': mtf_tan
})
def get_avg_mtf(self):
"""计算平均MTF"""
if not self.mtf_values:
return 0
total = sum(m['sagittal'] + m['tangential'] for m in self.mtf_values)
return total / (len(self.mtf_values) * 2)
# 使用
lens = Lens('L001', 50, 2.8)
lens.add_mtf(0.85, 0.82)
lens.add_mtf(0.88, 0.86)
print(f'镜头{lens.lens_id}平均MTF: {lens.get_avg_mtf():.3f}')
3.3.2 继承与多态
继承是面向对象的核心。比如我们有标准镜头和广角镜头,它们有共同属性,也有各自特点。
class StandardLens(Lens):
"""标准镜头"""
def __init__(self, lens_id, focal_length, aperture):
super().__init__(lens_id, focal_length, aperture)
self.lens_type = 'Standard'
def check_distortion(self, value):
"""标准镜头畸变检查"""
return value < 2.0
class WideAngleLens(Lens):
"""广角镜头"""
def __init__(self, lens_id, focal_length, aperture):
super().__init__(lens_id, focal_length, aperture)
self.lens_type = 'WideAngle'
def check_distortion(self, value):
"""广角镜头畸变检查(允许更大畸变)"""
return value < 3.5
# 多态
lenses = [
StandardLens('S001', 50, 2.8),
WideAngleLens('W001', 24, 2.8)
]
for lens in lenses:
print(f'{lens.lens_type}镜头{lens.lens_id}:')
print(f' 畸变检查结果: {lens.check_distortion(2.5)}')
我的经验:继承层次不要太深,三层以内最好。我见过有人搞了七八层继承,改一个基类,所有子类都受影响,那叫一个酸爽。记住:组合优于继承,能用组合解决的问题,别硬上继承。
3.4 模块与包管理
代码多了,就要分模块管理。Python的模块机制,说白了就是帮你把代码拆成多个文件,各司其职。
3.4.1 模块的创建与导入
假设我们有一个镜头测试工具包,目录结构如下:
lens_tools/
__init__.py
mtf_calculator.py
distortion_checker.py
utils.py
mtf_calculator.py内容:
def calculate_mtf(image_data):
"""计算MTF值"""
# 实际计算逻辑
return 0.85
def analyze_mtf_curve(frequencies, values):
"""分析MTF曲线"""
# 分析逻辑
pass
在其他文件中导入使用:
# 方式一:导入整个模块
import lens_tools.mtf_calculator as mtf
result = mtf.calculate_mtf(image_data)
# 方式二:导入特定函数
from lens_tools.mtf_calculator import calculate_mtf
result = calculate_mtf(image_data)
# 方式三:导入包内所有内容(不推荐)
from lens_tools import *
注意:from module import *这种写法,我建议你少用。它会污染命名空间,而且别人看你的代码,根本不知道函数从哪来的。显式导入才是好习惯。
3.4.2 __init__.py的作用
这个文件让Python把目录当成包。你可以在里面做初始化工作,或者控制对外暴露的接口。
# lens_tools/__init__.py
from .mtf_calculator import calculate_mtf
from .distortion_checker import check_distortion
__all__ = ['calculate_mtf', 'check_distortion']
这样,外部只需要from lens_tools import calculate_mtf,不用关心内部文件结构。
3.4.3 常用内置模块
| 模块名 | 用途 | 我在项目中怎么用 |
|---|---|---|
os |
操作系统接口 | 遍历测试数据文件夹、创建报告目录 |
sys |
系统相关参数 | 获取命令行参数、添加模块搜索路径 |
datetime |
日期时间处理 | 给测试报告打时间戳 |
re |
正则表达式 | 解析测试日志、提取关键数据 |
math |
数学运算 | MTF曲线拟合、光学计算 |
核心总结:这一节的内容,是Python从「能用」到「好用」的关键。文件读写让你能跟外部数据交互,异常处理让你的程序更健壮,面向对象让你的代码更清晰,模块管理让你的项目更规范。我在做镜头测试系统时,这些技术每天都在用。你把这些练熟了,后面做项目就顺了。