异常

当你的程序处于异常的状态的时候,会抛出_异常_。例如当你想要读取一个并不存在的文件的时候,或者当你要删除一个正在运行的程序的时候。这些情况通过异常来处理。

类似的,如果你的程序有一些无效的语句,Python也会抛出错误提示告诉你这里有一些错误

错误

我们来看一下一个简单的print函数。如果我们把print写成了Print会怎样?注意大小写的错误。这是Python会_抛出_一个语法错误。

>>> Print("Hello World")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'Print' is not defined
>>> print("Hello World")
Hello World

我们注意到抛出了一个NameError的错误,以及这个错误发生的位置。这就是当错误发生的时候错误处理程序所做的事情。

异常

我们尝试从控制台读取用户输入的信息,然后按下[ctrl-d]看看会发生什么。

>>> s = input('请输入 --> ')
Enter something --> Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
EOFError

Python抛出了一个名为EOFError的错误信息,他的是end of file的缩写(由ctrl-d触发),这是我们的程序刚开始的时候没有预料到的。

异常处理

我们可以用try..except语句处理异常。我们将正常执行的语句放在try语句块中,然后将错误处理程序放到except语句块中。

例如 (保存为 exceptions_handle.py):

try:
    text = input('请输入 --> ')
except EOFError:
    print('为什么你按下了EOF?')
except KeyboardInterrupt:
    print('你取消了操作')
else:
    print('你输入了 {}'.format(text))

输出为:

# 按下 ctrl + d
$ python exceptions_handle.py
请输入 --> 为什么你按下了EOF?

# Press ctrl + c
$ python exceptions_handle.py
请输入 --> ^C你取消了操作

$ python exceptions_handle.py
请输入 --> No exceptions
你输入了 No exceptions

它是如何工作的:

我们将所有的可能会抛出异常/错误的语句写在try块中,然后将对应的处理程序写在except块中。每个except语句可以处理一个特定的异常/错误,或者是一个异常/错误的列表(用括号表示)。如果没有指明异常/错误的名字,那么他会处理_所有的_错误/异常。

注意,每一个try语句至少应该有一个与之匹配的except语句,否则try语句就没有意义了。

如果你的程序发生了异常/错误,但是没有被处理,那么Python语言就会启动默认的异常处理程序,它会中止程序的运行,打印出错误的信息,这些内容我们已经看到了。

你也可以给你的try..except写上一个else语句块,当没有任何异常发生的时候就会执行else语句的内容。

在下面的例子中,我们将会学习如何获得异常对象,以便于我们能够得到关于异常的更多的信息。

抛出异常

你可以使用raise语句_抛出_一个异常,在语句中你需要提供异常/错误的名称以及抛出的异常对象。

你抛出的异常/错误必须是一个从Exception派生的类。

例如: (保存为exceptions_raise.py):

class ShortInputException(Exception):
    '''用户自定义的异常类。'''
    def __init__(self, length, atleast):
        Exception.__init__(self)
        self.length = length
        self.atleast = atleast

try:
    text = input('请输入 --> ')
    if len(text) < 3:
        raise ShortInputException(len(text), 3)
    # Other work can continue as usual here
except EOFError:
    print('Why did you do an EOF on me?')
except ShortInputException as ex:
    print(('ShortInputException: The input was ' +
           '{0} long, expected at least {1}')
          .format(ex.length, ex.atleast))
else:
    print('No exception was raised.')

输出为:

$ python exceptions_raise.py
请输入 --> a
ShortInputException: The input was 1 long, expected at least 3

$ python exceptions_raise.py
请输入 --> abc
No exception was raised.

它是如何工作的:

在这里我们创建了我们自己的异常类。新的异常类为ShortInputException。他有两个字段:length表示输入内容的长度,atleast表示程序期望的最小长度。

except语句中,我们制定由as变量保存弹出的异常/错误的对象。这很类似函数参数在函数调用中的作用。在这个特殊的except语句中,我们使用异常对象的lengthatleast字段构造了一个异常提示信息,让用户了解为什么会抛出这个异常。

Try ... Finally

设想一下你的程序需要读取一个文件,你怎样保证无论是否有异常抛出,文件对象都被正确的关闭呢?我们可以使用finally语句块做到这一点。

例如:(保存为exceptions_finally.py

import sys
import time

f = None
try:
    f = open("poem.txt")
    # Our usual file-reading idiom
    while True:
        line = f.readline()
        if len(line) == 0:
            break
        print(line, end='')
        sys.stdout.flush()
        print("Press ctrl+c now")
        # To make sure it runs for a while
        time.sleep(2)
except IOError:
    print("Could not find file poem.txt")
except KeyboardInterrupt:
    print("!! You cancelled the reading from the file.")
finally:
    if f:
        f.close()
    print("(Cleaning up: Closed the file)")

输出为:

$ python exceptions_finally.py
Programming is fun
Press ctrl+c now
^C!! You cancelled the reading from the file.
(Cleaning up: Closed the file)

它是如何工作的:

我们读取文件的内容,只是每读一行就让系统休息2秒,我们使用time.sleep函数让程序运行慢一点(正常情况下Python程序运行的飞快)。当程序还在运行的时候,按下ctrl + c键中止程序的运行。

我们注意到当程序退出的时候抛出了KeyboardInterrupt异常。然而,在程序退出之前,执行了finally语句块,并且文件对象被正确的关闭了。

注意,我们在print函数后面调用sys.stdout.flush()函数,这样可以及时输出结果。

with语句

try语句块中获取资源,然后再finally语句块中释放资源是一个非常常用的程序段,我们可以使用with简化一下程序的书写。

例如:(保存为exceptions_using_with.py

with open("poem.txt") as f:
    for line in f:
        print(line, end='')

它是如何工作的:

这段程序的输出应该和之前的例子是一模一样的。唯一的区别在与我们在with语句中使用open函数打开文件,这样的话系统会自动关闭这个文件。

实际的处理过程是这样的,with语句会获取open函数返回的对象,我们假定这个对象名称是"thefile"。

它_总是会_在进入with语句块之前调用thefile.__enter__函数,并且_总是会_在语句块的最后调用thefile.__exit__函数。

这样的话我们之前在finally语句块中写的程序就会自动的在__exit__方法中被执行,这种方式可以防止我们频繁使用try..finally语句。

关于这个主题更多的讨论已经超出了本书的范畴,请参考PEP 343

总结

本章我们讨论了try..excepttry..finally语句,我们还自己定义了一个我们自己的异常类型,并且在程序中将其抛出。

下一步,我们将会浏览一下Python标准库。


继续阅读标准库


书籍推荐