005-Python复合数据类型:列表、元组、字典、集合
昨天帮同事调试一段数据处理代码,问题出得挺典型。他需要合并两个设备上报的数据流,结果总是丢数据。我一看,他用列表累加,但没注意其中一个数据源偶尔返回None。这直接导致后续索引全乱——典型的复合数据类型使用不当引发的连锁反应。这类问题在嵌入式数据处理里太常见了,今天我们就系统聊聊Python里这几个核心容器。
列表:灵活但需要管住的手
列表用方括号,可动态增删,这是大家最熟悉的。但新手容易忽略它的可变性带来的副作用。
# 典型坑:在循环里修改列表长度devices=["sensor1","sensor2","sensor3","logger"]fori,devinenumerate(devices):if"logger"indev:devices.pop(i)# 这里踩过坑!循环中pop会导致索引错位# 实际运行时,最后一个元素可能被跳过# 安全做法:建新列表或倒序操作devices=[devfordevindevicesif"logger"notindev]列表推导式在处理嵌入式数据时特别实用。比如从原始字节流解析出一组温度值:
raw_bytes=[0x12,0x34,0x56,0x78,0x9A]temperatures=[(b[0]<<8|b[1])/10.0forbinzip(raw_bytes[::2],raw_bytes[1::2])]# zip把相邻字节配对,然后合并成16位整数再转换性能上要注意,列表在头部插入(insert(0, x))是O(n)操作。如果频繁在两端操作,考虑用collections.deque。
元组:不可变的契约
元组用圆括号,一旦创建就不能修改。这特性在嵌入式开发里其实是优点——保证数据不会被意外篡改。
# 定义设备配置:类型、地址、采样率sensor_config=("TMP117",0x48,10.0)# sensor_config[1] = 0x49 # 这会报错,正是我们想要的保护# 但注意:元组里的可变对象仍可变config=("I2C",[0x48,0x49],100)config[1].append(0x50)# 这居然可以!列表内容被改了# 所以元组的不可变是浅层的,设计时得想清楚函数返回多个值其实返回的就是元组:
defread_sensor():return25.3,0.95# 实际是 (25.3, 0.95)temp,confidence=read_sensor()# 自动解包字典:键值对的工程艺术
字典在嵌入式系统里常用来做寄存器映射、命令码表。它的查找是O(1),比列表遍历快得多。
# 寄存器地址映射表i2c_registers={0x00:"WHO_AM_I",0x20:"CTRL1",0x23:"STATUS",}# 安全访问:get方法避免KeyErrorreg_name=i2c_registers.get(0x30,"UNKNOWN")# 比直接 i2c_registers[0x30] 安全,尤其处理硬件寄存器时# 设置默认值更优雅fromcollectionsimportdefaultdict error_counts=defaultdict(int)# 不存在的键自动返回0error_counts["CRC_ERROR"]+=1# 直接加,不用先判断键是否存在字典推导式在数据转换时很高效:
# 反转键值对,用于反向查找name_to_addr={v:kfork,vini2c_registers.items()}内存注意:字典比较耗内存,在内存紧张的嵌入式环境里,如果键是连续整数,用列表可能更省空间。
集合:去重与集合运算
集合主要做两件事:去重和快速成员检查。硬件上报的数据常有重复,集合能天然处理。
# 从重复的传感器ID中提取唯一值raw_ids=[101,102,101,103,102,101]unique_ids=set(raw_ids)# {101, 102, 103},顺序可能变# 检查设备是否在允许列表中allowed_devices={"TMP117","BME280","SHT31"}current_device="TMP117"ifcurrent_deviceinallowed_devices:# O(1)查找print("Device supported")# 集合运算:找出失效的传感器configured={"sensor1","sensor2","sensor3"}responding={"sensor1","sensor3"}failed=configured-responding# 差集:{"sensor2"}注意集合本身可变,有不可变版本frozenset,可用作字典的键。
类型选择经验谈
实际工程中怎么选?我有几个习惯:
数据需要修改吗?不需要→优先考虑元组。元组的内存开销比列表小,创建也更快。
需要按键快速查找?需要→字典。哪怕键是整数,字典查找也比列表遍历快,除非数据量极小。
元素顺序重要吗?Python 3.7+字典保持插入顺序,但列表的序号访问更直观。
内存极度紧张?考虑用
array.array或bytes代替数字列表,特别是嵌入式环境。频繁判断“是否存在”?集合的
in操作是O(1),列表是O(n),数据量大时差异明显。
最后给个具体建议:处理硬件数据流时,我习惯用元组存原始数据(保证不被修改),用列表做中间处理,用字典做配置映射,用集合做状态去重。类型转换别怕开销,清晰的代码结构比那点微秒重要——调试时你就明白了。
下次我们聊聊这些容器在函数传参时的微妙行为,那又是另一个常见坑点。