原文: http://zetcode.com/lang/python/exceptions/

在 Python 教程的这一部分中,我们讨论 Python 中的异常。

执行期间检测到的错误称为异常。 在执行应用期间,许多事情可能出错。 磁盘可能已满,我们无法保存文件。 互联网连接可能断开,我们的应用尝试连接到站点。 所有这些都可能导致我们的应用崩溃。 为防止这种情况,我们必须应对可能发生的所有可能的错误。 为此,我们可以使用异常处理。

在 Python 中捕捉异常

在 Python 中,我们具有以下语法来处理异常:

  1. try:
  2. # do something
  3. except ValueError:
  4. # handle ValueError exception
  5. except (IndexError, ZeroDivisionError):
  6. # handle multiple exceptions
  7. # IndexError and ZeroDivisionError
  8. except:
  9. # handle all other exceptions
  10. finally:
  11. # cleanup resources

我们期望发生异常的代码写在try块中。 except关键字捕获程序中指定的或剩余的异常。 始终执行可选的finally块; 它用于清除资源,例如打开的文件或数据库连接。

ZeroDivisionError

除以零是不可能的。 如果我们尝试执行此操作,则会引发ZeroDivisionError并中断脚本。

注意:以下示例演示了异常在 Python 中的工作方式。 确保除数不为零而不是捕获ZeroDivisionError更直接。

zero_division.py

  1. #!/usr/bin/env python
  2. # zero_division.py
  3. def input_numbers():
  4. a = float(input("Enter first number:"))
  5. b = float(input("Enter second number:"))
  6. return a, b
  7. x, y = input_numbers()
  8. print(f"{x} / {y} is {x/y}")

在此脚本中,我们从控制台获得两个数字。 我们将这两个数相除。 如果第二个数字为零,则会出现异常。

  1. Enter first number:3
  2. Enter second number:0
  3. Traceback (most recent call last):
  4. File "C:/Users/Jano/PycharmProjects/Simple/simple.py", line 14, in <module>
  5. print(f"{x} / {y} is {x/y}")
  6. ZeroDivisionError: float division by zero

我们可以通过两种方式处理此问题。

zero_division2.py

  1. #!/usr/bin/env python
  2. # zero_division2.py
  3. def input_numbers():
  4. a = float(input("Enter first number:"))
  5. b = float(input("Enter second number:"))
  6. return a, b
  7. x, y = input_numbers()
  8. while True:
  9. if y != 0:
  10. print(f"{x} / {y} is {x/y}")
  11. break
  12. else:
  13. print("Cannot divide by zero")
  14. x, y = input_numbers()

首先,我们仅检查y的值不为零。 如果y值为零,我们将打印警告消息并再次重复输入周期。 这样我们就可以处理错误,并且脚本不会中断。

  1. $ ./zero_division2.py
  2. Enter first number:4
  3. Enter second number:0
  4. Cannot divide by zero
  5. Enter first number:5
  6. Enter second number:0
  7. Cannot divide by zero
  8. Enter first number:5
  9. Enter second number:6
  10. 5.0 / 6.0 is 0.8333333333333334

另一种方法是使用异常。

zero_division3.py

  1. #!/usr/bin/env python
  2. # zerodivision3.py
  3. def input_numbers():
  4. a = float(input("Enter first number:"))
  5. b = float(input("Enter second number:"))
  6. return a, b
  7. x, y = input_numbers()
  8. try:
  9. print(f"{x} / {y} is {x/y}")
  10. except ZeroDivisionError:
  11. print("Cannot divide by zero")
  12. x, y = input_numbers()

我们将代码放置在try关键字之后我们期望发生异常的位置。 如果引发了except关键字,则捕获该异常。 异常类型在except关键字之后指定。

  1. except ValueError:
  2. pass
  3. except (IOError, OSError):
  4. pass

要处理更多的异常,我们可以使用除关键字之外的更多异常,也可以将异常名称放在元组中。

ValueError

当内置操作或函数接收到类型正确但值不合适的自变量时,会引发ValueError,并且无法通过更精确的异常描述这种情况。

value_error.py

  1. #!/usr/bin/env python
  2. # value_error.py
  3. def read_age():
  4. age = int(input("Enter your age: "))
  5. if age < 0 or age > 130:
  6. raise ValueError("Invalid age")
  7. return age
  8. try:
  9. val = read_age()
  10. print(f"Your age is {val}")
  11. except ValueError as e:
  12. print(e)

在该示例中,我们有一个读取年龄作为用户输入的函数。 当用户提供不正确的值时,我们将引发ValueError异常。

  1. if age < 0 or age > 130:
  2. raise ValueError("Invalid age")
  3. return age

负年龄是没有道理的,在现代没有记录到年龄超过 130 岁的人。

Python 多个异常

可以在一个except子句中捕获多个异常。

multiple_exceptions.py

  1. #!/usr/bin/env python
  2. # multiple_exceptions.py
  3. import os
  4. try:
  5. os.mkdir('newdir')
  6. print('directory created')
  7. raise RuntimeError("Runtime error occurred")
  8. except (FileExistsError, RuntimeError) as e:
  9. print(e)

该代码示例在一个except语句中捕获了两个异常:FileExistsErrorRuntimeError

  1. os.mkdir('newdir')

使用os.mkdir()方法创建一个新目录。 如果目录已经存在,则触发FileExistsError

  1. raise RuntimeError("Runtime error occurred")

我们使用raise关键字手动引发运行时异常。

Python 异常参数

异常可以具有关联的值,该值指示错误的详细原因。 该值放在as关键字之后。

exception_as.py

  1. #!/usr/bin/env python
  2. # exception_argument.py
  3. try:
  4. a = (1, 2, 3, 4)
  5. print(a[5])
  6. except IndexError as e:
  7. print(e)
  8. print("Class:", e.__class__)

从异常对象中,我们可以获取错误消息或类名。

  1. $ ./exception_as.py
  2. tuple index out of range
  3. Class: <class 'IndexError'>

Python 异常层次结构

异常是按层次结构组织的,它们是所有异常的父级Exception

interrupt.py

  1. #!/usr/bin/env python
  2. # interrupt.py
  3. try:
  4. while True:
  5. pass
  6. except KeyboardInterrupt:
  7. print("Program interrupted")

脚本开始并不断循环。 如果按 Ctrl + C ,我们将中断循环。 在这里,我们捕获了KeyboardInterrupt异常。

  1. Exception
  2. BaseException
  3. KeyboardInterrupt

这是KeyboardInterrupt异常的层次结构。

interrupt2.py

  1. #!/usr/bin/env python
  2. # interrupt2.py
  3. try:
  4. while True:
  5. pass
  6. except BaseException:
  7. print("Program interrupted")

这个例子也可以。 BaseException也捕获键盘中断; 除其他异常。 但是,这不是一个好习惯。 我们应该在except子句中捕获特定的异常。

Python 用户定义的异常

如果需要,我们可以创建自己的异常。 我们通过定义一个新的异常类来做到这一点。

user_defined.py

  1. #!/usr/bin/env python
  2. # user_defined.py
  3. class BFoundEx(Exception):
  4. def __init__(self, value):
  5. self.par = value
  6. def __str__(self):
  7. return f"BFoundEx: b character found at position {self.par}"
  8. string = "There are beautiful trees in the forest."
  9. pos = 0
  10. for i in string:
  11. try:
  12. if i == 'b':
  13. raise BFoundEx(pos)
  14. pos = pos + 1
  15. except BFoundEx as e:
  16. print(e)

在我们的代码示例中,我们创建了一个新的异常。 异常是从基类Exception派生的。 如果在字符串中发现字母b的任何出现,我们将raise作为异常。

  1. $ ./user_defined.py
  2. 'BFoundEx: b character found at position 10'

清理

有一个finally关键字,始终会执行。 不管是否引发异常。 它通常用于清理程序中的资源。

cleanup.py

  1. #!/usr/bin/env python
  2. # cleanup.py
  3. f = None
  4. try:
  5. f = open('data.txt', 'r')
  6. contents = f.readlines()
  7. for line in contents:
  8. print(line.rstrip())
  9. except IOError:
  10. print('Error opening file')
  11. finally:
  12. if f:
  13. f.close()

在我们的示例中,我们尝试打开一个文件。 如果我们无法打开文件,则会弹出IOError。 如果我们打开文件,我们想关闭文件处理器。 为此,我们使用finally关键字。 在finally块中,我们检查文件是否已打开。 如果打开了,我们将其关闭。 当我们使用数据库时,这是一种常见的编程结构。 在那里,我们类似地清理打开的数据库连接。

栈跟踪

栈跟踪显示了引发未捕获的异常时的调用栈(至此为止已调用的函数栈)。 Python traceback模块提供了一个标准接口,用于提取,格式化和打印 Python 程序的栈跟踪。 它精确地模仿了 Python 解释器在打印栈跟踪时的行为。

stacktrace_ex.py

  1. #!/usr/bin/env python
  2. # stacktrace_ex.py
  3. import traceback
  4. def myfun():
  5. def myfun2():
  6. try:
  7. 3 / 0
  8. except ZeroDivisionError as e:
  9. print(e)
  10. print("Class:", e.__class__)
  11. for line in traceback.format_stack():
  12. print(line.strip())
  13. myfun2()
  14. def test():
  15. myfun()
  16. test()

在示例中,我们在嵌套的myfun2函数中将除以零的异常。

  1. for line in traceback.format_stack():

format_stack()从当前栈帧中提取原始回溯并将其格式化为元组列表。 我们使用for循环遍历元组列表。

  1. $ ./stacktrace_ex.py
  2. division by zero
  3. Class: <class 'ZeroDivisionError'>
  4. File "C:/Users/Jano/PycharmProjects/Simple/simple.py", line 30, in <module>
  5. test()
  6. File "C:/Users/Jano/PycharmProjects/Simple/simple.py", line 27, in test
  7. myfun()
  8. File "C:/Users/Jano/PycharmProjects/Simple/simple.py", line 23, in myfun
  9. myfun2()
  10. File "C:/Users/Jano/PycharmProjects/Simple/simple.py", line 20, in myfun2
  11. for line in traceback.format_stack():

在程序中,我们可以看到调用栈-导致错误的被调用函数的顺序。

在本章中,我们介绍了 Python 中的异常。