ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python流程控制全攻略:从三大结构到工程实战避坑指南

Python流程控制全攻略:从三大结构到工程实战避坑指南 如果只能用一个词回答“Python入门最难啃的是什么”我会选流程控制而不是某个具体语法。无论你是刚在 python官网下载安装完解释器、跟着 python安装教程 把环境跑通的新手还是已经能写爬虫、跑数据分析、日常调 numpy/sklearn 库的进阶用户最后都会发现程序能做的所有“智能”事情本质上就是“在什么条件下做什么、按什么顺序做、重复做多少次”这一整套规则就是流程控制。这篇文章我打算按工程实战的顺序来讲先讲三大基本结构再说分支判断的工程化写法然后展开循环背后的迭代思维接着把函数、生成器、异步和异常处理这些跟流程控制咬得很紧的内容串起来最后整理一份我在真实项目里踩过的坑和排查速查表。不管你现在处于 python入门 的哪个阶段这份内容都能直接拿来参考。1. 流程控制到底在控制什么三大结构的基本盘1.1 顺序结构程序默认的推进方式很多人一提到流程控制就只想到 if 和 for容易忽略最基础的顺序结构。顺序结构的意思很简单代码从上往下、一行接一行执行没有跳转、没有分支、没有循环。Python 是解释型语言文件的执行天然是顺序推进的除非你调用函数或者触发异常否则它会老老实实按行号往下走。我用一个最典型的入门题来说明python编程求长方体体积。length input(请输入长方体的长: ) width input(请输入长方体的宽: ) height input(请输入长方体的高: ) volume float(length) * float(width) * float(height) print(f长方体的体积是: {volume})这段代码没有任何分支和循环但里面藏着一个新手高频雷区input() 返回的一定是字符串你直接乘会得到字符串重复而不是数值所以必须做类型转换这里用 float() 把 str 变成浮点数。顺序结构看着简单它真正考验的是你对每一行数据状态的判断。我见过不少人写了半天最后报 TypeError问题就出在漏了类型转换而不是流程写错。再补充一点顺序结构不等于“线性死板”。当代码里出现函数调用、异常抛出时实际执行路径会暂时跳到别的地方再回来但从宏观视角看模块级代码依然是顺序推进的。所以写脚本时可以把“输入—处理—输出”当成三段式顺序流程先打印输入日志、再写处理逻辑、最后统一输出这种习惯在 python连接cmd 调用系统命令、写自动化脚本时尤其管用。1.2 条件分支if/elif/else 与判断条件的布尔逻辑如果说顺序结构是“一条路走到黑”那条件分支就是“前面出现岔路口”。Python 的分支语法固定是if 后面跟条件表达式、加冒号换行缩进写执行体需要多个条件时用 elif兜底用 else。price float(input(请输入当前价格: )) ma5 10.0 if price ma5: print(价格站上5日均线关注买入信号) elif price ma5: print(价格正好在均线上方向待定) else: print(价格在均线下方观望为主)这其实就是一个简化版的 python量化交易策略代码 骨架。真实策略里的信号生成无非就是把更多条件叠进来比如“价格突破布林带上轨并且成交量同步放大再确认”。注意判断条件的本质是布尔值Python 允许任何对象直接参与真值判断这一点在 2.2 会专门展开。还有个容易忽略的小语法链式比较。判断某个值是否落在区间里不用写成 x 0 and x 10可以直接写 if 0 x 10。这在处理 python结构化数据 时很常用比如判断一个用户的年龄、金额是否在合理区间可读性会好很多。1.3 循环结构for 与 while 的适用场景循环解决的是“重复做一件事”。Python 里有两个主角for 用于遍历一个已知的可迭代对象比如列表、元组、字典、rangewhile 用于“不知道要循环多少次只知道满足什么条件继续”。# for 循环遍历 range for i in range(3): print(i, end ) # while 循环猜数字直到猜对 secret 7 guess 0 while guess ! secret: guess int(input(请猜一个0-9的数字: )) print(猜对了)循环内部配合 break 和 continue 可以微调流程break 是立刻跳出整个循环continue 是跳过本次循环剩余代码直接进入下一次。很多人不知道 for 后面还可以跟 else它的含义是“如果循环正常走完没有被 break 中断就执行 else 块”。这个特性在搜索场景里很实用比如在列表里找目标找到了就 break如果整个遍历完都没找到就在 else 里给出提示。来看一道经典题李白打酒。题目大意是李白提着酒壶出门遇到店就把酒量翻一倍遇到花就喝一斗一共三遇店三遇花最后壶中酒正好喝光问原来壶里有多少酒。正推难算用流程控制倒推很快from fractions import Fraction wine Fraction(0) events [店, 花, 店, 花, 店, 花] for event in reversed(events): if event 花: wine 1 # 逆推把喝掉的那斗加回来 else: wine / 2 # 逆推进店前是现在的一半 print(wine) # 输出 7/8这道题在 python经典100编程题 和各类 python题库刷题训练 里经常出现它完美展示了循环 分支 类型转换的组合用法用 reversed 反转事件顺序用 if 判断当前事件是店还是花用 Fraction 避免浮点误差。类似的趣味题还有 python爱心代码、python中秋节祝福代码本质都是“循环控制坐标或字符输出”的练习。别看这些例子看起来娱乐练的其实是流程控制的节奏感。2. 分支判断的工程化用法从入门写法到防御式编程2.1 缩进与冒号Python语法里最容易翻车的两个地方先聊一个不是知识而是“肌肉记忆”的问题缩进和冒号。Python 用缩进划分代码块这一点和 C 系语言用大括号的方式完全不一样。新手最容易在 if、for、def 后面漏掉冒号或者把 Tab 和空格混在一起。你可能觉得这是小问题但报错信息往往不直观代码一多肉眼根本看不出哪里多了一个 Tab。我的建议是在 VSCode python环境配置 阶段就彻底解决这个问题。编辑器里开启“将缩进转换为空格”统一用 4 个空格表示一层缩进同时开启缩进提示。真实的工程环境里几乎没有正经项目会继续用 Tab 做缩进因为不同编辑器对 Tab 的渲染宽度不一致一换环境就是灾难。比缩进更值得警惕的是“嵌套地狱”。三层以上 if 套 if代码读起来就是俄罗斯套娃。我处理这种代码的原则是嵌套深度超过三层一定抽成函数或者用“守卫子句”提前返回把异常分支丢到前面。下面这段代码就有点危险# 不推荐嵌套太深 if data is not None: if data.get(status) 200: if items in data: for item in data[items]: process(item)同样的逻辑完全可以用更平的方式表达这就是 4.2 要讲的守卫子句。流程控制写得好不好不在于你用了多少 if而在于你让 if 变得多么容易被读出来。2.2 条件表达式的布尔陷阱与类型转换if 判断的其实是“真假”而 Python 中有很多东西都能参与真值判断0、0.0、、空列表 []、空字典 {}、None、False 都会被认为是“假”其余一切值默认是“真”。这是 python入门 阶段最重要的底层规则之一但很多人是从踩坑中才真正记住的。最常见的坑就是分不清 “if not data” 和 “if data is None”。前者判断的是“这个对象是否为假”空字符串、空列表、0 都会命中后者只判断“是否严格等于 None”。假如你要从配置文件里读取一个开关值0 本来是一个合法值写成 if not config_value 就会把它误判为“未配置”这个 bug 能让你排查半天。再说类型转换。python类型转换 不只是 int()、float()、str() 这几个内置函数还包括 bool()、list()、tuple()、dict()、bytes() 等。流程控制里最痛苦的 TypeError 往往出现在“类型不自知”的时候API 返回的字段一会儿是字符串一会儿是浮点Excel 读出来的日期可能是 datetime 对象也可能是字符串。我的经验是在关键入口处统一做一次转换后面所有分支都基于转换后的类型判断不要指望中途每次判断类型。raw input(请输入年龄: ) try: age int(raw) except ValueError: age 0 if 0 age 150: print(f年龄合法{age}) else: print(年龄不合法)这里 int(raw) 可能抛 ValueError所以用异常处理兜底这就是流程控制里“防御式编程”的雏形。后面第 6 章会专门展开 try/except。2.3 多分支匹配match/casePython 3.10如果你的 Python 版本是 3.10 及以上可以试试 match/case它比 if-elif-else 链更适合处理“固定多值”的判断。比如处理 HTTP 状态码、命令解析、菜单选择status 404 match status: case 200: print(OK继续解析) case 404: print(页面不存在) case 500 | 502 | 503: print(服务端异常稍后重试) case _: print(未知状态码)注意 case 后面的分支条件可以合并写用竖线表示“或”_ 相当于默认分支。match 不只是帮你少写几个 if它更擅长“结构化模式匹配”可以直接解包列表、字典里的字段。但我要提醒一句如果你用的还是 python 3.8 这种老版本这个特性用不了老老实实写 elif 即可不要为了新语法把项目基础版本硬往上拉。从 python官网下载 安装新版本很容易但公司生产环境的解释器版本往往由运维统一管理所以写代码前先敲 python -V 确认版本再决定要不要用 match/case。3. 循环背后的迭代哲学列表、字典、数组切片与推导式3.1 for 循环的可迭代对象与数组切片for 循环能遍历的远不止列表。字符串、元组、字典、集合、range、文件对象、pandas 的 DataFrame 列、数据库查询游标几乎一切“可迭代对象”都能放进 for。这也是 Python 相比很多语言更省心的地方你不用关心下标怎么递增for 会自己拿下一个元素。真正需要手动控制“步长”的场景靠的是切片lst[start:stop:step]。切片就是流程控制里“跳跃式访问”的工具它能精确控制你遍历一段序列的范围。举个实战例子python画图横坐标太密集这个问题在 matplotlib 里很常见当你有一千个点的横坐标直接画出来刻度全部挤成一团黑线。处理办法就是用切片抽稀刻度import matplotlib.pyplot as plt x list(range(1000)) y [v ** 2 for v in x] plt.plot(x, y) plt.xticks(ticksx[::100]) # 每100个刻度显示一个 plt.title(每隔100个刻度显示) plt.show()x[::100] 的含义是从开头到结尾、步长为 100这就是用切片参与流程控制的典型操作。再比如反转列表 x[::-1]、取前 5 个元素 x[:5]切割的本质就是构造一个子序列交给后续的 for 或函数处理。3.2 字典遍历与构建邻接矩阵案例字典是 Python 里流通性最强的数据结构遍历它有好几种姿势默认 for key in d 拿的是键for value in d.values() 拿值for key, value in d.items() 同时拿键值对。如果你同时需要序号和元素就再套一层 enumerate这个组合在打印表格、写入 Excel、生成报告时非常顺手。图论里有一个经典练习叫 python构建邻接矩阵。假设有 5 个节点节点之间有一组边要用二维列表表示谁和谁相连n 5 edges [(0, 1), (1, 2), (2, 0), (3, 4)] matrix [[0] * n for _ in range(n)] for u, v in edges: matrix[u][v] 1 matrix[v][u] 1 # 无向图对称填充 for row in matrix: print(row)初看这只是双重循环实际它把流程控制的两个要素都练了外层遍历边集内层列表推导式负责初始化。处理 python结构化数据 时很多“关系型”问题都可以先抽象成邻接矩阵或邻接表然后用循环去填。数据量小的时候这是直观方案数据量大了就换成 pandas DataFrame 或 numpy 数组核心逻辑其实没变。3.3 列表推导式一行替代三行列表推导式是流程控制的“压缩写法”把 for 循环和 if 条件合并到一行里。基础语法是 [表达式 for 变量 in 可迭代对象 if 条件]读的时候从 for 往左看先看循环来源再看过滤条件最后看你要生成什么。# 普通写法 result [] for i in range(1, 21): if i % 2 0: result.append(i ** 2) # 推导式写法 result [i ** 2 for i in range(1, 21) if i % 2 0]推导式的价值不只是少写两行它更接近数学上的“集合描述”可读性反而更好。遇到复杂逻辑时我建议先用普通循环写通确认没问题再抽成推导式不要一开始就在一行里塞三个 for 两个 if那是给自己找罪受。推导式在 numpy、sklearn 相关的科学计算代码里也频繁出现比如批量生成特征列、构造样本组合配合数组切片用起来效率很高。不过要记住推导式本质还是 Python 层的循环追求极致性能时应该尽量用 numpy 的向量化操作替代比如 np.where、np.vectorize这是数据科学里一个非常重要的性能意识。3.4 enumerate 和 zip 的配合使用两个配合 for 循环的高频函数值得单独说说enumerate 和 zip。enumerate 能同时拿到下标和元素替代手动维护计数器names [张, 李, 王] for idx, name in enumerate(names, start1): print(idx, name)zip 能把多个可迭代对象按位置“拉链”式组合起来并行遍历headers [姓名, 得分] scores [80, 95] for h, s in zip(headers, scores): print(h, s)在 python爬虫 解析表格、python写入excel 的时候这两个函数几乎是标配表头和数据行用 zip 对齐序号用 enumerate 补上。写循环时如果发现自己手写了一个计数器 i 1大概率可以改用 enumerate如果发现自己用下标去访问另外一个列表大概率可以改用 zip。这不是炫技是让流程更直观。4. 函数与流程控制的深度联动多写一个def少写一堆if4.1 定义函数的参数规则python定义函数 是承接流程控制的最好方式。函数把一段流程封装起来给它起个名字之后每次调用相当于复用这套流程。参数规则是绕不开的基本功位置参数按顺序传关键字参数按名字传可以不按顺序默认参数def test(a, b10)不传时用默认值可变参数 *args接收任意多个位置参数打包成元组关键字参数 **kwargs接收任意多个关键字参数打包成字典。有个经典陷阱默认参数不要用可变对象。def add_item(item, bucket[]) 这个 [] 只会创建一次多次调用会共享同一个列表结果第二次调用时 bucket 里还留着上一次的内容。正确的做法是写成 def add_item(item, bucketNone):然后在函数内部再判断这本质上就是一个流程控制的守卫逻辑def add_item(item, bucketNone): if bucket is None: bucket [] bucket.append(item) return bucket4.2 流程控制在函数中的应用返回值替代多层嵌套函数内部最常见的问题是按部就班从上往下堆 if堆到后面连作者自己都分不清哪个分支是主逻辑。我在 2.1 提过守卫子句这里细讲一下实现方式。核心思想把所有的异常、边界、错误条件提前 return 掉让主流程留在最后代码会平铺直叙、非常清晰。def fetch_data(source): if source is None: return None if not isinstance(source, dict): return None if source.get(status) ! 200: return None return source.get(data)你看每个异常条件一行 return没有层层包裹。很多 python连接公司系统自动拉表 的脚本就是从这类函数演化来的先判断参数是否合法、再判断接口返回码、最后才进入核心的数据处理流程。把这些判断拆到函数里主流程会显得很干净。另一个常用技巧是“返回值 兜底参数”。比如处理一个外部输入时可以约定返回 None 表示“没有结果”调用方再根据 None 决定下一步。这样流程控制就不只是写在一个函数里而是通过函数的返回值在多个函数之间传递整体逻辑自然串成一条线。4.3 递归与分治计算流程的自我重复函数调用自己是递归递归是流程控制里比较高级的分支。它天然适合解决“大问题可以拆成小问题、小问题和大问题结构相同”的场景比如目录遍历、树形结构、分治排序。经典的就是斐波那契数列def fib(n): if n 1: return n return fib(n - 1) fib(n - 2)递归的边界条件就是流程控制里的退出条件。写递归时我最怕的不是递归本身而是忘记边界导致无限递归最后抛 RecursionError。Python 默认递归深度有限制通常是 1000可以用 sys.getrecursionlimit() 查看实在需要更深的递归再临时调大但一般不建议因为 Python 的递归性能并不好。很多 python经典100编程题 都会给出递归版和循环版双解我强烈建议初学者都写一遍这对理解“循环能做的事递归也能做递归能做的事循环不一定方便”帮助很大。5. 生成器、协程与异步更高阶的流程调度5.1 yield生成器惰性求值如何改变流程控制函数里出现了 yield 关键字它就不再是普通函数而是一个生成器函数。调用生成器函数不会立即执行内部代码它返回一个生成器对象只有当你 for 遍历它或调用 next() 时代码才会真正执行到第一个 yield然后暂停、保存现场、等你再次索要下一个值。这个“暂停-恢复”机制是流程控制的高级形态。它能解决大数据量场景的内存爆炸问题。比如你想处理一个 10GB 的日志文件一次性 readlines 会直接让内存爆掉正确做法是逐行读取并逐行 yielddef read_large_file(path): with open(path, r, encodingutf-8) as f: for line in f: yield line.strip() for row in read_large_file(huge.log): process(row)这里 for 循环天然支持生成器你感觉不到内部有暂停但它确实避免了一次性加载所有行。在数据科学、日志处理、实时流处理的场景里生成器几乎是标配。除了生成器函数还有生成器表达式(x * 2 for x in range(10))。它和列表推导式的区别是惰性求值不会一次性生成整个列表适合管道式处理。5.2 协程与async/await并发流程再进阶一层是协程。用 async def 定义的函数叫协程函数内部用 await 挂起等待耗时操作。Python 的异步流程控制核心是事件循环asyncio.run() 启动asyncio.gather() 并发调度多个协程。最典型的使用场景是 python爬虫 批量抓取网页网络请求是 IO 密集操作等待响应的间隙可以切到别的请求整体速度从“串行”变“并发”。import asyncio import aiohttp async def fetch_status(session, url): async with session.get(url) as resp: return resp.status async def main(): urls [ https://example.com, https://example.org, https://example.net, ] async with aiohttp.ClientSession() as session: results await asyncio.gather( *(fetch_status(session, url) for url in urls) ) print(results) asyncio.run(main())这里要注意async/await 不是“并行计算”它是“并发调度”。在单线程里多个协程交替执行谁在等 IO 谁就挂起让出控制权。如果代码里出现 CPU 密集的大循环异步并不会加速反而会占用事件循环、挡住其他任务。想加速 CPU 密集逻辑应该上多进程或 numpy 向量化方向别搞错了。5.3 别急着上一整套异步我见过不少朋友学完协程之后恨不得把所有脚本都改成 async。我的建议是脚本规模小、IO 次数少串行 for 循环就够了只有当你需要同时发起几十个甚至上百个网络请求、且每个请求都很慢的时候才值得用异步。异步的调试难度和维护成本比同步代码高一个量级控制台里堆满未处理的 coroutine 警告时你会怀念简单 for 循环的。如果你确实要用异步写爬虫还要注意并发上限。几十个协程一起发请求容易把目标服务器打挂也会触发对方的风控。用信号量asyncio.Semaphore控制并发数量是成熟项目的标准做法。这部分属于“流程调度”的进阶设计思路和循环里的 break/continue 一脉相承控制总量、控制节奏、控制退出条件。6. 异常处理让流程控制在报错时不崩盘6.1 try/except/finally的逻辑结构正常流程之外还有一个必须掌握的流程控制异常处理。很多新手把 try/except 当成“出错了再说”其实异常处理的本质是“预设一条备用路径”——主路径一旦出错程序跳转到备用路径而不是整个崩溃。try: result int(raw_value) print(f转换成功{result}) except ValueError: print(转换失败raw_value 不是数字) finally: print(无论成功失败这里都会执行)这里有三个注意点。第一except 要尽量精确不要只写一个裸 except:那样会连 CtrlC 中断、系统内存错误也一起吞掉排错的时候完全不知道发生了什么。第二except 可以有多个按异常类型从上往下匹配先写子类异常再写父类异常。第三finally 块适合放清理操作比如关闭连接、关闭文件、记录日志不管前面是正常 return 还是异常抛出让出了函数它都会执行。在 python连接Oracle查询数据、python连接公司系统自动拉表 这类场景里数据库连接失败、SQL 执行超时、类型转换失败都是家常便饭正确的做法就是连接层用 try 捕获超时重连数据处理层单独捕获类型错误最外层把日志写得足够详细。6.2 raise主动抛出业务异常被动接收异常只是异常处理的一半主动 raise 是另一半。所谓防御式编程就是在进入流程主逻辑之前先做校验不合法就主动抛出异常让调用方知道“这个输入有问题我不处理它”。def parse_age(text): if text is None or not text.strip(): raise ValueError(年龄文本不能为空) age int(text) if age 0 or age 150: raise ValueError(f年龄超出合理范围: {age}) return age这里等价于一个“前置条件检查”的流程控制不满足条件就中断。好处是错误发生时你能给出明确原因而不是等到业务逻辑深处才出现莫名其妙的报错。尤其写开源库、写公共工具函数的时候主动 raise 是接口规范的一部分调用方可以根据异常类型决定是提示重试还是记录失败。6.3 with上下文管理资源清理的流程控制操作文件、数据库连接、网络会话时最容易出现“打开了没关闭”的资源泄漏。with 语句是 Python 里最优雅的资源清理流程控制它会在代码块结束时自动调用对象的exit方法把关闭动作交给你不用担心with open(result.txt, w, encodingutf-8) as f: f.write(写入一行数据) # 出了 with 块文件自动关闭用 python写入excel 时也是这个套路。openpyxl 的 Workbook 保存后要 closepandas 的 ExcelWriter 也要用 with 包起来否则缓存可能会没刷到磁盘。写文件失败时with 还能保证句柄释放不会把文件锁在那里。如果你写的是自定义资源类也可以实现enter和exit两个魔法方法把资源释放逻辑收拢到一个地方。6.4 重试循环异常处理与循环的经典结合异常和循环结合可以写出非常实用的重试机制。很多接口调用、数据库连接、网络请求都有瞬时失败的情况直接抛错误退出太粗暴合理做法是在循环里做有限次重试次数用完再抛异常。import time def fetch_with_retry(url, max_retry3): for attempt in range(1, max_retry 1): try: resp requests.get(url, timeout10) if resp.status_code 200: return resp.text if resp.status_code 500 and attempt max_retry: time.sleep(2 * attempt) continue resp.raise_for_status() except requests.RequestException as e: if attempt max_retry: raise time.sleep(2 * attempt) return None这里的流程是尝试请求 → 判断状态码 → 决定是立即返回、指数退避重试、还是最终抛出。重试时的 sleep 时间逐渐加大2 秒、4 秒、8 秒避免在服务端还没恢复时疯狂打请求。这个模式在 python爬虫、对接外部接口、自动拉表任务里非常好用也是工程代码和练习代码最明显的分水岭之一。7. 常见坑与实战排查我写流程控制时翻过的车7.1 循环变量泄漏与闭包延迟绑定Python 的 for 循环变量在循环结束后不会自动销毁它仍然存在于当前作用域里。for i in range(3): pass print(i) # 输出 2而不是报错这个特性有时候会让代码行为变得意外。如果循环之前有一个同名变量循环会把它覆盖掉如果循环一次都没执行比如可迭代对象是空的i 可能保存着旧值。写脚本时最好别依赖这个行为循环结束后的变量状态不要做任何假设。更隐蔽的是闭包延迟绑定。在循环里创建 lambda 函数如果用到循环变量所有函数捕获的是同一个最终值funcs [lambda: i * 2 for i in range(3)] print(funcs[0]()) # 4而不是 0解决办法是给 lambda 增加默认参数lambda xi: x * 2。这个坑在绑定回调、注册事件处理器时特别常见一旦业务逻辑复杂起来排查成本很高。7.2 死循环与计算量失控写了 while 忘记递增、忘记 break程序就会空转CPU 风扇直接起飞。最简单的排查方法是先跑小规模测试确认循环一定能退出再加上保险机制总循环次数上限、最大等待时间、日志打印。另一个容易失控的是嵌套循环的规模。比如大数据集里做双重循环100 万元素的笛卡尔积就是一万亿次操作再怎么优化 Python 也没戏。遇到这种问题先看能不能用 numpy、pandas 的向量化操作替代再看能不能通过字典索引、集合去重减少循环次数。python科学计算和数据科学应用 里反复强调向量化就是因为 Python 层 for 循环在数据量面前是最先崩掉的环节。调试性能问题我习惯用 tqdm 显示进度from tqdm import tqdm for item in tqdm(data): process(item)一眼看到进度条卡在哪往往就能定位到是哪个循环有问题。7.3 None、空值与守卫子句第三类高频坑是 None 和空值的乱入。None 不是 False不是 0不是空字符串它就是“没有值”这个状态。很多人写 if not result 想判断“结果不存在”结果 result 是数字 0 的时候也被挡掉了反过来用 if result is None 才能精确判断“没有返回”。我的建议是写流程控制之前先明确你到底要排除的是 0、空字符串、空列表、还是 None不同目标用不同的判断写法。处理字典时优先用 d.get(key, default) 而不是直接 d[key]前者取不到键时返回默认值流程可以继续走。处理列表时先确认可能为空的场景循环天然可以处理空列表但如果循环体里有 list[0] 这种取第一个元素的操作空列表就直接 IndexError。流程控制的健壮性往往就体现在这些“空值边界”上。7.4 流程控制速查表一个表把写法备齐最后整理一份速查表涵盖了这篇文章出现的主要写法适合贴在旁边随手查需求写法备注互斥多条件判断if / elif / else从上往下匹配固定值多路匹配match / casePython 3.10遍历未知长度数据for item in iterable任何可迭代对象需要下标遍历for i, item in enumerate(seq)start 可指定起始值并行遍历多个序列for a, b in zip(seq1, seq2)以短序列为准跳过本次循环continue立刻进入下一轮立刻结束循环break跳出整个循环循环正常结束才执行for … else被 break 中断则不执行按固定步长取子序列seq[start:stop:step]步长为负可逆向延迟生成大量数据yield生成器函数并发调度协程await / asyncio.gatherIO 密集场景资源自动清理with open(...) as f自动调用exit7.5 写完流程控制后的三问自查文章写到这教程部分基本结束我还想分享一个自己坚持了很久的习惯每次写完一段包含循环和分支的代码不急着运行先在心里问三个问题。第一这个循环一定会在有限次内结束吗退出条件是否可达是不是存在某个输入会让 break 永远不会触发第二每个分支都有兜底吗if 有 else、try 有 except、循环有提前 return这些出口是不是都覆盖到了第三如果输入是空值、None、0、空字符串程序会怎么走是崩掉还是优雅跳过这三问看起来简单但真的能筛掉大多数 bug。我日常排查线上问题时十次里有八次最后都落在“某个边界分支没处理”上而不是算法本身想错了。还有一个提效小技巧条件复杂时把判断抽成命名清晰的函数比如 def is_trading_time(now):、def is_valid_payload(data):流程控制代码读起来就会像句子一样顺别人接手你的项目也会轻松很多。这些经验是写再多 demo 都换不来的希望对你有用。
返回列表