一、那个让我凌晨三点还在调试的if-else
上周排查一个嵌入式设备日志解析的bug,问题出在温度阈值判断上。代码看起来很简单:
python
if temperature > 85:
print("高温告警")
elif temperature > 75:
print("温度偏高")
elif temperature > 65:
print("温度正常")
else:
print("温度偏低")
设备在72度时竟然触发"高温告警"------你能看出问题吗?我盯着屏幕看了半小时才反应过来:阈值顺序反了。当temperature=72时,第一个条件temperature > 85不成立,但第二个temperature > 75成立,所以......等等,72不大于75啊!
实际代码是这样的:
python
if temperature > 65: # 这里踩过坑:顺序错了
print("温度正常")
elif temperature > 75:
print("温度偏高")
elif temperature > 85:
print("高温告警")
else:
print("温度偏低")
教训 :if-elif-else的条件顺序决定了判断逻辑的优先级。写阈值判断时,要么从大到小(85->75->65),要么从小到大但用and连接区间。我现在都这样写:
python
if temperature > 85:
status = "CRITICAL"
elif 75 < temperature <= 85: # 明确区间,避免歧义
status = "WARNING"
elif 65 < temperature <= 75:
status = "NORMAL"
else:
status = "LOW"
二、循环里的那些"坑"
while循环:别忘了更新条件
早期写设备轮询时犯过这种错:
python
retry_count = 0
while retry_count < 3: # 死循环警告!
response = read_sensor()
if response is None:
print("读取失败")
# 忘记写 retry_count += 1 了!
嵌入式开发中,while循环经常配合硬件状态使用:
python
# 等待芯片就绪信号
timeout = 1000
while not gpio_read(READY_PIN):
timeout -= 1
if timeout <= 0:
raise TimeoutError("芯片启动超时")
time.sleep(0.001) # 必须加延时,否则CPU占用率100%
for循环:range的细节
python
# 遍历5次?不对,是0到4共5次
for i in range(5):
print(f"第{i}次循环") # 输出0,1,2,3,4
# 需要1到5时
for i in range(1, 6): # 记住:左闭右开
print(f"第{i}次")
# 倒计时怎么写
for i in range(5, 0, -1): # 第三个参数是步长
print(f"倒计时{i}")
三、函数定义:从工具函数到工程思维
参数传递的坑
python
def append_item(item, my_list=[]): # 别这样写!
my_list.append(item)
return my_list
print(append_item(1)) # [1]
print(append_item(2)) # 预期是[2],实际是[1, 2]!
默认参数在函数定义时就被创建了,所有调用共享同一个列表。应该这样写:
python
def append_item(item, my_list=None):
if my_list is None:
my_list = [] # 每次调用创建新列表
my_list.append(item)
return my_list
类型提示:不是摆设
Python是动态类型,但类型提示能让代码更健壮:
python
def parse_packet(raw_data: bytes) -> dict | None:
"""
解析传感器数据包
:param raw_data: 原始字节流
:return: 解析后的字典,失败返回None
"""
if len(raw_data) < 8:
logger.warning("数据包长度不足")
return None
# ... 解析逻辑
用VSCode或PyCharm时,类型提示能提供自动补全和错误检查。在团队协作中,这比写文档还有用。
返回多个值?其实是元组
python
def analyze_signal(data):
avg = sum(data) / len(data)
peak = max(data)
rms = (sum(x*x for x in data) / len(data)) ** 0.5
return avg, peak, rms # 这是个元组
# 接收时直接解包
mean, max_val, rms_val = analyze_signal(samples)
四、作用域:新手容易晕的地方
python
count = 0
def increment():
count += 1 # 报错!UnboundLocalError
def increment_correct():
global count # 声明使用全局变量
count += 1
但全局变量要慎用。更好的做法是:
python
class Counter:
def __init__(self):
self.count = 0
def increment(self):
self.count += 1
return self.count
五、lambda:匿名但不神秘
很多教程把lambda讲得很复杂,其实它就是简单的单行函数:
python
# 传统写法
def square(x):
return x * x
# lambda写法
square = lambda x: x * x
# 常用场景:排序
points = [(1, 3), (2, 1), (3, 2)]
points.sort(key=lambda p: p[1]) # 按y坐标排序
但别滥用,复杂的逻辑还是用def定义,可读性更重要。
六、调试技巧:我常用的几种方法
- 条件断点模拟:
python
for i in range(100):
# 只想看i=47时的情况
if i == 47: # 临时插入的条件断点
import pdb; pdb.set_trace()
process_data(i)
- 函数执行时间测量:
python
import time
def slow_function():
start = time.perf_counter()
# ... 函数体
elapsed = time.perf_counter() - start
print(f"耗时: {elapsed:.3f}秒")
return result
- 参数检查装饰器:
python
from functools import wraps
def validate_args(func):
@wraps(func)
def wrapper(*args, **kwargs):
for arg in args:
if arg is None:
raise ValueError("参数不能为None")
return func(*args, **kwargs)
return wrapper
@validate_args
def process_data(data):
# 现在不用担心data为None了
pass
七、个人经验:几个工程实践建议
关于if-else:当嵌套超过三层时,就该考虑重构了。可以用提前返回(early return)减少嵌套:
python
# 而不是
def process(data):
if data is not None:
if len(data) > 0:
if validate(data):
# 真正的逻辑在这里,缩进太深了
pass
# 改成
def process(data):
if data is None:
return
if len(data) == 0:
return
if not validate(data):
return
# 现在这里很清爽
关于循环:在嵌入式场景,避免在循环内分配大内存。比如处理串口数据时:
python
# 不好:每次循环都创建新列表
while True:
buffer = [] # 内存频繁分配释放
# 读取数据...
# 好些:复用缓冲区
buffer = bytearray(1024)
while True:
length = uart.readinto(buffer) # 复用内存
process(buffer[:length])
关于函数 :函数长度最好控制在一屏内(约30行)。如果函数太长,要么拆分子函数,要么考虑用类来组织代码。函数名要动词开头,get_xxx、process_xxx、validate_xxx这种形式最直观。
最后一点:写代码时想象半年后的自己(或者同事)来维护这段代码。那时候你可能已经忘了现在的实现细节,所以清晰的逻辑比聪明的技巧更重要。流程控制是代码的骨架,函数是肌肉,而可读性是灵魂。
(下一篇预告:006、Python数据结构深潜------列表、元组、字典与集合的工程应用)