3、Python基础回顾(上):数据类型与结构、控制流、函数与异常处理

各位同学好,我是老赵。今天咱们聊聊Python基础,这部分内容虽然基础,但却是整个SIL自动化测试框架的基石。你想想看,如果连数据类型都搞不清楚,后面写测试脚本肯定会踩坑。我这些年带团队,发现很多问题其实都出在最基础的地方。

3.1 数据类型与结构:选对容器,事半功倍

Python的数据类型,说白了就是用来装数据的容器。我个人习惯把常用的四种结构——list、dict、set、tuple——叫做「四大金刚」。它们各有各的脾气,用对了地方,代码写起来特别顺手。

3.1.1 List(列表):最灵活的序列

List是Python里最常用的数据结构,没有之一。它支持增删改查,还能嵌套。我在项目中经常用它来存储测试用例的集合。

# 定义一个测试用例列表
test_cases = ["CAN_001", "CAN_002", "LIN_001"]
test_cases.append("ETH_001")  # 追加
test_cases.insert(1, "CAN_003")  # 插入
print(test_cases[0])  # 索引访问
我的经验:在SIL测试中,我习惯用list来管理动态变化的测试序列。比如某个ECU的报文列表,因为测试过程中可能会动态增加或删除监控项,list的灵活性正好派上用场。

3.1.2 Dict(字典):键值对的最佳拍档

Dict是另一种我几乎每天都要用的结构。它通过键来查找值,速度非常快。我曾经在项目中用dict来存储信号映射表,比如把CAN信号的ID映射到物理值上。

# 信号映射字典
signal_map = {
    "0x123": "EngineSpeed",
    "0x456": "VehicleSpeed",
    "0x789": "BrakeStatus"
}
print(signal_map["0x123"])  # 输出:EngineSpeed
注意:Dict的键必须是不可变类型(如字符串、数字、元组)。我曾经见过有人用list做键,结果程序直接报错。记住,可变对象不能哈希。

3.1.3 Set(集合):去重与关系运算

Set的特点是无序且元素唯一。我在做测试数据清洗时特别喜欢用它。比如从一堆报文ID中找出重复的,或者求两个测试集合的交集。

# 去重示例
raw_ids = [0x100, 0x200, 0x100, 0x300]
unique_ids = set(raw_ids)
print(unique_ids)  # 输出:{0x200, 0x100, 0x300}

# 集合运算
set_a = {0x100, 0x200}
set_b = {0x200, 0x300}
print(set_a & set_b)  # 交集:{0x200}

3.1.4 Tuple(元组):不可变的序列

Tuple和list很像,但一旦创建就不能修改。我一般用它来存储固定配置,比如某个测试环境的IP地址和端口号,因为这些东西不应该被意外修改。

# 固定配置
server_config = ("192.168.1.100", 8080)
ip, port = server_config  # 解包
结构 可变性 有序性 常用场景
List 可变 有序 动态数据集合
Dict 可变 无序(3.7+有序) 键值映射
Set 可变 无序 去重、集合运算
Tuple 不可变 有序 固定配置

3.2 控制流:让代码有逻辑地跑起来

控制流就是告诉程序什么时候该做什么事。if、for、while这三个家伙,我几乎每天都在用。嗯,这里要注意,控制流写不好,代码就会像一团乱麻。

3.2.1 if条件判断

if语句用来做分支判断。我在写测试脚本时,经常用它来判断测试结果是否通过。

test_result = 85
if test_result >= 90:
    print("测试通过")
elif test_result >= 70:
    print("测试需复查")
else:
    print("测试失败")
避坑指南:我曾经在项目中犯过一个低级错误——把赋值号=写成了比较符==。结果条件永远为真,测试流程全乱了。记住,判断相等要用==。

3.2.2 for循环:遍历的利器

for循环用来遍历任何可迭代对象。我个人习惯用for来处理列表、字典等结构。

# 遍历测试用例
test_list = ["TC001", "TC002", "TC003"]
for tc in test_list:
    print(f"正在执行:{tc}")

# 遍历字典
signal_map = {"Speed": 100, "RPM": 3000}
for key, value in signal_map.items():
    print(f"{key}: {value}")

3.2.3 while循环:条件驱动

while循环适合在不确定循环次数时使用。比如等待某个信号值达到预期。

# 等待信号值变化
signal_value = 0
while signal_value < 100:
    signal_value = read_signal()  # 假设这是读取信号的函数
    time.sleep(0.1)
print("信号值已达标")
警告:while循环一定要有退出条件,否则就是死循环。我见过有人忘记更新循环变量,结果程序卡死,整个测试环境都得重启。

3.3 函数定义与作用域:模块化的基础

函数是代码复用的基本单元。我写测试框架时,会把每个测试步骤封装成函数,这样既清晰又便于维护。

3.3.1 函数定义

def calculate_checksum(data: bytes) -> int:
    """计算校验和"""
    checksum = 0
    for byte in data:
        checksum ^= byte
    return checksum

# 调用
result = calculate_checksum(b"\x01\x02\x03")
print(result)
我的习惯:我建议每个函数都加上类型注解和文档字符串。虽然Python是动态类型,但加了注解后,代码可读性会好很多,别人接手你的代码时也不会一头雾水。

3.3.2 作用域规则

Python的作用域遵循LEGB规则:Local、Enclosing、Global、Built-in。说白了,就是先找局部变量,再找外层函数变量,然后找全局变量,最后找内置变量。

global_var = "全局变量"

def outer():
    enclosing_var = "外层变量"
    def inner():
        local_var = "局部变量"
        print(local_var)  # 局部
        print(enclosing_var)  # 外层
        print(global_var)  # 全局
    inner()

outer()
注意:在函数内部修改全局变量时,要用global关键字声明。我曾经在项目中忘记加global,结果变量没改成功,排查了半天才发现问题。

3.4 异常处理机制:让程序更健壮

异常处理是测试脚本中必不可少的部分。你想想看,测试过程中什么意外都可能发生——网络断了、设备没响应、数据格式不对。如果没有异常处理,脚本可能直接崩溃。

3.4.1 try-except基本结构

try:
    result = 10 / 0  # 会触发ZeroDivisionError
except ZeroDivisionError as e:
    print(f"捕获到异常:{e}")
except Exception as e:
    print(f"其他异常:{e}")
else:
    print("没有异常时执行")
finally:
    print("无论是否异常都执行")

3.4.2 自定义异常

在测试框架中,我经常自定义异常来区分不同的错误类型。

class TestTimeoutError(Exception):
    """测试超时异常"""
    pass

class TestDataError(Exception):
    """测试数据异常"""
    pass

# 使用
try:
    raise TestTimeoutError("CAN报文超时")
except TestTimeoutError as e:
    print(f"测试超时:{e}")
避坑指南:我曾经在项目中滥用try-except,把所有异常都吞掉了。结果测试失败了也不知道原因。记住,异常处理不是用来隐藏问题的,而是用来优雅地处理问题。该打印日志就打印,该重新抛出就重新抛出。

3.5 知识体系总览

下面这张图是我自己整理的Python基础知识结构,你可以把它当作学习地图。

Python基础回顾 数据类型与结构 List Dict Set Tuple 控制流 if/elif/else for循环 while循环 函数定义与作用域 def定义 参数传递 LEGB作用域 异常处理机制 try-except finally 自定义异常 图:Python基础回顾知识体系

好了,这一章的内容就到这里。记住,基础不牢,地动山摇。把这些数据类型、控制流、函数和异常处理练熟了,后面写SIL测试脚本就会得心应手。