前面我们已经学了文件,也开始接触真实数据了。到这一步,很多人都会遇到一个很现实的问题:代码明明写得差不多,怎么一运行就报错了,程序还直接停了。
这时候,你就正式碰到了 Python 里一个非常重要的概念:异常。
很多新手一看到报错就紧张,觉得自己是不是学不会编程。其实完全没必要。异常不是坏东西,它本质上是在提醒你,程序运行过程中出现了一个没法正常继续的问题。
你可以把它理解成,程序在执行时突然遇到了一堵墙。撞上去了,过不去,于是只能停下来,并把问题告诉你。
一、什么叫异常
先看一个最简单的例子:
print(10 / 0)
运行后,程序会报错。原因很简单,任何数都不能除以 0。
这时候 Python 不会装作没看见,也不会继续瞎跑,而是直接告诉你:这里出问题了。
这种在程序运行过程中出现的问题,就叫异常。
注意这句话很关键:
异常不是写代码那一刻就已经决定的,而是程序运行到某一步时才暴露出来的问题。
也就是说,代码能不能写出来,和代码能不能顺利运行完,是两回事。
二、为什么程序会崩
很多人说程序崩了,其实大多数时候,说的就是程序抛出了异常,又没有被处理,于是直接中断了。
比如下面这些场景,都非常常见。
你读取一个根本不存在的文件。 你把字符串当数字去计算。 你访问了一个不存在的列表下标。 你调用了一个对象没有的方法。 你把 0 当成除数。
只要程序遇到这种无法正常继续的情况,Python 就会抛出异常。异常如果没人接住,程序就会停。
所以你可以把程序崩溃理解成这样:
程序运行中出错了。 这个错误不是普通提醒,而是足以打断当前流程的问题。 程序没有处理它,于是只能停止。
三、异常和语法错误不是一回事
这是初学者很容易混淆的地方。
比如你写:
if5 > 3 print('hello')
这里少了一个冒号,这属于语法错误。
语法错误的特点是,程序还没真正开始运行,Python 在检查代码时就已经发现你写得不符合语法规则了,所以根本跑不起来。
而异常不一样。异常通常是代码语法没问题,程序也能启动,但运行到某一行时出了事。
比如:
num = 10print(num / 0)
这段代码语法完全正确,但运行时会报错。因为问题不是写法,而是逻辑执行到这里时发生了非法操作。
所以你一定要分清两类问题:
语法错误,是代码写得不合法。 异常,是代码能运行,但运行过程中出了问题。
这两者不是一个层面。
四、先看几个最常见的异常例子
第一个,除零错误:
print(8 / 0)
第二个,类型错误:
print('10' + 5)
字符串和整数不能直接相加,所以会报错。
第三个,索引越界:
nums = [1, 2, 3]print(nums[5])
列表里明明只有 3 个元素,你却去取第 6 个位置,自然不行。
第四个,键不存在:
info = {'name': '张三'}print(info['age'])
字典里没有 age 这个键,所以也会报错。
第五个,文件不存在:
with open('abc.txt', 'r', encoding='utf-8') as f: print(f.read())
如果当前目录下根本没有这个文件,程序也会报错。
这些例子看起来五花八门,但本质上都一样:
程序想做一件事,但当前条件不允许它这么做。
于是,异常出现了。
五、异常不是敌人,它其实是在保护你
这一点你一定要尽早建立起来。
很多新手一看到报错,第一反应是烦。其实你换个角度看就会明白,异常恰恰是在保护程序,也是在保护你。
比如,你本来想读取一个不存在的文件。如果 Python 不报错,继续往下执行,那后面的数据可能全是假的,或者逻辑会越来越乱。
再比如,你本来想拿年龄做加法,结果变量里放的是一段文字。如果 Python 还继续算,那结果反而更危险。
所以异常其实是在明确告诉你:
这里不对。 别再往下糊弄了。 先把问题解决掉。
从这个角度看,异常并不是程序在和你作对,而是在尽职尽责地把问题暴露出来。
六、异常信息里通常会告诉你什么
当程序报错时,屏幕上一般会出现一大段信息。初学者看到这一屏英文,往往头都大了。其实不用怕,你先学会看最关键的两部分就行。
第一部分,是出错的位置。 也就是哪一个文件、哪一行代码出了问题。
第二部分,是异常类型。 比如 ZeroDivisionError、TypeError、IndexError、FileNotFoundError 等。
再往后通常还会有更具体的解释,告诉你为什么出错。
比如:
除数不能为 0 列表索引越界 文件不存在 类型不支持这种操作
你现在先不用把整段报错全吃透,只要先养成一个习惯:
看到异常,先找出错行,再看异常名字,最后看解释。
后面第69章我们会专门讲怎么阅读报错信息。你现在先有个整体感觉就够了。
七、为什么同样的代码,有时候报错,有时候不报错
因为异常和运行时的数据、环境、输入,都有关系。
比如这段代码:
age = int(input('请输入年龄:'))print(age + 1)
如果用户输入的是:
18
程序没问题。
但如果用户输入的是:
十八
程序就可能报错。因为 int() 没法把这段中文直接转成整数。
这就说明,异常并不总是代码本身有问题。有时候,代码逻辑没错,但外部输入不符合预期,也会触发异常。
再比如读取文件。文件存在时,程序能跑。文件不存在时,程序就会报错。
所以你会慢慢发现,真正的程序开发不是只写理想情况,还要考虑各种意外情况。
而异常,正是这些意外情况最直接的表现方式。
八、程序员为什么一定要理解异常
因为现实世界不是考试题,不会永远只给你最标准的输入。
用户可能输错内容。 文件可能丢失。 数据可能为空。 网络可能超时。 数据库可能连不上。
你如果只会写一条最顺的主流程,代码看起来能跑,但一碰到真实环境就容易出问题。
所以学异常,表面上是在学报错,实际上是在学一件更重要的事:
接受程序不会永远按理想状态运行。
一个成熟的程序员,和一个只会照着教程写代码的人,差别往往就在这里。
前者会提前考虑意外。 后者只盯着正常流程。
九、异常和 if 判断有什么区别
这是很多初学者会问的问题。
既然程序可能出错,那我是不是多写几个 if 判断就够了,为什么还要学异常?
这个问题问得很好。
if 判断是你主动去检查某个条件。 异常则是程序运行时真的遇到了问题,并且已经没法按正常方式继续。
比如:
age_text = input('请输入年龄:')if age_text.isdigit(): age = int(age_text) print(age)else: print('输入的不是数字')
这里你提前做了判断,所以避免了出错。这当然是好事。
但不是所有问题都能靠 if 提前判断完。比如文件是否突然被删除,网络请求会不会中断,数据库会不会超时,这些都可能在运行中动态发生。
所以更准确地说:
if 是预防。 异常是程序在运行中遇到问题后的反馈。
这两者不是谁替代谁,而是经常要配合使用。
十、几个最常见的异常名字,先混个脸熟
这一章不要求你全背下来,但有几个你肯定会经常见到。
ZeroDivisionError除以 0 的错误
TypeError类型不支持当前操作
ValueError值的形式不对,比如字符串转整数失败
IndexError列表下标越界
KeyError字典里没有这个键
FileNotFoundError文件不存在
你现在先别死背,后面遇得多了自然会熟。编程很多时候就是这样,不是靠一次记住,而是靠反复见面。
十一、看一个更贴近实际的小例子
假设你写了这样一段代码,想读取成绩并计算平均值:
scores = [90, 85, 88]avg = sum(scores) / len(scores)print(avg)
这段代码没问题。
但如果某一天,scores 变成了空列表:
scores = []avg = sum(scores) / len(scores)print(avg)
这时候就会出错。因为 len(scores) 是 0,等于在做除以 0。
这个例子特别有代表性。它说明异常并不总是因为你写法离谱,有时候只是因为你没考虑某些边界情况。
也就是说,学异常的过程,其实也是你开始建立边界意识的过程。
十二、再看一个文件场景
我们前面学过文件读取:
with open('data.txt', 'r', encoding='utf-8') as f: content = f.read() print(content)
在理想状态下,这段代码没有问题。
但真实情况里,data.txt 可能不存在。 一旦文件找不到,程序就会抛出异常并中断。
这说明什么?
说明程序写出来以后,不是只看语法对不对,还要看它依赖的外部条件是否成立。
文件是否存在 路径是否正确 编码是否匹配 权限是否允许读取
这些都可能影响程序能不能顺利执行。
你会发现,代码世界开始越来越像现实世界了。现实世界最大的特点,就是充满不确定性。
十三、为什么说异常处理能力比死记语法更重要
很多初学者前期学得挺快,变量、循环、函数都能写,但一报错就卡住。不是因为知识点不够,而是因为没有建立异常意识。
真正让你进步快的,不是永远不出错,而是出错以后你知道该怎么判断。
这是哪一类错误 是语法层面,还是运行时问题 是数据不对,还是文件不对 是路径错了,还是类型错了 是逻辑有漏洞,还是输入超出了预期
当你开始这样想问题时,你就不是在机械学语法了,而是在真正学开发。
十四、这一章你最应该改变的心态
从今天开始,你要把下面这个习惯慢慢改掉:
一报错就慌 一报错就觉得自己不适合编程 一报错就马上复制粘贴问别人
更好的心态应该是:
先看是哪一行 再看是什么异常 再想为什么会发生 最后再决定怎么改
异常不可怕。 真正可怕的是,一看报错就脑子空白。
编程高手和新手的差别,很多时候不是谁报错更少,而是谁更会面对报错。
十五、先别急着处理异常,先学会认识异常
这一章我们故意还没有讲 try...except。因为很多人一上来就想学怎么把错误包住,但连异常是什么都没搞明白,这样学很容易浮。
你现在最重要的任务,是先建立这几个认知:
异常是运行时出现的问题。 异常会打断程序正常执行。 异常通常会提供出错位置和错误类型。 异常不是敌人,而是问题提示。 真实程序一定会遇到异常。
这几个认知一旦立住,下一章你再学 try...except,就会很顺。
十六、一个小实验,自己感受异常出现的瞬间
建议你亲手运行下面几段代码,感受一下不同异常的样子。
第一段:
print(10 / 0)
第二段:
nums = [1, 2, 3]print(nums[5])
第三段:
info = {'name': '小李'}print(info['age'])
第四段:
age = int('十八')print(age)
你会发现,这几段代码报的错都不一样。
这正是异常的意义所在。 Python 不只是告诉你错了,还会告诉你错的是哪一类问题。
这就像医生不只是说你不舒服,而是进一步告诉你到底是感冒、发烧,还是扭伤。类型不同,后面的处理方式也不同。
十七、本章小练习
你可以做一个很简单但很有用的练习。
自己写出下面四段代码,并分别运行:
除以 0 访问越界下标 读取不存在的文件 把一段非数字文本转成整数
然后观察每次报错时,重点看三件事:
哪一行报错 异常名字是什么 后面的解释在说什么
这个练习非常基础,但特别有价值。因为它会帮你建立一个能力:不再害怕报错,而是开始读懂报错。
十八、本章总结
这一章,我们正式认识了异常。
异常,是程序在运行过程中遇到的问题。 它和语法错误不同,语法错误是代码写法不合法,异常是代码能跑,但运行中出了问题。 当异常没有被处理时,程序通常会中断,这就是很多人说的程序崩了。 常见异常包括除零、类型错误、索引越界、键不存在、文件不存在等。 异常并不是坏事,它是在提醒你程序当前无法正常继续。 学异常,本质上是在学如何面对不确定的输入、不稳定的环境和真实世界里的各种意外。
下一章我们继续往前走,进入真正的异常处理核心:try...except 基础:让程序学会优雅报错。