# Python 异常

> 原文： [http://zetcode.com/lang/python/exceptions/](http://zetcode.com/lang/python/exceptions/)

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

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

## 在 Python 中捕捉异常

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

```py
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`

```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}")

```

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

```py
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`

```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`值为零，我们将打印警告消息并再次重复输入周期。 这样我们就可以处理错误，并且脚本不会中断。

```py
$ ./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`

```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`关键字之后指定。

```py
except ValueError:
    pass
except (IOError, OSError):
    pass

```

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

## `ValueError`

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

`value_error.py`

```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`异常。

```py
if age < 0 or age > 130:
    raise ValueError("Invalid age")

return age

```

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

## Python 多个异常

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

`multiple_exceptions.py`

```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`。

```py
os.mkdir('newdir')

```

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

```py
raise RuntimeError("Runtime error occurred")

```

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

## Python 异常参数

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

`exception_as.py`

```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__)

```

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

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

```

## Python 异常层次结构

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

`interrupt.py`

```py
#!/usr/bin/env python

# interrupt.py

try:
    while True:
       pass

except KeyboardInterrupt:

    print("Program interrupted")

```

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

```py
Exception
  BaseException
    KeyboardInterrupt

```

这是`KeyboardInterrupt`异常的层次结构。

`interrupt2.py`

```py
#!/usr/bin/env python

# interrupt2.py

try:
    while True:
        pass

except BaseException:

    print("Program interrupted")

```

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

## Python 用户定义的异常

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

`user_defined.py`

```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`作为异常。

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

```

## 清理

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

`cleanup.py`

```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`

```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`函数中将除以零的异常。

```py
for line in traceback.format_stack():

```

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

```py
$ ./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 中的异常。