Python流程控制详解:条件判断、循环与异常处理实战
2026/9/24 21:46:46 网站建设 项目流程

说实话,Python这门语言能火到今天这个程度,核心语法功不可没。我见过太多人一上来就pip install、requests、selenium,爬虫跑得飞起,但问到for循环和while循环到底什么区别、breakcontinue谁先执行,一下子就愣住了。这就是典型的“地基没打牢就开始盖二楼”。

这篇文章要聊的,就是Python核心语法里的流程控制语句——条件判断、循环、循环控制,还有经常被忽略的异常处理。说白了,流程控制就是让程序学会“思考”的东西:什么时候走这条路,什么时候走那条路,什么时候停下来重来一遍。不管你是刚看完安装教程的零基础新人,还是已经能写点小脚本但总觉得代码不够利索的初学者,把这一块吃透,你后面学爬虫、数据分析、写自动化脚本都会顺很多,因为所有项目本质上都是在“控制流程”。

1. 流程控制到底在解决什么问题

1.1 程序不是“一根筋”:顺序、分支、循环

可以回想一下,你写完一个最简单的print("hello"),程序从上到下逐行执行,这叫顺序结构。但它只能处理“一条路走到黑”的情况。

现实里的问题几乎都不是线性的。比如你写一个判断用户登录的程序:用户输入密码,密码对,进入首页;密码不对,提示重新输入;连续错三次,锁定账号。这里每一步都是一个分岔路口,程序必须根据当前条件决定下一步往哪走。这种“根据条件选路径”的能力,就是流程控制的第一个核心——条件分支。

再比如你要打印1到100的所有偶数,总不能写100行print吧。你得让程序“重复做某一件事”,这就是流程控制的第二个核心——循环。循环让代码有了复用能力,一段逻辑可以跑成千上万次,而且每次跑的数据还可以不一样。

1.2 为什么说Python的流程控制更适合新手

我最早学过一阵子C语言,当时最痛苦的就是大括号。写一个if,你得小心翼翼地把{}配对,少一个括号编译直接报错。Python把这个事情彻底简化了:它用缩进来表示代码块,同一个缩进级别就是一个块。也就是说,你的代码长什么样,逻辑就是什么样。

if score >= 60: print("及格了") print("这句也在if里面") print("这句在if外面")

这种设计对新手极度友好,你不用记那些“符号配对”,只需要盯住缩进别乱。但它也带来一个著名的坑:缩进错误。我后面会在常见问题那节单独说。总之,Python把流程控制的“形式门槛”降到了最低,让你把精力集中在逻辑本身,而不是语法符号上。

2. 条件分支:让代码学会“看情况办事”

2.1 if/elif/else的基本写法与缩进细节

条件分支是Python里最常用的流程控制语句,没有之一。基本结构是这样的:

temperature = 36.5 if temperature >= 38: print("发烧了,注意休息") elif temperature >= 37.3: print("低烧,多喝水") elif temperature >= 36: print("体温正常") else: print("体温偏低,注意保暖")

注意几个细节:

  • ifelifelse后面都要有英文冒号:。漏了冒号会直接报SyntaxError
  • elifelse if的缩写,Python里没有elseif这种写法,多写一个空格都不行。
  • 分支只会执行第一个满足条件的那一个。比如温度是37.5,它会走elif temperature >= 37.3这个分支,然后整个if结构结束,后面即使条件也成立,也不会再执行。

这里有个新手经常犯的迷糊:为什么条件顺序会影响结果?看上面例子,如果你把temperature >= 36放在最前面,那37.5也会命中它,后面就永远不会走到了。所以写条件分支的时候,范围大的条件往后放,范围小的往前放,这是一个很重要的经验。

2.2 真值判断和逻辑运算的坑

很多Python初学者写条件判断时,喜欢写得很啰嗦:

if name != "" and name is not None: print("名字非空")

其实Python有一种“真值判断”的约定:空字符串、空列表、空字典、0、None,在条件表达式里都会被当成False。所以上面这段完全可以简化成:

if name: print("名字非空")

反过来,判断“非空”就是if not name。这种写法看着简单,但逻辑完全一样,而且运行效率略高。我自己写代码的时候,基本不会去写!= ""这种判断,除非有特殊需要。

再说逻辑运算符。andor在Python里有个特性叫“短路求值”:and只要左边是假,右边根本不会执行;or只要左边是真,右边也不会执行。比如:

user = None name = user.get("name") if user else "默认用户"

这里是借助三元表达式做了个空安全判断。但短路求值有个更加实际的应用场景:避免程序崩溃。

# 如果文件对象是None,直接跳过读取 if file_obj and file_obj.readable(): data = file_obj.read()

如果file_objNoneand左边为假,右边不会执行,file_obj.readable()不会被调用,也就不会报AttributeError。这个技巧在写爬虫、处理外部数据时特别常用。

2.3 嵌套分支与Python 3.10的match语句

有些时候,分支里还要再分支,这就出现了嵌套的if。但嵌套层数一多,代码会变得很难读。我见过有人写出四层缩进的if,那个代码基本没法维护。处理嵌套,有两个思路:

第一个思路是“提前返回”。把每个不满足的条件写成if直接return,后面再写主逻辑。这种风格叫guard clause,中文可以叫“守卫子句”。

def check_age(age): if age < 0: return "年龄不合法" if age < 18: return "未成年" if age < 60: return "成年人" return "老年人"

第二个思路是用Python 3.10之后新增的match语句。它有点像其他语言里的switch,但能力更强,支持模式匹配:

match command: case "start": print("启动服务") case "stop": print("停止服务") case "restart": print("重启服务") case _: print("未知命令")

这里case _是默认分支,类似于elsematch很适合用来处理“根据某个值走不同分支”的场景,比写一长串if/elif要清晰得多。不过需要注意,如果你用的Python版本低于3.10,match是用不了的,这也是为什么我一直建议新手装最新稳定版Python的原因。

2.4 实操经验:条件判断的顺序和链式比较

关于条件顺序,除了“小范围优先”之外,还有一个原则叫“高频优先”。比如用户输入的数据,大概率是合法的,那么就把合法判断放在前面,异常情况放在后面,这样大部分请求走第一个分支就结束了,执行效率更高。虽然Python不慢,但好习惯值得养成。

Python还有一个非常好用的语法,就是链式比较。其他语言里写if (x > 5 && x < 10),Python直接写成:

if 5 < x < 10: print("x在5到10之间")

这种写法既简洁又直观,我第一次看到的时候觉得“这也太爽了吧”,后来写C#的时候老想着这么写,可惜人家不支持。

3. 循环结构:while和for怎么选

3.1 while:适合“不知道要循环几次”的场景

while循环的语法很简单:条件为真就继续执行循环体,为假就退出。我习惯把它理解成“只要...就一直...”。

count = 0 while count < 5: print(f"第{count + 1}次") count += 1

这里有个很重要的点:循环体里一定要有改变条件的语句,比如count += 1。如果没有,条件永远为真,程序就会陷入无限循环。我当年刚学的时候,有一次忘记写count += 1,控制台直接把数字刷了满屏,最后只能强制关闭终端。

while真正的用武之地,是那些“不知道要循环多少次”的场景。比如等待用户输入一个合法值:

answer = input("请输入一个偶数: ") while not answer.isdigit() or int(answer) % 2 != 0: answer = input("输入不合法,请重新输入一个偶数: ") print(f"你输入的是{answer}")

这种循环次数完全由运行时的输入决定的场景,用while最合适。所以记住一个大概的判断标准:知道循环次数,用for;不知道次数,用while

3.2 for + range:最经典的遍历组合

for循环在Python里其实是一种“遍历”机制。它不像是C语言那样“i=0; i<10; i++”的计数器循环,而是“从某个序列里一个一个取元素”的遍历循环。

for i in range(5): print(i) # 输出0到4

range有三个参数可以调:range(起始, 结束, 步长)。起始默认是0,步长默认是1,结束值不包含在内。比如:

for i in range(1, 10, 2): print(i) # 输出1,3,5,7,9 for i in range(10, 0, -1): print(i) # 输出10到1

for循环最常用的场景是遍历列表、元组、字典这些容器。比如遍历列表:

fruits = ["apple", "banana", "cherry"] for fruit in fruits: print(fruit)

这种写法非常直观,每个元素都会被拿出来处理一遍。需要注意的是,遍历过程中尽量不要修改容器本身,这个坑我后面单独讲。

3.3 遍历字典、字符串的小技巧

字典本身就是可迭代对象,直接for key in dict遍历的是它的键。如果你想要键值对,就要用.items()方法:

score_dict = {"小红": 90, "小明": 85, "小刚": 78} for name, score in score_dict.items(): print(f"{name}考了{score}分")

这里name, score就是解包。如果只需要值,可以用.values();只需要键,直接遍历字典本身或.keys()都可以。

再一个是同时拿索引和值时用enumerate。我见过不少新手写这样的代码:

index = 0 for fruit in fruits: print(index, fruit) index += 1

这个逻辑没问题,但完全可以用enumerate一行搞定:

for index, fruit in enumerate(fruits, start=1): print(index, fruit)

enumerate的第二个参数start可以指定起始编号,默认是0。这样代码更简洁,也不容易漏掉index += 1这一步。

如果你有两个列表,想“按位”配对遍历,可以用zip

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

zip会把多个可迭代对象按索引组合成元组,然后一次性解包。这个技巧在做数据分析和处理表格时非常香。

3.4 嵌套循环:打印乘法表与图形入门

嵌套循环就是循环里套循环。最经典的练习就是打印九九乘法表:

for i in range(1, 10): for j in range(1, i + 1): print(f"{j}×{i}={i * j}", end="\t") print()

这个代码里有两个关键点:内层循环的range(1, i + 1)控制了每行输出的数量,让它随i增大而增加;end="\t"print不换行而是输出制表符;外层循环里那个单独的print()负责换行。

类似的,打印一个直角三角形:

for row in range(1, 6): for col in range(1, row + 1): print("*", end=" ") print()

嵌套循环是学习流程控制时的一个坎,但只要理解“外层循环控制行,内层循环控制列”这个口诀,基本就能搞定。你还可以试着用两层循环画一个爱心,这个和网上很火的“python爱心代码”思路一样,本质也就是控制字符输出的位置。

3.5 边遍历边修改列表,几乎是必踩的坑

我在带新人时,发现几乎每个人都会在某天踩到这个坑——遍历一个列表,同时想把满足条件的元素删掉:

numbers = [1, 2, 3, 4, 5, 6] for num in numbers: if num % 2 == 0: numbers.remove(num) print(numbers) # 你以为输出[1,3,5],实际是[1,3,5,6]

为什么6还在?因为for循环内部是通过索引遍历的。删除元素后,列表长度变了,索引对应关系全乱了,那个6根本没轮到就被跳过了。这种问题很隐蔽,有时候数据量大一点,你根本发现不了。

解决办法有三种:

  1. 遍历副本:for num in numbers[:]:,对副本遍历,对原列表修改。
  2. 用列表推导式重新生成:numbers = [num for num in numbers if num % 2 != 0]
  3. 倒序遍历:for num in reversed(numbers):,从后往前删不会影响前面元素的索引。

我个人的习惯是能不用remove就不用,列表推导式一行搞定,而且语义更清楚。这个经验在你以后处理爬虫数据清洗、日志筛选时会经常用上。

4. 循环的控制权:break、continue、else、pass

4.1 break:立刻离开整个循环

break是最简单直接的控制语句:执行到它,立刻跳出当前这层循环。比如你要在列表里找第一个大于10的数:

numbers = [3, 8, 15, 22, 9] for num in numbers: if num > 10: print(f"找到了: {num}") break

如果没有break,这个循环会继续把后面的数也遍历完。有了break,找到目标就停下,效率高,逻辑上也更符合“找到一个就够了”的语义。注意break只会跳出它所在的那一层循环,如果你在嵌套循环的内层使用,外层循环不受影响。

4.2 continue:跳过当前这一次,继续下一次

continue的作用是“跳过本次循环的剩余代码,直接进入下一次循环”。比如只打印奇数:

for i in range(10): if i % 2 == 0: continue print(i)

执行到continue时,后面的print(i)不会执行,直接进入下个i。这个和break的区别要记牢:break是结束整个循环,continue是结束当前这一次迭代。我经常用continue做数据过滤,比如爬虫里跳过不符合规则的响应,继续处理下一条。

4.3 循环后的else:判断循环是否“正常走完”

这是Python一个比较冷门但很好用的特性:for循环和while循环后面可以跟一个else,当循环正常结束(没有被break中断)时,会执行else里的代码。

经典的例子是判断一个数是不是素数:

num = 17 for i in range(2, num): if num % i == 0: print(f"{num}不是素数") break else: print(f"{num}是素数")

如果num能被某个i整除,就执行break跳出,else不会执行;如果循环从头走到尾都没有break,说明没有任何约数,此时执行else,判定为素数。这个写法比用一个flag变量标记“是否是素数”要简洁得多。

我第一次看到这个特性的时候觉得很神奇,但后来确实越用越顺。唯一需要注意的是,很多人不了解这个语法,反而觉得难以理解,所以如果你是在团队项目里使用,最好在注释里说明一下意图。

4.4 pass:啥也不干的占位符

pass是一个空语句,不做任何事情。它的作用就是占位。比如你还没想好某个分支具体怎么实现,可以先写上pass保证代码不报错:

if condition: pass # 之后再来实现 else: print("其他情况")

很多初学者不理解“为什么需要pass这么个没用的东西”。因为Python用缩进表示代码块,一个代码块里如果一行内容都没有,语法就会报错。pass就是用来“占据”这个位置,让程序能正常运行起来。记住一个原则:pass是给开发阶段用的,不是给生产逻辑用的。真正的业务逻辑里出现pass,大概率是代码还没写完。

4.5 综合小例子:登录重试机制

把这些控制语句串起来,写一个非常常见的场景:登录重试,最多试3次。

MAX_RETRY = 3 password = "123456" for attempt in range(1, MAX_RETRY + 1): user_input = input("请输入密码: ") if user_input == password: print("登录成功!") break else: remaining = MAX_RETRY - attempt if remaining == 0: print("密码错误次数过多,账号已锁定") else: print(f"密码错误,还剩{remaining}次机会") else: print("三次机会已用完")

这一段代码里用了forif/elsebreak、循环后的else,几乎覆盖了前面所有知识点。break在登录成功后退出循环,然后else不会执行;如果循环正常结束,说明三次都失败了,执行else打印锁定提示。这个结构在写自动化脚本、爬虫重试逻辑时非常常见。

5. 异常处理:被忽略的“第四种流程控制”

5.1 什么是异常,为什么必须在流程控制里讲

异常处理往往被单独拎出来讲,但我觉得它就是流程控制的一部分。程序运行的时候,输入不可控、网络不可控、文件可能不存在,各种意外随时会发生。如果你不处理这些意外,程序会直接崩溃,给用户留下一屏看不懂的报错。

异常处理的本质是“当程序发生错误时,走另一条流程”。这和if本质上没有区别,只是它判断的条件是“有没有发生异常”。所以我把异常处理当成流程控制的第四个家族成员,而且是写健壮代码必须掌握的。

5.2 try/except/else/finally的基本结构

Python里异常处理的关键字有四个:tryexceptelsefinally。基本结构是:

try: # 可能出错的代码 num = int(input("请输入一个整数: ")) except ValueError: # 出错时执行 print("输入的不是整数") else: # 没出错时执行 print(f"你输入的是{num}") finally: # 不管出错与否都会执行 print("本次输入流程结束")

四个部分的执行顺序是:先执行try里的代码,如果抛异常,跳到对应的except执行;如果没抛异常,执行else;最后无论如何都执行finally

finally的用途很明确:释放资源。比如关闭文件、关闭数据库连接、关闭网络请求。有些初学者会忘记用finally,直接把关闭操作写在try的最后面,但这样一旦中间出错,后面的关闭代码就不会执行了,资源一直被占着,程序多了就会出问题。

5.3 捕获多个异常和异常对象

一个try块可以跟多个except,用来处理不同类型的错误:

try: data = {"name": "小明", "age": 18} age = data["age"] result = 100 / int(age) print(result) except KeyError as e: print(f"键不存在: {e}") except ZeroDivisionError as e: print(f"除零错误: {e}") except ValueError as e: print(f"类型转换错误: {e}")

这里as e是把异常对象赋值给变量e,方便我们打印出具体的错误信息。注意except的顺序也很重要,如果异常类型之间有继承关系,子类的except要写在父类前面,否则子类的处理永远不会被命中。

还有一种写法是把多个异常类型放在一个元组里:

except (KeyError, ValueError) as e: print(f"数据问题: {e}")

这种写法适合“这两种异常走同样的处理逻辑”的场景。

5.4 raise:主动抛出异常的时机

raise是主动抛出异常的关键字。什么时候需要主动抛?答案是:当程序遇到一个“自己处理不了”的情况时,应该让上层代码知道。

比如你写了一个函数,负责从用户输入里提取年龄,结果发现输入是负数:

def get_age(): age = int(input("请输入年龄: ")) if age < 0: raise ValueError("年龄不能为负数") return age

调用方可以捕获这个异常,也可以继续向上抛。这个机制的好处是,你不需要通过返回值来“告知”调用方出错,异常本身就是最好的错误信息载体。新手容易犯的错误是到处try/except,把所有错误都吞掉,这样反而掩盖了问题。正确的姿势是:能处理的就地处理,处理不了的raise让上层处理,实在处理不了就让程序崩掉,至少你能看到报错。

5.5 实例:让用户输入数字直到正确

把循环和异常处理结合,写一个更实用的输入校验工具:

while True: raw = input("请输入一个数字: ") try: num = float(raw) except ValueError: print("输入无效,请重新输入") continue if num < 0: print("数字不能为负数,请重新输入") continue print(f"合法输入: {num}") break

这个例子里,while True配合break是一个经典的输入循环结构。try/except负责捕获“字符串转数字”失败的情况,continue让用户重新输入,条件判断负责业务规则,break在拿到合法输入后退出循环。这段代码几分钟就能讲完,但它串起了条件和循环几乎所有知识点,而且你以后写命令行工具时,可以直接抄这套模板。

6. 综合实战:用流程控制写一个“猜数字”小游戏

6.1 需求拆解

讲完一堆语法点,我们来做个小项目把它们串起来。经典的控制台小游戏“猜数字”特别适合:

  1. 程序随机生成一个1到100之间的整数。
  2. 用户输入猜测的数。
  3. 程序提示猜大了、猜小了还是猜对了。
  4. 用户猜错就继续猜,猜对就结束,并显示次数。
  5. 如果用户输入的不是数字,要提示重新输入,不能崩。

这个需求天然包含随机数、while循环、if/elif/else判断、break退出、异常处理,几乎把我们今天讲的内容全部用上了。

6.2 完整代码

import random target = random.randint(1, 100) attempts = 0 print("欢迎来猜数字!范围是1到100") while True: raw = input("请输入你猜的数: ") try: guess = int(raw) except ValueError: print("请输入一个有效的整数") continue attempts += 1 if guess > target: print("猜大了,再试试") elif guess < target: print("猜小了,再试试") else: print(f"恭喜你!猜对了,一共猜了{attempts}次") break

6.3 代码解读:每个知识点出现在哪里

整个程序的骨架是while True无限循环,直到break才退出,这就是“不知道要循环几次”的经典场景。try/except处理了输入不是整数的情况,continue让流程回到循环开头重新输入,不会因为输入非法而崩溃。if/elif/else三兄弟承担了核心判断逻辑,根据用户输入和目标数字的大小关系给出不同反馈。attempts += 1就是循环里最常见的“计数器”写法,最后在else分支里把计数结果展示出来。

这个代码可以继续扩展,比如加入“最多猜10次”的限制,超过次数就break并输出目标数字;也可以把最高分记录写进文件里,下次玩的时候读取。爬虫、数据分析这些更复杂的项目,底层逻辑也是一样的:判断状态、获取数据、处理异常、循环重试。

6.4 扩展方向

如果你把这个小游戏改造成“猜数字并记录历史成绩”,就会用到文件读写;改造成“多人轮流猜”,就会用到列表和函数;改造成“网络版”,就会用到socket。每一个扩展方向都会引入新的知识点,但核心的流程控制部分已经在这里了。我就是靠这种“小游戏+小工具”的方式一路把Python基础打牢的,比干啃语法书效果好太多。

7. 常见问题与调试技巧实录

7.1 新手最容易踩的几个坑

我把这些年亲眼见过的报错和逻辑错误整理了一下,基本逃不出下面这几个:

问题表现原因解决办法
IndentationError缩进错误,代码块层级混乱混用空格和Tab,或缩进不一致统一用4个空格,编辑器开启“显示空白字符”
SyntaxError: expected ':'if/while/for后面漏冒号英文冒号忘了写检查每行条件语句结尾
UnboundLocalError局部变量引用报错函数内部和外部的变量名冲突global声明,或改名
无限循环程序卡死,刷屏while条件永远为真检查循环里是否有更新条件的语句
break只跳出一层嵌套循环里退出不彻底break只作用于最近一层使用“标志位”或把逻辑封装成函数用return
IndexError列表索引越界访问了下标不存在的元素打印列表长度,检查range边界

还有一个很隐蔽的逻辑错误,就是赋值和比较混淆。写if guess = target这是语法错误,因为=是赋值,==才是比较。但在某些语言里这种错误不会报语法错,所以很多从其他语言转过来的朋友会在这里栽跟头。Python还算好的,至少会直接报错,不会让你默默地去比较。

7.2 调试技巧:print大法依然是神

很多初学者一遇到程序不按预期跑,就慌了,到处乱改。我的建议是:先用print把关键变量打印出来,看看程序到底走到了哪个分支。

还是拿猜数字举例,如果你发现不管猜什么都说猜大了,可以在判断前打印一下目标数字:

print(f"调试信息: target={target}, guess={guess}")

这样你一眼就能看出来随机数的范围是不是不对,或者是不是比较符号写反了。这种“print大法”虽然简单,但真的能解决80%的流程问题。等你熟练了,可以再学pdb断点调试,但刚开始真没必要用那么重的工具。

7.3 一段真实排查实录

有一次我帮朋友看代码,他的需求很简单:遍历一个用户列表,把所有状态为“停用”的用户找出来,打印他们的名字。代码长这样:

users = [ {"name": "张三", "status": "正常"}, {"name": "李四", "status": "停用"}, {"name": "王五", "status": "正常"}, ] for user in users: if user["status"] == "停用": print(user["name"])

看起来一点问题没有,但他跑出来结果为空。我让他打印一下user["status"],结果发现数据里的状态值其实是"停用 ",后面多了一个空格。类似这种“看不见的字符”问题,在爬虫和数据处理中太常见了。排查办法很简单,输出前后加个标记:

print(f"[{user['status']}]")

这样空格就显形了。我特别想告诉刚入门的朋友:当程序没按预期走流程,别急着怀疑语法,先看看变量值到底是什么。数据不对,流程再对也没用。

7.4 关于调试流程的几个个人建议

最后分享一点调试流程控制的小心得。

第一,把大问题拆小。比如一个复杂的多条件判断,先注释掉一半条件,看看另一半是否正常,逐步缩小排查范围。第二,不要凭感觉改代码。每改一个地方就跑一次,确认影响了什么,这样能避免“修好这个又弄坏那个”。第三,善用else分支打印“不应该走到这里”的提示。比如在if链的最后加一个else: print("意外情况:", 变量),帮你尽早发现逻辑漏洞。这些习惯看起来不起眼,但在写几百行项目的时候,能帮你省下大量时间。

我个人在实际带人过程中体会最深的一点是:很多人学流程控制时觉得“太简单了”,一看就懂,但一写就错。真正的问题往往不是语法不会,而是没有把“程序是一步一步执行”这件事记在脑子里。每当你写下一个if或者for,都可以试着把自己当成Python解释器,沿着代码走一遍,看看每一步会执行到哪一行、变量变成什么值。这样走几遍,你对流程控制的理解就会完全不一样。

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

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

立即咨询