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.DictReadercsv.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('不管有没有异常,我都会执行')

我个人习惯把elsefinally都用上。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从「能用」到「好用」的关键。文件读写让你能跟外部数据交互,异常处理让你的程序更健壮,面向对象让你的代码更清晰,模块管理让你的项目更规范。我在做镜头测试系统时,这些技术每天都在用。你把这些练熟了,后面做项目就顺了。

Python基础速通(下)知识体系 Python核心进阶 文件读写 CSV读写 JSON读写 异常处理 try-except 自定义异常 面向对象编程 类与对象 继承与多态 模块与包管理 模块导入 __init__.py 目标:写出健壮、可维护、可扩展的Python代码

专注资料整理