Python基础8(错误和异常)

张开发
2026/4/8 5:48:14 15 分钟阅读

分享文章

Python基础8(错误和异常)
提示文章写完后目录可以自动生成如何生成可参考右边的帮助文档文章目录异常介绍语法错误异常异常处理try except捕获指定类型的异常以及获取异常描述信息elsefinally一、 else vs 写在外面的代码二、 finally vs 写在外面的代码 终极总结图谱抛出异常raiseassert断言自定义异常异常的传递with语法工作原理Python常见异常 Python 异常处理核心总结⚠️ 异常处理 7 大易错点全解析新手与老手常踩的坑易错点 1使用“裸” except: 吞噬一切异常最臭名昭著的坏习惯易错点 2except 捕获顺序写反大网兜放在了小网兜前面易错点 3finally 里的 return 会“劫持”真正的返回值易错点 4在生产环境中使用 assert 做数据校验易错点 5混淆了 else 和“写在 try 外面的代码”易错点 6误以为 with 内部不会报错易错点 7把异常当成常规的 if-else 控制流来用异常介绍Python是一门解释型语言只有在程序运行后才会执行语法检查。所以只有在运行或测试程序时才会真正知道该程序能不能正常运行。Python有两种错误很容易辨认语法错误和异常。语法错误程序解析时遇到的错误。例如以下程序因缺少 : 而出现语法错误。whileTrueprint(1)# while True print(1)# ^^^^^# SyntaxError: invalid syntax异常Python 程序的语法是正确的在运行它的时候也有可能发生错误。运行期检测到的错误被称为异常。例如以下程序因变量名未找到而引发NameError。print(var1)# print(var1)# ^^^^# NameError: name var1 is not defined. Did you mean: vars?大多数的异常都不会被程序处理都以错误信息的形式打印出来错误信息的前面部分显示了异常发生的上下文并以调用栈的形式显示具体信息。异常处理对异常进行处理并不是将错误规避了而是当程序运行的时候出现错误的时候提供解决方案不终止程序可以让程序继续执行。try except可以使用 try except 语句来捕获异常并处理。1语法try:可能发生异常的代码except:异常处理的代码如果没有发生异常程序会忽略except中的代码继续向下执行。如果发生了异常会忽略try中剩余代码执行except中的代码。2案例try:result 3 / 1print(“没有发生异常”)except:print(“发生异常了”)print(“End”)捕获指定类型的异常以及获取异常描述信息在打印出来的异常信息中冒号之前是异常类型,冒号之后是异常描述信息# NameError: name a is not defined# NameError: 冒号之前是异常类型# : name a is not defined 冒号之后是异常描述信息print(a)如果出现的异常不是我们指定的类型中的其中一个我们在程序中想对不同类型的异常进行不同的处理并在处理异常的时候要获取异常信息我们可以通过如下方式。1语法try:可能发生异常的代码except异常类型1as变量名1:异常处理的代码except异常类型2as变量名2:异常处理的代码except(异常类型3,异常类型4,异常类型5)as变量名3:异常处理的代码except:异常处理的代码如果没有发生异常程序会忽略except中的代码继续向下执行。如果发生了异常会忽略try中剩余代码根据异常类型匹配到相应的 except 并执行其中的代码。如果发生了异常且异常类型无法和任何except匹配异常将向外传递。一个except可以同时处理多个异常将这些异常放在一个元组中。最后一个 except 可以忽略异常类型它将被作为通配符使用。2案例try:result3/0print(发生异常了)exceptZeroDivisionErrorase:print(e)except(RuntimeError,TypeError,NameError)ase:print(e)except:print(Unexpected error)print(End)else可选地将else放在所有except之后。如果try中代码没有发生异常将执行 else 中的代码。1语法try:可能发生异常的代码except异常类型1as变量名1:异常处理的代码except异常类型2as变量名2:异常处理的代码else:没有异常时执行的代码2说明从执行效果上说将代码放到else块和直接放到try块中是一样的。将try正常执行完毕而没有引发任何异常后被执行的代码放到else中。提供了一种清晰的逻辑区分将正常情况的代码与异常处理代码分开使代码更易于理解和维护有助于代码的可读性和可维护性。例如你希望在try块中有些操作执行成功后再执行其它代码那就可以把代码放到else语句块中。3案例try:resultx/yexceptZeroDivisionError:print(除数不能为零)else:print(f结果是:{result})finally可选地放在最后。无论是否发生异常都会执行的代码通常用于执行一些必须要进行的清理操作例如关闭文件、释放资源如网络连接、数据库连接、锁等即使在执行 try 块中的代码时出现了异常也能保证这些操作得以完成。1语法try:可能发生异常的代码except异常类型1as变量名1:异常处理的代码except异常类型2as变量名2:异常处理的代码else:没有异常时执行的代码finally:无论是否发生异常都会执行的代码2说明如果从执行效果上说大部分场景将代码放到finally语句和放到try-except块外效果是一样的。finally 语句块是 try-except 结构的一部分它确保了无论 try 块中是否发生异常也无论 except 块是否被执行其中的代码都会被执行直接放在try-except结构外面的代码只会在try-except结构正常执行完毕后才会执行如果在try块中出现异常且没有被except块捕获或者在except块中出现了新的异常导致程序终止那么这部分代码将不会被执行。3案例# try:# result 3 / 0# except ZeroDivisionError as e:# print(e)# else:# print(result)# finally:# print(finally)# print(End)#输出结果# division by zero# finally# Endtry:result3/0exceptNameErrorase:print(e)else:print(result)finally:print(finally)print(End)#输出结果# finally# Traceback (most recent call last):# File e:\Hello\hello.py, line 15, in module# result 3 / 0# ~~^~~# ZeroDivisionError: division by zero可能会有这个疑惑“既然代码顺着往下走就会执行为什么还要专门发明else和finally这两个关键字”答案的核心在于“状态的纯粹性”和“执行的绝对保障”。我们把它们和“写在外面的代码比如你的print(End)”进行对比彻底拆解它们的区别。一、elsevs 写在外面的代码【核心区别】else只有在try里面的代码100% 成功、没有引发任何异常时才会执行。它代表“纯粹的成功”。写在外面只要程序没崩溃不管try是成功了还是报错且被except捕获并处理了外面的代码都会执行。【为什么要用else】假设我们要打开一个文件并读取内容如果没有else你可能会这样写try:# 假设这里是打开文件和计算result10/2exceptZeroDivisionErrorase:print(出错了)# 写在外面的代码print(计算结果是,result)问题来了如果上面发生的是10 / 0except捕获了错误程序没崩溃继续往下走。此时执行到外面的print(result)时程序会引发一个新的报错NameError因为报错导致result根本就没有被计算和定义使用else的优雅姿势try:result10/2exceptZeroDivisionErrorase:print(出错了压根没算出 result)else:# 只有上面完全成功才会走到这里print(顺利算出结果,result)总结else划清了界限把**“可能出错的代码”放try里和“必须依赖上面成功才能执行的代码”**放else里完美分开了。二、finallyvs 写在外面的代码这是整个try...except体系中最重要的一部分。请仔细看你提供的第二个例子的输出。【核心区别】finally拥有绝对的执行特权。无论程序是正常结束、还是被except捕获、甚至是发生了未捕获的致命错误导致程序崩溃finally里的代码一定会被执行完毕。写在外面一旦发生了未被捕获的致命异常程序当场“死亡”写在外面的代码永远没有机会执行。【证据就在你的代码里】看你的第二个例子try:result3/0# 发生除零错误exceptNameErrorase:# 你的网兜只兜 NameError所以没兜住除零错误print(e)# ... 省略 else ...finally:print(finally)print(End)运行结果程序发现网兜漏了准备崩溃。在临死前它看到了finally。它强撑着最后一口气打印出了finally。程序正式崩溃抛出红色报错信息。写在外面的print(End)根本没有被打印出来【为什么要用finally两大杀手锏】杀手锏 1收拾残局释放资源无论你程序是死是活借了别人的东西必须还比如你打开了一个文件、或者连接了数据库。如果在读取文件时程序崩溃了如果不把关闭文件的代码写在finally里文件就会一直被占用导致内存泄漏。fopen(test.txt,r)try:# 万一这里发生了崩溃contentf.read()finally:# 无论如何哪怕程序死了这行代码也一定会执行安全关闭文件f.close()杀手锏 2无视return、break的阻拦在函数中如果在try或者except里面执行了return也就是函数准备结束并返回了外面的代码绝对不会再执行了。但是finally依然会强行插队执行deftest():try:print(开始计算)return计算成功# 准备带着结果撤退finally:print(等等无论你要去哪必须等我 finally 打印完)print(这行在外面永远不会打印)print(test())# 输出结果# 开始计算# 等等无论你要去哪必须等我 finally 打印完# 计算成功 终极总结图谱为了方便你彻底记住请看这张“命运判定表”执行情况except会走吗else会走吗finally会走吗外面的代码会走吗一帆风顺无异常❌✅✅✅发生异常且被兜住✅❌✅✅发生异常没被兜住程序崩溃❌❌✅临死前执行❌随着程序陪葬try 里面触发了return❌❌✅强行插队执行❌直接退出了一句话口诀else是锦上添花只有全对我才出场。finally是生老病死哪怕天塌下来我都必须把手里的活干完。写在外面是芸芸众生必须天下太平没崩溃、没提前 return我才能走到最后。抛出异常raise当你想要在代码中明确表示发生了错误或异常情况时可以使用 raise 来抛出异常。这可以帮助你在满足某些条件时停止程序的正常执行并将控制权转移到异常处理部分。1语法raise 异常类型(“异常描述”)2案例defint_add(x,y):ifisinstance(x,int)andisinstance(y,int):returnxyelse:raiseTypeError(参数类型错误)print(int_add(1,2))# 3print(int_add(1,2))# TypeError: 参数类型错误assert断言assert用于判断一个表达式在表达式条件为False的时候触发异常常用于调试程序。1语法assert表达式[,异常描述]等价于ifnot表达式:raiseAssertionError([异常描述])2案例defint_add(x,y):assertisinstance(x,int)andisinstance(y,int),参数类型错误returnxyprint(int_add(1,2))# 3print(int_add(1,2))# AssertionError: 参数类型错误自定义异常通过直接或者间接继承Exception类来创建自己的异常。例如classMyError(Exception):def__init__(self,value):self.valuevaluedef__str__(self):print(diaoyng)returnrepr(self.value)try:raiseMyError(1)exceptMyErrorase:print(e)print(触发自定义异常:,e.value)e是MyError的实例异常的传递当存在 try 嵌套或函数嵌套时若内层出现了异常且在内层无法处理会将异常一层一层向外传递直到异常被处理或程序报错。try:try:try:print(1/0)exceptNameErrorase:print(第三层,e)exceptTypeErrorase:print(第二层,e)exceptExceptionase:print(第一层,type(e),e)# 第一层 class ZeroDivisionError division by zero若是内层能处理则作为正常返回若是raise抛出一个异常则外层继续接收try:try:try:print(1/0)exceptExceptionase:print(第三层,e)raise(e)exceptExceptionase:print(第二层,e)exceptExceptionase:print(第一层,type(e),e)# 第一层 class ZeroDivisionError division by zerowithPython中的with语句用于异常处理封装了try except finally编码范式提供了一种简洁的方式来确保资源的正确获取和释放同时处理可能发生的异常提高了易用性。使代码更清晰、更具可读性简化了文件流等公共资源的管理。语法withexpressionasvariable:# 代码块expression通常是一个对象或函数调用该对象需要是一个上下文管理器即实现了 __enter__和__exit__方法。variable是可选的用于存储expression的__enter__方法的返回值。工作原理with 语句在底层实现了两个魔法方法enter()当你进入 with 块时它负责安全地打开资源比如打开文件。exit()这是真正的魔法 当你离开 with 代码块时无论是正常执行完还是因为报错跳出甚至是你在里面 return 了Python 都会自动且绝对地调用这个方法在这个方法里执行了 f.close()。你永远不可能“忘记”关闭文件因为 Python 把关闭的权力从你手里收回去了它替你代劳了。1常规方式try:fileopen(test.txt,w)file.write(a)file.close()finally:print(文件是否关闭,file.closed)# 文件是否关闭 False【致命痛点资源泄漏】程序执行到 file.write(a) 时因为 a 没有定义瞬间抛出 NameError 异常。异常一出现程序立刻跳出当前的执行流直接跳过了 file.close() 这行代码结果就是程序带着报错退出了但文件 test.txt 依然在内存和操作系统中保持着“打开”的状态。如果这是一个长时间运行的服务器程序随着时间的推移这种没关闭的文件越来越多最终会耗尽操作系统的文件句柄File Descriptors导致整个系统崩溃经典的“Too many open files”错误。2使用 try finallytry:fileopen(test.txt,w)try:file.write(a)finally:file.close()finally:print(文件是否关闭,file.closed)# 文件是否关闭 True进步它绝对安全因为 finally 拥有“免死金牌”无论 file.write(a) 怎么报错file.close() 都一定会被执行。文件被成功关闭了。痛点为什么还需要进化太丑、太啰嗦了 为了写一行数据你不得不嵌套两层 try写了一堆样板代码Boilerplate Code。反人类直觉打开文件的逻辑和关闭文件的逻辑被物理隔开了阅读代码时眼睛要跳来跳去。极易遗忘程序员是人是人就会偷懒或者遗忘。只要有一次忘记写 finally: file.close()第一种方式的致命灾难就会重演。3使用 withtry:withopen(test.txt,w)asf:f.write(a)finally:print(文件是否关闭,f.closed)# 文件是否关闭 TruePython常见异常异常说明AssertionError当 assert 语句失败时将被引发。AttributeError当属性引用或赋值失败时将被引发。IndexError当序列抽取超出范围时将被引发。KeyError当在现有键集合中找不到指定的映射字典键时将被引发。KeyboardInterrupt当用户按下中断键 (通常为 Control-C 或 Delete) 时将被引发。MemoryError当一个操作耗尽内存但情况仍可通过删除一些对象进行挽救时将被引发。NameError当某个局部或全局名称未找到时将被引发。OSError此异常在一个系统函数返回系统相关的错误时将被引发此类错误包括 I/O 操作失败例如 文件未找到 或 磁盘已满 等。SyntaxError当解析器遇到语法错误时引发。TypeError当一个操作或函数被应用于类型不适当的对象时将被引发。 Python 异常处理核心总结异常的本质程序在运行期遇到了处理不了的情况抛出一个错误对象。黄金捕获语法try放置可能会炸的代码。except精准布置网兜捕获特定类型的炸弹异常对象e。else奖励机制。只有try完美执行才会执行这里隔离正常逻辑与异常逻辑。finally免死金牌/终极清道夫。无论死活、无论是否return必定执行。主动抛出raise主动扔出一个炸弹用于业务逻辑报错。assert断言通常用于开发阶段的内部防御和调试。资源管理神兵with语句上下文管理器底层封装了__enter__和__exit__是替代try...finally关闭文件/资源的终极优雅方案。⚠️ 异常处理 7 大易错点全解析新手与老手常踩的坑易错点 1使用“裸”except:吞噬一切异常最臭名昭著的坏习惯案发现场为了不让程序崩溃直接写一个except:把所有错误吃掉。try:do_something()except:# ❌ 灾难裸 exceptpass致命原因它不仅会吃掉代码里的 Bug比如变量名写错的NameError连你按CtrlC想强行停止程序KeyboardInterrupt或者程序正常的退出请求SystemExit都会被它拦截你的程序会变成一个无法被杀死的“僵尸”。正确做法永远使用except Exception as e:。Exception是常规错误的基类它不会拦截系统级别的退出指令。易错点 2except捕获顺序写反大网兜放在了小网兜前面案发现场try:1/0exceptExceptionase:# ❌ 父类大网放在了前面print(捕获通用错误)exceptZeroDivisionError:# ❌ 子类小网放在了后面永远无法执行print(除以零了)底层原因Python 是自上而下匹配异常的。Exception是所有常规异常的“爸爸”如果把它放第一位任何错误都会被它拦下后面的精准捕获就成了死代码。正确做法先抓具体的子类最后抓通用的父类兜底。易错点 3finally里的return会“劫持”真正的返回值案发现场deftest_func():try:returnTry 里的结果finally:returnFinally 里的结果# ❌ 极度危险的做法print(test_func())# 输出结果是什么致命原因输出的竟然是Finally 里的结果因为finally拥有绝对特权如果finally里面写了return或break它会强行覆盖掉try或except里的返回值导致逻辑严重混乱。正确做法永远不要在finally块中使用return或break。finally只应该用来做“清理工作”如关文件、关连接。易错点 4在生产环境中使用assert做数据校验案发现场deflogin(password):# ❌ 用 assert 校验用户输入的密码assertlen(password)6,密码太短print(登录成功)致命原因assert是给程序员自己调试用的。当 Python 代码在生产环境中以**优化模式运行命令加了-O参数python -O main.py**运行时所有的assert语句会被彻底忽略直接跳过这会导致你的安全防线瞬间形同虚设。正确做法对外部输入的数据校验永远使用if... raise ValueError(密码太短)。易错点 5混淆了else和“写在try外面的代码”案发现场以为把依赖try结果的代码直接写在后面就行了。try:datafetch_data_from_network()exceptNetworkError:print(网络错误)process(data)# ❌ 易错如果上面走入了 exceptdata 根本没定义这里会引发 NameError 导致程序二次崩溃正确做法将必须依赖try成功才能执行的代码放入else中。try:datafetch_data_from_network()exceptNetworkError:print(网络错误)else:process(data)# ✅ 只有网络请求真成功了才会处理数据。易错点 6误以为with内部不会报错案发现场withopen(test.txt,r)asf:# 认为用了 with 就绝对安全了不再需要 trycontentf.read()致命原因with语句仅仅是替你把finally: f.close()给做了保证文件一定关闭但它不会替你捕获FileNotFoundError文件不存在或者读取过程中的错误。正确做法如果文件可能不存在依然需要把with抱在try...except里面。try:withopen(test.txt,r)asf:contentf.read()exceptFileNotFoundError:print(文件找不到了)易错点 7把异常当成常规的if-else控制流来用案发现场# ❌ 糟糕的代码味道滥用异常try:itemmy_list[10]exceptIndexError:itemNone致命原因异常处理在底层的性能开销比普通的if判断要大。异常顾名思义应该用于“非预期的、偶然发生的错误”。如果是可以预见的逻辑应该用普通条件判断。正确做法# ✅ 优雅的做法iflen(my_list)10:itemmy_list[10]else:itemNone

更多文章