05、Python流程控制与函数定义:从调试现场到工程实践

一、那个让我凌晨三点还在调试的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定义,可读性更重要。

六、调试技巧:我常用的几种方法

  1. 条件断点模拟
python 复制代码
for i in range(100):
    # 只想看i=47时的情况
    if i == 47:  # 临时插入的条件断点
        import pdb; pdb.set_trace()
    process_data(i)
  1. 函数执行时间测量
python 复制代码
import time

def slow_function():
    start = time.perf_counter()
    # ... 函数体
    elapsed = time.perf_counter() - start
    print(f"耗时: {elapsed:.3f}秒")
    return result
  1. 参数检查装饰器
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_xxxprocess_xxxvalidate_xxx这种形式最直观。

最后一点:写代码时想象半年后的自己(或者同事)来维护这段代码。那时候你可能已经忘了现在的实现细节,所以清晰的逻辑比聪明的技巧更重要。流程控制是代码的骨架,函数是肌肉,而可读性是灵魂。


(下一篇预告:006、Python数据结构深潜------列表、元组、字典与集合的工程应用)

相关推荐
Thomas.Sir2 小时前
第十一章:深入剖析 Prompt 提示工程
python·prompt
Fortune792 小时前
用Pandas处理时间序列数据(Time Series)
jvm·数据库·python
@haihi2 小时前
ESP32 MQTT示例解析
开发语言·网络·mqtt·github·esp32
艾莉丝努力练剑2 小时前
【Linux:文件】文件基础IO进阶
linux·运维·服务器·c语言·网络·c++·centos
2401_878530212 小时前
高级爬虫技巧:处理JavaScript渲染(Selenium)
jvm·数据库·python
2401_873544922 小时前
使用Black自动格式化你的Python代码
jvm·数据库·python
艾莉丝努力练剑2 小时前
【MYSQL】MYSQL学习的一大重点:表的约束
linux·运维·服务器·开发语言·数据库·学习·mysql
Fortune792 小时前
用Python破解简单的替换密码
jvm·数据库·python
程序猿编码2 小时前
基于ncurses的TCP连接可视化与重置工具:原理与实现(C/C++代码实现)
linux·c语言·网络·c++·tcp/ip