Python 中的异常
最后修改于 2023 年 10 月 18 日
在本 Python 教程中,我们将讨论 Python 中的异常。
在执行期间检测到的错误称为异常。在我们的应用程序执行期间,可能会出现许多问题。磁盘可能已满,我们无法保存文件。互联网连接可能中断,我们的应用程序尝试连接到某个站点。所有这些都可能导致我们的应用程序崩溃。为了防止这种情况,我们必须处理可能发生的所有错误。为此,我们可以使用异常处理。
在 Python 中捕获异常
在 Python 中,我们有以下语法来处理异常
try: # do something except ValueError: # handle ValueError exception except (IndexError, ZeroDivisionError): # handle multiple exceptions # IndexError and ZeroDivisionError except: # handle all other exceptions finally: # cleanup resources
我们期望出现异常的代码写在 try 块中。except 关键字捕获程序中指定的或剩余的异常。可选的 finally 块始终被执行;它用于清理资源,例如打开的文件或数据库连接。
ZeroDivisionError
不能除以零。如果我们尝试这样做,则会引发 ZeroDivisionError 并中断脚本。
ZeroDivisionError 更直接。#!/usr/bin/env python
# zero_division.py
def input_numbers():
a = float(input("Enter first number:"))
b = float(input("Enter second number:"))
return a, b
x, y = input_numbers()
print(f"{x} / {y} is {x/y}")
在此脚本中,我们从控制台获取两个数字。我们将这两个数字相除。如果第二个数字为零,则会得到一个异常。
Enter first number:3
Enter second number:0
Traceback (most recent call last):
File "C:/Users/Jano/PycharmProjects/Simple/simple.py", line 14, in <module>
print(f"{x} / {y} is {x/y}")
ZeroDivisionError: float division by zero
我们可以通过两种方式处理这个问题。
#!/usr/bin/env python
# zero_division2.py
def input_numbers():
a = float(input("Enter first number:"))
b = float(input("Enter second number:"))
return a, b
x, y = input_numbers()
while True:
if y != 0:
print(f"{x} / {y} is {x/y}")
break
else:
print("Cannot divide by zero")
x, y = input_numbers()
首先,我们简单地检查 y 值是否不为零。如果 y 值为零,则打印警告消息并再次重复输入周期。通过这种方式,我们处理了错误,并且脚本没有中断。
$ ./zero_division2.py Enter first number:4 Enter second number:0 Cannot divide by zero Enter first number:5 Enter second number:0 Cannot divide by zero Enter first number:5 Enter second number:6 5.0 / 6.0 is 0.8333333333333334
另一种方法是使用异常。
#!/usr/bin/env python
# zerodivision3.py
def input_numbers():
a = float(input("Enter first number:"))
b = float(input("Enter second number:"))
return a, b
x, y = input_numbers()
try:
print(f"{x} / {y} is {x/y}")
except ZeroDivisionError:
print("Cannot divide by zero")
x, y = input_numbers()
我们将期望异常的代码放在 try 关键字之后。如果引发了异常,则 except 关键字会捕获该异常。异常类型在 except 关键字之后指定。
except ValueError:
pass
except (IOError, OSError):
pass
要处理更多异常,我们可以使用更多 except 关键字,或者将异常名称放在一个元组中。
ValueError
当内置操作或函数接收到的参数类型正确但值不合适,并且情况未由更精确的异常描述时,将引发 ValueError。
#!/usr/bin/env python
# value_error.py
def read_age():
age = int(input("Enter your age: "))
if age < 0 or age > 130:
raise ValueError("Invalid age")
return age
try:
val = read_age()
print(f"Your age is {val}")
except ValueError as e:
print(e)
在示例中,我们有一个从用户那里读取年龄作为输入的函数。当用户提供不正确的值时,我们会引发 ValueError 异常。
if age < 0 or age > 130:
raise ValueError("Invalid age")
return age
负年龄没有意义,而且在现代没有记录超过 130 岁的人。
Python 多重异常
可以在一个 except 子句中捕获多个异常。
#!/usr/bin/env python
# multiple_exceptions.py
import os
try:
os.mkdir('newdir')
print('directory created')
raise RuntimeError("Runtime error occurred")
except (FileExistsError, RuntimeError) as e:
print(e)
代码示例在一个 except 语句中捕获了两个异常:FileExistsError 和 RuntimeError。
os.mkdir('newdir')
使用 os.mkdir 方法创建一个新目录。如果该目录已经存在,则会触发 FileExistsError。
raise RuntimeError("Runtime error occurred")
我们使用 raise 关键字手动引发运行时异常。
Python 异常参数
异常可以有一个关联的值,该值指示错误的详细原因。该值放在 as 关键字之后。
#!/usr/bin/env python
# exception_argument.py
try:
a = (1, 2, 3, 4)
print(a[5])
except IndexError as e:
print(e)
print("Class:", e.__class__)
从异常对象中,我们可以获取错误消息或类名。
$ ./exception_as.py tuple index out of range Class: <class 'IndexError'>
Python 异常的层次结构
异常按层次结构组织,Exception 是所有异常的父类。
#!/usr/bin/env python
# interrupt.py
try:
while True:
pass
except KeyboardInterrupt:
print("Program interrupted")
脚本开始一个无休止的循环。如果我们按下 Ctrl+C,我们会中断循环。在这里,我们捕获了 KeyboardInterrupt 异常。
Exception
BaseException
KeyboardInterrupt
#!/usr/bin/env python
# interrupt2.py
try:
while True:
pass
except BaseException:
print("Program interrupted")
此示例也有效。BaseException 也会捕获键盘中断;以及其他异常。但是,这不是一个好的做法。我们应该在 except 子句中捕获特定的异常。
Python 用户定义的异常
如果需要,我们可以创建自己的异常。我们通过定义一个新的异常类来做到这一点。
#!/usr/bin/env python
# user_defined.py
class BFoundEx(Exception):
def __init__(self, value):
self.par = value
def __str__(self):
return f"BFoundEx: b character found at position {self.par}"
string = "There are beautiful trees in the forest."
pos = 0
for i in string:
try:
if i == 'b':
raise BFoundEx(pos)
pos = pos + 1
except BFoundEx as e:
print(e)
在我们的代码示例中,我们创建了一个新异常。该异常派生自基本 Exception 类。如果我们在字符串中找到字母 b 的任何出现,我们会 raise 我们的异常。
$ ./user_defined.py 'BFoundEx: b character found at position 10'
清理
有一个 finally 关键字,它始终被执行。无论是否引发异常。它通常用于清理程序中的某些资源。
#!/usr/bin/env python
# cleanup.py
f = None
try:
f = open('data.txt', 'r')
contents = f.readlines()
for line in contents:
print(line.rstrip())
except IOError:
print('Error opening file')
finally:
if f:
f.close()
在我们的示例中,我们尝试打开一个文件。如果我们无法打开该文件,则会引发 IOError。如果打开了文件,我们希望关闭文件句柄。为此,我们使用 finally 关键字。在 finally 块中,我们检查文件是否已打开。如果已打开,我们将其关闭。这是我们使用数据库时常见的编程结构。在那里,我们以类似的方式清理打开的数据库连接。
堆栈跟踪
堆栈跟踪 显示在未捕获的异常被抛出时调用堆栈(调用到该点的函数堆栈)。Python traceback 模块提供了一个标准接口,用于提取、格式化和打印 Python 程序的堆栈跟踪。它完全模仿了 Python 解释器在打印堆栈跟踪时的行为。
#!/usr/bin/env python
# stacktrace_ex.py
import traceback
def myfun():
def myfun2():
try:
3 / 0
except ZeroDivisionError as e:
print(e)
print("Class:", e.__class__)
for line in traceback.format_stack():
print(line.strip())
myfun2()
def test():
myfun()
test()
在示例中,我们在嵌套的 myfun2 函数中有一个除以零的异常。
for line in traceback.format_stack():
format_stack 从当前的堆栈帧中提取原始的回溯信息,并将其格式化为元组列表。我们使用 for 循环遍历元组列表。
$ ./stacktrace_ex.py
division by zero
Class: <class 'ZeroDivisionError'>
File "C:/Users/Jano/PycharmProjects/Simple/simple.py", line 30, in <module>
test()
File "C:/Users/Jano/PycharmProjects/Simple/simple.py", line 27, in test
myfun()
File "C:/Users/Jano/PycharmProjects/Simple/simple.py", line 23, in myfun
myfun2()
File "C:/Users/Jano/PycharmProjects/Simple/simple.py", line 20, in myfun2
for line in traceback.format_stack():
在程序中,我们可以看到调用堆栈 - 导致错误的已调用函数的顺序。
在本章中,我们已经介绍了 Python 中的异常。