☰
python基础知识之try...except...的详细用法实例
2026/10/13 10:32:02 网站建设 项目流程

前言


先划清范围:这一篇只咬try语句本身的语法。异常是什么、异常体系怎么分层、该在哪一层捕获——那些是机制层面的问题,属于另一篇的话题。这里不讨论「该不该捕获」,只讨论「捕获怎么写才符合语言的规矩」。


try语句的骨架是四个子句:try、except、else、finally。它们各自的执行条件和相互之间的顺序,几乎是 Python 里被误解得最多的语法点之一。几个典型的困惑:



  • 写了好几个except,为什么后面那个从来没被执行过?

  • except ... as e里的e,出了块怎么就用不了了?

  • else到底什么时候执行?finally是不是一定执行?

  • finally里写return会发生什么?


这些问题语言参考里都有白纸黑字的答案,本文逐条对照讲清楚。所有语法细节均可在 Python 语言参考的「try 语句」一节核对。


一、多个except的匹配顺序:子类必须写在前面


语言参考给出的匹配规则是:try子句里一旦发生异常,就会依次检查各个except子句,直到找到一个匹配的为止。而「匹配」的定义是:抛出异常的类,等于该子句表达式求出的类,或者是它的某个父类。


这句话的反面同样重要:父类的子句会拦住所有子类异常。所以下面这段代码里,第二个分支是死代码:


# 适用于 Python 3.8+
def parse(value):
try:
return int(value)
except Exception: # ❌ 先拦住了所有 Exception 的子类
print("出错了")
return None
except ValueError: # 永远不会被执行到
print("不是数字")
return None

ValueError是Exception的子类,int("abc")抛的ValueError在第一个子句就已经匹配上了,第二个永远轮不到。这个坑的隐蔽之处在于:代码不报错,只是分支悄悄失效。


正确写法是把具体类型放前面、笼统类型放最后:


# 适用于 Python 3.8+
def parse(value):
try:
return int(value)
except ValueError: # ✅ 具体的在前
print("不是数字")
return None
except (TypeError, OverflowError):
print("类型或范围不对")
return None
except Exception: # ✅ 兜底放最后
print("未预期的错误")
return None

两条配套规则:



  • 没有表达式的except:子句如果出现,必须放在最后,它匹配任何异常。这不是风格建议,是语法要求。

  • except子句头部的那个表达式,在匹配时被求值。语言参考提到一个细节:如果except头部的表达式求值本身抛了异常,原本的异常处理搜索会被取消,转而按「整个try语句抛出这个新异常」来处理。所以别在except的类型位置上放会出错的表达式。


顺带说一个版本变化:Python 3.14 起,捕获多种异常类型时可以省略那对括号(PEP 758)。也就是说except ValueError, TypeError:在 3.14 里是合法的;但在更早的版本里这是语法错误,必须写except (ValueError, TypeError):。代码要兼容 3.13 及以前时,老老实实带括号。


二、用元组匹配多种异常,以及as的绑定与删除


要捕获多种异常,把类型放进一个元组:


# 适用于 Python 3.8+
data = {"items": [1, 2, 3]}
try:
first = data["items"][10]
except (KeyError, IndexError) as exc:
print("取不到这个元素:", exc)

元组里的每个元素都必须是异常类型,不能是异常实例。把实例放进元组,匹配时不会命中,异常照样往上抛:


# 适用于 Python 3.8+ 反例:把实例当类型
try:
int("abc")
except (ValueError("自定义实例"), TypeError) as exc:
print("这里不会被执行")

ValueError("自定义实例")是一个实例而不是类,匹配规则要求的是「异常类」,所以它永远不会命中。


as绑定的目标,出块后会被删除


语言参考对这一点的表述是:当异常通过as 目标被赋值后,它会在except子句结束时被清除。参考里给出了它等价的翻译:


except E as N:
foo

# 等价于

except E as N:
try:
foo
finally:
del N

也就是说,N这个名字在块结束后会被删掉。这不是疏漏,而是有意的设计——异常对象身上挂着回溯信息,会和栈帧构成引用环,让栈帧里的局部变量一直存活到下一次垃圾回收;删掉这个名字正是为了打破这个环。


后果是:块外再引用这个名字会抛NameError。


# 适用于 Python 3.8+
try:
int("abc")
except ValueError as exc:
message = str(exc) # 需要的信息在块内提取出来
print(message) # ✅ 可用
print(exc) # ❌ NameError: name 'exc' is not defined

想在块外使用异常对象,就在块内先赋给另一个名字。


另外,语言参考还提到:在执行except子句的代码块之前,异常会被存放在sys模块里,可以在块内通过sys.exception()取得(3.11 起提供)。离开异常处理器时,sys里保存的异常会恢复到之前的值。这条是给少数需要访问「当前异常」的场景用的,日常写as exc就够了。


Python 2 的写法对照


这一段是 2 与 3 差异的重灾区:




用途Python 2Python 3



绑定异常对象except ValueError, e:except ValueError as e:

块外能否访问e能,变量仍然存在不能,块结束即被删除

捕获多种except (ValueError, TypeError), e:except (ValueError, TypeError) as e:



Python 2.7 已于 2020 年 1 月 1 日停止维护(PEP 373)。看到except X, e这种逗号写法的代码,一律按上表改写,不要直接拿过来跑——在 Python 3 里那个逗号会被解析成别的东西,行为完全不是你想要的那样。


三、else与finally的精细语义


else:只在「顺利走完且没提前跳走」时执行


语言参考对else的定义有三个条件,缺一不可:控制流从try子句正常离开、没有异常被抛出、并且没有执行return、continue或break。


最后一条容易被忽略。看这个例子:


# 适用于 Python 3.8+
def find_first_even(numbers):
for n in numbers:
try:
if n % 2 == 0:
break # 提前跳出,else 不会执行
except TypeError:
continue
else:
print("循环正常结束,没有提前 break")
return None
print("找到了")
return n

break一执行,else就被跳过了。这正好对应try的else与「循环的else」在直觉上的一致性——两者都表示「这段流程完整走完了,没有被中途打断」。


还有一个关键性质:else子句里抛出的异常,不会被前面那些except子句处理。语言参考明确写了这一点。原因在于else的定位是「try成功之后才做的事」,它的错误应当往上传播,而不是被自己刚刚走完的这段逻辑回头接住。


finally:无论怎样都会执行


finally是清理子句:try子句会被执行,包括其中的except和else;无论中途发生了什么,finally都会被执行。几个精确的行为:



  • 如果有未处理的异常被暂时保存,它会在finally子句结束时被重新抛出。

  • 如果finally子句自己抛出了另一个异常,被保存的那个异常会被设置为新异常的上下文(即挂到__context__上)。

  • 如果finally里执行了return、break或continue这类「跳走」的语句,被保存的异常会被丢弃。


最后一条的后果很惊人,下一节专门讲。


执行顺序可以归纳成一张表:




情况执行顺序



try顺利走完try→else→finally

try中抛出并被某个except捕获try(到出错处)→except→finally

try中抛出且无人捕获try(到出错处)→finally→ 异常继续向上抛

try中执行了returnfinally先执行,然后才真正返回

else中抛出异常try→else(到出错处)→finally→ 异常向上抛(前面的except不处理它)



四、finally里的return会覆盖try里的return


语言参考把规则讲得很干脆:函数返回值由最后执行的那条return语句决定。而finally子句总会执行,所以finally里的return一定是最后执行的那一条。


# 适用于 Python 3.8+
def foo():
try:
return "try"
finally:
return "finally"

调用foo()得到的是"finally",不是"try"。try里那条return已经把返回值算好了,但finally里又执行了一条return,后者覆盖了前者。


同样的机制会丢弃异常。语言参考给的例子是:下面这个函数返回42,而不是抛出ZeroDivisionError:


# 适用于 Python 3.8+
def f():
try:
1 / 0
finally:
return 42

1 / 0抛出ZeroDivisionError,本来会被暂存、准备在finally结束后重新抛出;但finally里执行了return,按规则「被保存的异常被丢弃」,于是这个除零错误凭空消失了。这是最危险的一类坑:错误被无声无息地吃掉,排查时连条线索都没有。


break和continue有同样的效果,同样会把暂存的异常丢掉。


也正因为如此,语言在版本间不断收紧这件事:



  • Python 3.8 之前,finally子句里出现continue是非法的(这是实现上的历史遗留问题)。

  • Python 3.14 起,编译器会在finally块里出现return、break或continue时发出SyntaxWarning(PEP 765)——这是在提醒你,这里很可能不是你想要的行为。


结论很直接:不要在finally里写return/break/continue。清理逻辑就只做清理,把返回值留给try的正常出口。


常见坑点


1. 把父类异常写在子类前面


❌except Exception:写在except KeyError:前面,后者永远匹配不到。 ✅ 具体的异常类型写前面,笼统的兜底写最后,无表达式的except:必须放最末。


2. 在except块外使用as绑定的名字


❌except ValueError as e: pass之后又print(e),得到NameError。 ✅ Python 3 在块结束时会删除这个名字;块内先赋给别的变量再用。


3. 用 Python 2 的逗号语法


❌except ValueError, e:或except (ValueError, TypeError), e:。 ✅ 一律改成except ValueError as e:/except (ValueError, TypeError) as e:;Py2 已停维护。


4. 以为break之后else还会执行


❌try里if 条件: break,还指望else里的收尾代码跑一遍。 ✅ 执行了return、break、continue中任意一个,else都不会执行。


5. 以为else里的异常会被本层的except接住


❌ 把「结果处理」写在else里,期待出错时被上面的except兜住。 ✅else的异常不会由前面的except处理,会直接往上传播;该兜就写在try里。


6. 在finally里return


❌finally: return result,把try里的返回值、甚至正在传播的异常一并覆盖掉。 ✅finally只做清理(关闭文件、释放锁),返回值交给try或else的正常出口。


7. 用finally吃掉了异常还不知道


❌try里除零,finally里恰好有条return,异常被静默丢弃。 ✅ 记住规则:finally里执行return、break或continue会丢弃暂存的异常;3.14 起这还会触发SyntaxWarning。


8. 在except类型位置上写会在求值时出错的东西


❌ 把一次函数调用、一次字典取值放在except的类型位置,它求值失败会取消原本的处理搜索。 ✅ 类型位置只放异常类或异常类的元组,动态选择类型就先算好存成变量。


总结




语法点精确规则



匹配顺序依次检查except子句直到命中;子类必须写在父类前面

无表达式的except:必须放在最后,匹配任何异常

元组捕获except (A, B):匹配 A 或 B;3.14 起可省略括号(PEP 758)

as绑定绑定到目标名,块结束时被删除;要在块外使用需另存

else仅在顺利离开try、无异常、且未执行return/break/continue时执行;其异常不被本层except处理

finally总会执行;暂存的异常会在其后重抛

finally里跳走执行return/break/continue会丢弃暂存的异常

返回值由最后执行的return决定,所以finally里的return会覆盖try里的



try语句的语法细节不多,但每一条都有明确的后果:匹配顺序错了,分支会静默失效;忘了as变量会被删除,块外就报NameError;在finally里return,异常会被悄悄吃掉。把上表这几行记住,写出来的try就不会再有意外的行为了。

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

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

立即咨询