Python 异常
本文介绍 Python 中的异常 ,执行期间检测到的错误称为异常。 在执行应用期间,许多事情可能出错。 磁盘可能已满,我们无法保存文件。 Internet 连接可能断开,我们的应用尝试连接到站点。 所有这些都可能导致我们的应用崩溃,为防止这种情况,我们必须应对可能发生的所有可能的错误,为此,我们可以使用异常处理。
在 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
并中断脚本。
注意: 下面的示例演示了异常在Python中的工作方式。更直接的方法是确保除数不是零而不是捕获 ZeroDivisionError
.
zero_division.py
| #!/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
|
我们可以通过两种方式处理此问题。
zero_division2.py
| #!/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
|
另一种方法是使用异常。
zero_division3.py
| #!/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
|
要处理更多的异常,我们可以使用除关键字之外的更多异常,也可以将异常名称放在元组中。
ValueError
当内置操作或函数接收到类型正确但值不合适的自变量时,会引发ValueError
,并且无法通过更精确的异常描述这种情况。
value_error.py
| #!/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
子句中捕获多个异常。
multiple_exceptions.py
| #!/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()
方法创建一个新目录。 如果目录已经存在,则触发FileExistsError
。
| raise RuntimeError("Runtime error occurred")
|
我们使用raise
关键字手动引发运行时异常。
Python 异常参数
异常可以具有关联的值,该值指示错误的详细原因。 该值放在as
关键字之后。
exception_as.py
| #!/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
。
interrupt.py
| #!/usr/bin/env python
# interrupt.py
try:
while True:
pass
except KeyboardInterrupt:
print("Program interrupted")
|
脚本开始并不断循环。 如果按 Ctrl
+ C
,我们将中断循环。 在这里,我们捕获了KeyboardInterrupt
异常。
| Exception
BaseException
KeyboardInterrupt
|
这是KeyboardInterrupt
异常的层次结构。
interrupt2.py
| #!/usr/bin/env python
# interrupt2.py
try:
while True:
pass
except BaseException:
print("Program interrupted")
|
这个例子也可以。 BaseException
也捕获键盘中断; 除其他异常。 但是,这不是一个好习惯。 我们应该在except
子句中捕获特定的异常。
Python 用户定义的异常
如果需要,我们可以创建自己的异常。 我们通过定义一个新的异常类来做到这一点。
user_defined.py
| #!/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
关键字,始终会执行。 不管是否引发异常。 它通常用于清理程序中的资源。
cleanup.py
| #!/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 解释器在打印堆栈跟踪时的行为。
stacktrace_ex.py
| #!/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():
|
在程序中,我们可以看到调用堆栈-导致错误的被调用函数的顺序。