☰
Python条件与循环详解:从基础语法到工程避坑指南
2026/10/1 5:06:56 网站建设 项目流程

很多朋友初学 Python 的时候都有一种错觉:语法这么简单,看两眼就会了。可一旦真去写一个自动化脚本、做一个数据处理,或者写个爬虫,就发现代码开始不听话。其实十个人里有八个,问题不在语法本身,而在于看到if、for、while的时候,脑子里没有一张清晰的“执行逻辑图”。

这一篇我准备把 Python 的条件语句和循环语句一次性讲透。会从最基本的写法讲到工程里常见的坑,再给一些可以照抄的实操案例。不管你是刚入门想补基础,还是写过一阵子想系统梳理控制流,这篇都值得收藏跟着敲一遍。毕竟在 Python 入门、Python 基础语法这些话题里,真正能卡住新手的地方,百分之八十都集中在条件判断和循环控制上。

1. 条件与循环的底层逻辑:为什么语法简单却威力巨大

1.1 程序流程的三种结构,条件循环属于哪一环

任何程序,不管多复杂,本质上都由三种结构组合而成:顺序结构、选择结构、循环结构。顺序结构就是一行一行从上往下执行,这个不用多解释;选择结构就是“看情况走哪条路”,对应到 Python 就是if条件语句;循环结构就是“反复做同一件事”,对应到 Python 就是for和while循环语句。

理解了这一点,你再看 Python 代码就不会觉得章节是散的。一个 web 接口要校验参数,那层校验逻辑本质就是条件语句;一个量化策略要回放历史行情,那层 K 线遍历本质就是循环语句;爬虫去翻页抓数据,看起来又是循环又是条件,其实也只是把这两种结构嵌套在一起用。

所以我会建议所有学 Python 的人,不要为了写循环而写循环,也不要把条件语句当成语法知识点去背。你应该想清楚一件事:代码在哪个节点需要做决策?在哪个范围内需要重复执行?一旦这两件事想明白了,代码结构自己就浮出来了。

1.2 这篇文章适合谁,看完能解决什么问题

如果你刚开始接触 Python,刚看完变量、数据类型那几章,那这篇正好可以帮你把流程控制的地基打牢。条件语句和循环语句是继变量之后最核心的“语法骨架”,所有后续的函数、类、文件操作、异常处理都建立在它们上面。

如果你已经写过一段时间 Python,但总觉得代码重复度高、逻辑绕、改起来头疼,这篇里的常见坑和工程习惯也会很有帮助。比如遍历列表时修改列表导致的诡异结果、while循环忘记写退出条件导致程序卡死、条件判断的顺序不对导致结果和预期反着来——这些问题在各类 Python 教程里经常被一笔带过,但实际项目里非常常见。

我写的实操案例会贴近真实场景,比如数据清洗里的空值过滤、批量计算里的遍历、交互输入里的重试处理。如果你后续想做数据分析、爬虫、自动化脚本,这些模式基本都会遇到。

2. 条件语句全解析:if、elif、else 的写法与思考方式

2.1 基础语法:缩进、执行时机、真值判断

先看最简单的结构:

score = 78 if score >= 60: print("及格了") print("继续保持") print("不管怎样都会执行")

这段代码有两个关键点要注意。

第一,Python 用缩进表示代码块。if后面的print("及格了")和print("继续保持")前面有缩进,它们属于if满足条件时才执行的块;最后一个print没有缩进,所以不管条件是否成立都会执行。这是 Python 和 C、Java 用大括号{}表示代码块的一个很大区别。不少从其他语言转过来的朋友,写 Python 时最喜欢犯的错就是缩进长度不统一,明明看起来对齐了,一运行爆个IndentationError。

第二,条件语句在运行到这里时只会检查一次。if score >= 60这个判断成立之后,直接进入分支块执行,执行完跳出整个 if 结构继续往下走。它不会像循环那样多次检查。所以你要是想写出“每隔一段时间判断一次”的逻辑,不能光靠if,得搭配循环或者定时器。

再聊聊 Python 的真值判断。条件不一定非要比较运算符,可以直接写一个变量。Python 里None、0、0.0、空字符串""、空列表[]、空字典{}、空集合set()都会被当成False,叫“假值”;其他情况都算True。所以很多老 Python 程序员写文件读取判断时,会直接写:

data = get_data() if data: print("有数据,开始处理") else: print("没有数据")

这不只是简化写法,而是一种约定俗成的习惯。你在看别人代码时,要是看到if not data、if items这种,要立刻反应过来这是在判断“是否为空”,而不是在做数值运算。

2.2 三元表达式、链式比较与布尔运算优先级的坑

条件语句除了完整的if ... elif ... else写法,还有几个高频出现的变体。

三元表达式长这样:

status = "成年" if age >= 18 else "未成年"

这个表达式的意思是,如果age >= 18成立,就把"成年"赋给status,否则赋"未成年"。它是为了处理“二选一赋值”这种简单场景。可读性比写一整段 if/else 更好,但千万别在里面套复杂逻辑,否则别人读代码的时候只想打人。

链式比较是 Python 一个讨喜的小设计,比数学还要直观:

if 18 <= age < 60: print("青年至中年阶段")

这段等价于if age >= 18 and age < 60。Python 允许这种写法,其实底层是把比较拆成两次,但逻辑上更符合日常表达。

说到and和or,这里有个非常容易踩的优先级坑。and的优先级高于or。看这个例子:

a = True b = False c = False result = a or b and c

因为先算b and c得到False,再算a or False得到True。如果你本来想表达(a or b) and c,那结果就完全不一样了。我见过不少人在写复杂权限判断时栽在这里。解决方案很土但有效:拿不准的时候加括号。代码里多几层括号不丢人,逻辑错了才丢人。

2.3 实操案例:用条件语句做业务评分和数据清洗

举一个典型的业务评分案例。假设要根据考试成绩划分等级,90 以上为 A,80 到 89 为 B,70 到 79 为 C,60 到 69 为 D,其余为 E:

def get_grade(score): if score >= 90: return "A" elif score >= 80: return "B" elif score >= 70: return "C" elif score >= 60: return "D" else: return "E" print(get_grade(82)) # B print(get_grade(59)) # E

写这种连续分级的判断时,顺序很重要。要按范围从大到小写,或者从小到大写,千万不要把顺序写乱。比如你先把score >= 80写在前面,那 90 分也会进入这个分支,输出 B,结果就错了。这正是条件语句里最典型的“执行顺序”问题:elif是按顺序依次检查的,一旦某个条件成立,后面的elif通通不会再看了。

再举一个数据清洗场景。假设你拿到了一个含混合类型的列表,里面可能有空字符串、None、数字和布尔值,现在要筛出可以参与数值计算的元素:

raw = ["3", None, "", 0, 5.5, False, "8", " "] clean = [] for item in raw: if isinstance(item, str) and item.strip(): clean.append(float(item)) elif isinstance(item, (int, float)) and not isinstance(item, bool): clean.append(item) print(clean) # [3.0, 5.5, 8.0]

这段里有两个值得注意的细节。第一,空字符串和只含空格的字符串要用.strip()处理,因为""是假值没错,但" "是真值,直接if item会漏掉这类脏数据。第二,Python 里bool是int的子类,isinstance(True, int)成立,所以要把布尔值单独排除掉。这种细节不对照实际数据搞一次,光看教程真发现不了。

3. 循环语句之 for:从遍历到批量处理

3.1 for 循环的运行机制与 range() 函数的用法

for循环是 Python 里最常用的循环语句,但它的底层思维和 C 语言不太一样。C 语言里的for更像“执行 N 次”,而 Python 的for本质是“遍历一个可迭代对象”。字符串、列表、元组、字典、集合、文件对象,只要能被迭代,都能放进for里。

for name in ["小明", "小红", "小刚"]: print(f"你好,{name}")

这个循环每轮从列表里取一个元素,赋给变量name,然后执行缩进块里的代码。取完最后一个元素,循环自动结束。

很多新手刚接触循环时,喜欢用下标遍历:

fruits = ["苹果", "香蕉", "草莓"] for i in range(len(fruits)): print(i, fruits[i])

这个写法没错,但不够 Pythonic。range(len(...))是 C 时代思维的遗留,在 Python 里一般会用enumerate替代,后面我会专门讲。不过range()本身是个高频工具,还是得掌握它的三种用法。

range(stop)表示从 0 到 stop-1,比如range(5)得到 0、1、2、3、4;range(start, stop)表示从 start 到 stop-1;range(start, stop, step)里的 step 可以是正数也可以是负数。比如range(1, 10, 2)得到 1、3、5、7、9,range(10, 0, -2)得到 10、8、6、4、2。

需要注意的是range(5)生成了一个range对象,它不是列表,但支持迭代。判断一个数在不在某个等差序列里也可以用in操作,因为range实现了相应的成员判断逻辑,效率也很高。

3.2 遍历增强技巧:enumerate、zip、dict.items()

光会for i in xxx只能算入门,真正写项目时会经常需要同时拿元素和索引、同时遍历两个列表、或者遍历字典的键和值。

enumerate解决“既要索引又要元素”的问题:

products = ["手机", "电脑", "耳机"] for idx, product in enumerate(products, start=1): print(idx, product)

第二个参数start=1很实用,写报表、打印序号时不用自己手动维护计数器了。你可能会问,Python 里有没有类似i++的写法?没有,因为迭代才是 Python 的风格。每次循环从可迭代对象里取出一个值,不需要你操心自增的问题。

zip解决“同时遍历多个列表”的问题:

names = ["张三", "李四", "王五"] scores = [88, 92, 73] for name, score in zip(names, scores): print(f"{name}: {score}")

这里zip会把两个列表按位置“拉链”一样拼起来,每轮循环拿到一个名字和对应的分数。它的余数是 Python 一个很经典的行为:当两个列表长度不一致时,zip以短的为准。如果你希望以长的为准,缺失的位置用默认值补齐,那要用itertools.zip_longest,不过我实际项目中用到它的频率不高。

遍历字典是另一个高频场景。直接for key in my_dict拿到的是键;要拿值就用.values();键和值都要,用.items():

user_info = {"name": "王五", "age": 25, "city": "成都"} for key, value in user_info.items(): print(key, "=", value)

这里需要注意,Python 3.7 之后字典能保证插入顺序,所以遍历顺序和插入顺序一致。这在以前可是没有任何保证的,如果你在维护老项目,遇到字典遍历乱序的问题,升级版本后多半会好。

3.3 实操案例:用 for 循环批量处理结构化数据

for循环最大的价值在于批量操作。我拿一个常见的“结构化数据”处理场景来演示。假设你从接口拿到一批用户数据,每个用户是一个字典,字段有name、age、spend,现在要做两件事:筛掉未成年用户,对剩余用户的消费额做汇总。

users = [ {"name": "张三", "age": 25, "spend": 120.5}, {"name": "李四", "age": 17, "spend": 89.0}, {"name": "王五", "age": 30, "spend": 230.0}, {"name": "赵六", "age": 16, "spend": 45.5}, ] total = 0 valid_count = 0 for user in users: if user["age"] < 18: continue total += user["spend"] valid_count += 1 print(f"成年用户数: {valid_count},总消费额: {total:.2f}")

这里用到了continue,它表示“跳过本轮循环剩余代码,直接进入下一轮”。continue在处理脏数据时非常好用,能让循环体减少一层嵌套。如果你想表达“未成年用户不参与统计”的逻辑,写continue永远比写一大段if user["age"] >= 18:再包住后面所有逻辑更清晰。

我操盘真实数据时,还习惯在循环里加一个计数器或者打点日志,否则处理了几万条数据后,哪里算错了根本无从查起。上面的valid_count不只为最后输出服务,更是调试时确认循环分支是否走了预期路径的依据。

4. 循环语句之 while:条件驱动型循环的控制与退出

4.1 while 循环的设计思路:什么时候用 while,什么时候用 for

for循环适合遍历已知集合或已知次数,而while循环适合“条件为真就继续跑”的场景,它不关心集合,只关心判断条件。最简单的形态:

count = 0 while count < 5: print("当前计数:", count) count += 1

这个循环的执行逻辑是:先检查count < 5,如果成立就执行块内代码,执行完回到条件处再检查。count从 0 一直加到 5,条件变为假,循环停止。

实际操作里,while更适合用在“次数事前不确定”的场景,比如等待某个服务就绪、不断重试网络请求、循环读用户输入直到合法为止。这个划分听起来简单,但新手很容易把两者用错。我见过有同学把for i in range(100):写成while i < 100:,却忘了在循环体里更新i,结果程序直接卡死。这就是典型的“拿 while 干 for 的活”造成的悲剧。

4.2 break、continue、else 的高频组合

除了continue,另一个控制循环的关键词是break。break表示“立即结束整个循环”,后面的代码不再执行,循环也不再继续。一个经典场景是搜索列表里的第一个目标值:

numbers = [3, 7, 9, 12, 15] for num in numbers: if num % 3 == 0 and num % 5 == 0: print("找到了:", num) break

这里找到 15 之后就退出循环,不会继续遍历后面的元素。如果列表很长,break能有效减少无效迭代。很多新手不知道的一点是,break只跳出它所在的最内层循环,不负责跳出外层。要跳出多层嵌套,就得用标志位或者把逻辑封装成函数用return结束。

while和break的组合则是“重试机制”的标配。比如写一个获取用户输入的小程序,要求必须输入非空内容,否则一直问:

while True: text = input("请输入内容(输入 q 退出): ").strip() if text == "q": print("用户主动退出") break if text: print("收到内容:", text) break print("输入不能为空,请重试")

while True是一个刻意构造的无限循环,它的退出完全依赖break。这种写法在处理交互输入、网络重试时很常见,因为它让逻辑集中在循环体内判断,而不是在循环条件处堆一堆判断式。

再说说 Python 里for和while都能配的else子句。这个特性让很多人迷惑,因为else太容易联想到if。它的规则是:循环正常结束(没有被break提前打断)时,执行else块。

for num in [2, 4, 6]: if num % 3 == 0: print("存在能被3整除的数:", num) break else: print("列表中没有能被3整除的数")

这段代码因为6能被 3 整除,break触发,else不执行。如果列表是[2, 4, 8],循环跑完没中断,else才打印提示。这种写法在“检查是否存在满足条件的元素”的场景里很顺手,比专门设一个found标志位再判断要干净。

4.3 防止死循环:工程上的三道防线

while最大的风险就是死循环。程序跑起来不停机,CPU 直接拉满,排查起来也麻烦。写生产环境的代码,我会习惯性给while加三道防线。

第一道防线:初始化条件变量,并确保条件变量在循环体内能被修改。前面说的count += 1就是最基本的一种,很多人漏掉这一步就跑了。

第二道防线:设置最大次数上限。不管什么场景,我都喜欢给循环加一个保护:

MAX_RETRY = 5 retry_count = 0 success = False while not success and retry_count < MAX_RETRY: retry_count += 1 print(f"第 {retry_count} 次尝试") success = do_something() if not success: print("多次尝试后仍失败,记录日志")

这个模式在调用外部接口、写爬虫、连接数据库时特别实用。与其让程序无限等下去,不如明确告诉它“试 5 次不行就算了”。任何依赖外部资源的循环,都应该有类似的上限机制。

第三道防线:在循环里打印或记录日志。死循环最怕的不是死,而是你不知道它死在哪个环节。适当输出当前状态,比如处理到第几条数据、当前计数是多少,能在程序不规范响应时快速定位。

在实际开发中,我见过因为忘记更新条件变量导致服务器端进程卡死的案例。有时候不是大家不知道更新,而是代码逻辑复杂,多个分支里有的分支漏了更新。出现这种情况,最好反思一下:这个循环的条件是不是太复杂了?能不能把循环更新逻辑统一放到循环体最后,或者干脆换一种遍历方式?

5. 流程控制的进阶组合与效率优化

5.1 让代码更简洁:列表推导式与生成器替代嵌套循环

写循环写多了,你会发现有些循环就是“从一组数据里变换出另一组数据”,这种场景在 Python 里可以用列表推导式一步完成。

比如要把一个列表里的每个数字翻倍:

nums = [1, 2, 3, 4, 5] doubled = [n * 2 for n in nums] print(doubled) # [2, 4, 6, 8, 10]

这个写法本质上等价于:

doubled = [] for n in nums: doubled.append(n * 2)

列表推导式看起来精简很多,底层执行效率通常也更高,因为 Python 内部对它做了专门优化。它还可以加条件过滤:

nums = [1, 2, 3, 4, 5, 6, 7, 8] even = [n for n in nums if n % 2 == 0] print(even) # [2, 4, 6, 8]

甚至支持双层:

pairs = [(x, y) for x in range(3) for y in range(2)] print(pairs) # [(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)]

但这里有一个忠告:列表推导式适合逻辑清晰的场景,一旦推导式里嵌套超过两层,或者条件表达式长得吓人,宁可改回普通的for循环。代码是给人看的,可读性永远要排在炫技前面。我自己写的时候会守住一个原则:一个推导式超过一行放不下,就考虑拆开。

for循环还有一处可优化的地方,就是数据量巨大时用生成器而不是列表。生成器不会一次性把所有结果放进内存。把列表推导式的中括号换成圆括号,就得到生成器表达式:

total = sum(n * n for n in range(10000000))

这里range(10000000)配合平方生成,如果硬生生构成长列表,内存占用会非常惊人。生成器表达式让每一步计算汇总时一次只产生一个值,省内存、跑得快,在数据量上来的时候优势明显。

5.2 循环里调用函数时要注意什么

很多人在循环里调用函数时容易犯一个小毛病:把费时费力的“配置加载”“连接创建”等操作不小心放进了循环体,导致每轮迭代都重复执行一次。如果函数的执行结果不依赖当前迭代变量,就应该把它挪到循环外面。

比如下面的写法就浪费资源:

for user in users: db = get_db_connection() # BAD! 每轮都重新建连接 save_user(db, user)

正确写法应该是把get_db_connection()提到循环外面,复用同一个连接。循环里调用函数还有一个隐性问题:Python 的变量作用域是“函数级”,不是“块级”。循环里定义的变量,循环结束后还能访问。像for i in range(5): pass结束之后,i的值是 4。这个特性有些新手第一次遇到会很困惑,但我习惯利用这一点,在循环结束后读取最后一个值做确认,反而成了调试技巧。

另外,在循环里写lambda要注意延迟绑定的问题。举个例子:

funcs = [] for i in range(3): funcs.append(lambda: i) print([f() for f in funcs]) # 输出 [2, 2, 2],而不是 [0, 1, 2]

原因是循环结束时i已经是 2,lambda 函数体内的i是同一个变量,调用时才去读取。我记得的应对办法是给 lambda 设置默认参数捕获当前值:lambda i=i: i。不过老实说,工程中我会尽量避免在循环里构造复杂闭包,真需要这样写也一定会在代码注释里说明意图。

5.3 经典组合场景:用状态标志让逻辑更清晰

有一些业务流程,无法靠单一循环或简单if表达,需要引入状态标志。经典例子是“轮询直到条件成立或超时”:

import time deadline = time.time() + 10 ready = False while time.time() < deadline and not ready: status = check_service_status() if status == "ready": ready = True print("服务已就绪") break print("等待服务就绪...") time.sleep(1) if not ready: print("服务在限定时间内未就绪")

这里两个核心标志:一个是时间边界deadline,一个是业务状态ready。我写这类代码时习惯把所有状态变量最好放在循环前面集中声明,这样别人一进来就能看到这个循环在关心什么。等到循环结束后,用ready判断最终结果。

逻辑一旦复杂,有个小技巧很管用:给变量起“能读出意图的名字”。状态标志别叫flag,叫found、success、ready这类语义明确的词。这也是控制流代码可维护性的关键。

6. 常见错误排查与工程习惯实录

6.1 IndentationError 与字符串缩进不一致

条件语句和循环语句都靠缩进划定范围,所以 Python 对缩进极其敏感。最常见的是混合使用 Tab 和空格。哪怕开头看起来一样,解释器也会报IndentationError: unexpected indent。不同编辑器对 Tab 的显示可能相同,但实际字符不同,非常坑人。

我的建议很明确:项目里统一用 4 个空格做缩进。主流 Python 编辑器,比如 VS Code、PyCharm,都能配置“Tab 转换成空格”。把这项配置改好,基本上能杜绝这个问题。如果你的代码从网上复制下来,先全选格式化一遍,或者直接运行black之类的格式化工具整理。

6.2 遍历列表时增删元素,结果总是很奇特的坑

这是一个让无数新手崩溃的问题。直接讲案例:

nums = [1, 2, 3, 4, 5, 6] for num in nums: if num % 2 == 0: nums.remove(num) print(nums) # 期望得到 [1, 3, 5]?

实际运行结果可能是[1, 3, 5],但也有可能是[1, 3, 5, 6]之类,因为遍历时列表长度变了,索引也跟着偏移。Python 的for在遍历时其实是在迭代一个隐式的索引,删除元素会让后续元素前移,导致部分元素被跳过。这类问题用文字解释不好理解,但一旦你打印出“每轮遍历到哪个元素”就会豁然开朗。

更稳妥的做法是遍历原始列表的副本,在原列表上删除:

nums = [1, 2, 3, 4, 5, 6] for num in nums[:]: # 遍历副本 if num % 2 == 0: nums.remove(num) print(nums) # [1, 3, 5]

或者更符合 Python 风格的做法是过滤出一个新列表,再重新赋值:

nums = [num for num in nums if num % 2 != 0]

第二种写法避免了删除索引的种种混乱,也体现了列表推导式在处理这类问题时天然的优势。

6.3 条件优先级与除法类型导致的“离奇”结果

条件语句里还有个容易踩的坑是运算符优先级和类型问题。比如判断一个数是否为偶数:

n = 7 if n % 2 == 0: print("偶数") else: print("奇数")

这段没问题。但如果有人习惯把判断写成if n % 2 == 0 and n > 5,一旦两个条件都涉及比较且没有括号,就需要小心可读性。Python 的解析顺序里,算术运算优先于比较运算,比较运算优先于布尔运算。所以上面n % 2 == 0会先算n % 2,再和0比较,结果没问题。但是三种优先级混在一起的时候,我强烈建议加括号。

另一个离奇结果来自 Python 的除法。在 Python 3 里,普通除法/得到浮点数,整数除法//才得到整数。比如判断“这个数平均分给5个人能不能整数分完”,新手可能写if total / 5 == total // 5,这在部分情况下成立,但浮点精度会导致意外结果。最稳写法是这样:

if total % 5 == 0: print("可以平均分配") else: print("不能平均分配")

类似的浮点精度问题还出现在判断累积值时。避免用==判断浮点数,改用abs(a - b) < 1e-9,或者干脆用 Decimal 处理金额计算。

6.4 排查流程控制问题时的调试技巧

调试控制流问题,最笨但最有效的方法是“到处打印”。我见过很多人调试喜欢猜,改一行跑一次,跑完不对再猜一行,效率极低。正确的做法是先确认“当前程序到底走到哪个分支”。

具体可以这样做:在循环开头打印轮次和关键变量,在if/else的每个分支里打印一个标记字符。举个例子:

for idx, item in enumerate(data): print(f"第{idx}条数据: {item}") if item["price"] == 0: print("命中空价格分支") continue total_price += item["price"]

看到输出后,读代码的人自然知道逻辑流是怎么回事。调试完再把打印删掉或者用日志模块替代。

还有一个常用的招数:把逻辑拆到独立小函数里,用几组边界数据分别测试。比如你怀疑while的退出条件写错了,可以单独调一个只有循环的小脚本,传入不同初始值看输出。这比在几百行代码里上下翻找省时间多了。

我自己的习惯是把“边界值”测一遍:空列表、只含一个元素的列表、全是脏数据的列表、数值接近 0 的数据。条件语句最喜欢在这些边界上翻车,测过一轮之后信心会大很多。

最后再说两句个人体会

代码里真正决定你的程序能不能正确完成业务逻辑的,往往不是某个高深的算法,而是这些最基础的条件与循环。我写多了之后有个很深的感受:写循环之前,先在心里模拟一次完整执行过程。比如某个变量在循环结束之后应该是什么值?中间经历了几次break?最极端的情况是什么?把这些问题想清楚,很多 bug 在写的时候就能避免。

另外一个很推荐的做法是,定期用代码格式化工具统一风格,再给关键控制流的逻辑配上一行注释。半年前的代码,回头看时如果没有注释,哪怕是自己的,也得花几分钟才能想起来当初为什么这么写。条件语句和循环语句是所有 Python 代码的地基,地基稳了,后面学函数式编程、学异常处理、学并发,都会轻松不少。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询