第4章 流程控制:条件判断与循环
我在做振镜打标系统时,最早遇到的一个问题就是:程序怎么知道振镜准备好了?怎么控制打标顺序?说白了,这些都得靠流程控制来解决。写自动化脚本,不会控制流程,那就跟手动操作没区别。
这一章,我把条件判断和循环控制放在一起讲。因为它们在实际项目里从来不分家。你想想看,打标时是不是得先判断设备状态,然后循环处理每一个点位,遇到异常还得跳出或跳过?来,我们一个一个拆开说。
4.1 条件判断:if-elif-else
条件判断,说白了就是让程序"看情况办事"。在振镜打标里,最常见的场景是根据振镜的状态决定下一步操作。
基本语法长这样:
if 条件1:
执行代码块1
elif 条件2:
执行代码块2
else:
执行代码块3
条件从if开始,中间可以加多个elif,最后用else兜底。哪个条件先满足,就执行哪个分支,剩下的全跳过。记住,elif和else不是必须的,但if必须有。
关键记忆点: 条件判断是"短路"的——一旦命中一个分支,后面的就不再检查了。这在写多条件逻辑时特别重要。
运算符速查表
| 运算符 | 含义 | 振镜打标示例 |
|---|---|---|
== |
等于 | status == 'ready' |
!= |
不等于 | status != 'error' |
> |
大于 | curr_x > limit_x |
< |
小于 | count < max_count |
>= |
大于等于 | speed >= 100 |
<= |
小于等于 | offset <= tolerance |
and |
与 | ready and not error |
or |
或 | status == 'idle' or status == 'ready' |
not |
非 | not is_error |
实战案例:振镜状态机
我在项目中遇到过一种情况:振镜控制卡在某个状态不动了,程序却还在往下发指令。后来加了一段状态判断,问题就解决了。
status = get_galvo_status()
if status == 'ready':
start_marking()
print("开始打标")
elif status == 'busy':
print("振镜忙,等待中...")
wait_for_completion()
elif status == 'error':
print("振镜报错,执行复位")
reset_galvo()
else:
print("未知状态,重启通信")
restart_connection()
这段代码里,elif用了两次,对应三种已知状态。最后的else是兜底——不管前面漏了什么,都不会让程序卡死。
我的习惯:写条件判断时永远加一个else分支,哪怕里面只放一行日志。这样调试的时候一眼就能看出是否出现了预期之外的情况。
4.2 while循环
为什么要用while?举个例子:振镜在打标前需要移动到指定位置,但移动需要时间。你不能假设"指令发出就到了",得循环检测位置反馈,直到到达目标才往下走。
基本语法:
while 条件:
循环体代码
条件为True就继续循环,为False就退出。注意,如果条件永远为True,就变成死循环了。这在某些场景下是有意为之,但大多数时候是个坑。
实战案例:等待振镜就绪
import time
def wait_for_galvo(timeout=5.0):
"""等待振镜就绪,超时返回False"""
start = time.time()
while not is_galvo_ready():
if time.time() - start > timeout:
print("等待超时!")
return False
time.sleep(0.05)
return True
这个循环每50毫秒检查一次振镜状态。加了超时保护,不会无限等下去。我在项目里曾经吃过没加超时的亏——振镜硬件异常,程序卡在while里出不来,整个流程都冻住了。
我曾经踩过一个坑:写while循环时忘了更新条件变量,结果死循环把CPU跑满了。振镜控制是实时性要求高的场景,CPU被占满会导致通信丢包,打标位置偏移。所以while里一定要有让条件变化的语句,或者加time.sleep()让出CPU。
4.3 for循环
for循环用来遍历一个"可迭代对象"——列表、元组、字符串、range等。在振镜打标里,最常见的用法是遍历打标点列表。
基本语法:
for 变量 in 可迭代对象:
循环体代码
实战案例:遍历打标点
mark_points = [
(10.0, 20.0),
(30.0, 50.0),
(60.0, 80.0),
(100.0, 120.0)
]
for index, (x, y) in enumerate(mark_points):
print(f"处理第 {index+1} 个点: ({x}, {y})")
move_to(x, y)
fire_laser()
enumerate()是我很喜欢的一个内置函数,它同时返回索引和元素值,省得自己手动计数。
range() 的用法
需要按次数循环时,用range():
# 打标3次
for i in range(3):
print(f"第 {i+1} 次打标")
fire_laser()
range(3)生成0、1、2三个数,循环执行3次。还可以写range(1, 10, 2)表示从1到9,步长为2。
我建议:遍历列表时优先用for item in list,而不是用下标for i in range(len(list))。前者更简洁、更Pythonic,也不容易越界。
4.4 break与continue
这两个关键字是循环的"控制手柄"。它们解决的是同一个问题:循环不能一直按部就班地跑,中途需要干预。
- break:立即退出整个循环,不再执行剩余迭代。
- continue:跳过本次循环的剩余代码,直接进入下一次迭代。
实战案例:打标异常处理
points = [(10, 20), (30, 50), (-1, -1), (60, 80), (200, 300)]
for x, y in points:
# 检查坐标是否超出振镜范围
if x < 0 or y < 0:
print(f"跳过无效点: ({x}, {y})")
continue
if x > 150 or y > 150:
print(f"超出打标范围,停止打标: ({x}, {y})")
break
print(f"打标点: ({x}, {y})")
move_to(x, y)
fire_laser()
这段代码里:continue跳过负数坐标的点,程序继续处理下一个;break则在遇到超出范围的坐标时,直接停止整个打标流程。
为什么会这样设计?你想想看,坐标异常可能是数据错误,跳过就行;但超出硬件范围是严重问题,必须停下来保护设备。
区分要点: break是"不干了,全停";continue是"这个不行,下一个"。
嵌套循环中的break
注意,break只退出它所在的那一层循环,不是所有循环。多层嵌套时,需要逐层处理。
# 遍历多组打标数据
for group in mark_groups:
for point in group:
if is_fault(point):
print("遇到故障点,停止当前组")
break
else:
continue
break
这里用了for...else语法——循环正常结束(没被break)时执行else块;被break了就不执行。这个写法在Python里很常见,其他语言不一定有。
4.5 综合案例:振镜打标流程控制
把上面所有知识点串起来,写一个完整的打标控制函数:
def run_marking_job(points, max_retry=3):
"""执行打标任务,带状态判断和异常处理"""
retry_count = 0
completed = 0
while retry_count < max_retry:
# 检查振镜状态
status = get_galvo_status()
if status == 'error':
print("振镜异常,无法继续")
return False
elif status != 'ready':
print("振镜未就绪,等待...")
time.sleep(0.1)
continue
# 遍历打标点
for point in points:
x, y = point
if x < 0 or y < 0:
print(f"跳过无效点 ({x}, {y})")
continue
if not move_to(x, y):
print(f"移动到 ({x}, {y}) 失败,重试")
retry_count += 1
break
fire_laser()
completed += 1
else:
# 所有点都打完了
print(f"打标完成,共 {completed} 个点")
return True
# 如果走到这里,说明内层循环被break了
print(f"重试第 {retry_count} 次")
print("重试次数用尽,任务失败")
return False
这个函数用了while做重试控制,for遍历点位,if-elif-else判断状态,continue跳过无效点,break在失败时打断内层循环。你看,流程控制的几个要素全用上了。
记得我刚开始做振镜控制时,把逻辑写成了顺序执行——从头到尾一条线。结果一遇到异常就整个崩掉。后来学会了用条件判断和循环把代码组织成"状态机"的样子,鲁棒性一下就上去了。
重要提醒:实际项目中,振镜控制通常有硬件看门狗。如果你的Python程序因为死循环或阻塞导致通信超时,看门狗会强制复位硬件。所以写循环时一定要有超时或次数限制,别让程序"死等"。
好了,流程控制就讲到这里。下面这张图展示了本章所有知识点的关系,你可以把它当做一个"速查地图"来用。