Python 错误和异常

错误和异常


在编写代码的时候,先不管出于什么原因,在运行阶段,可能都看到过一些错误的信息。这些信息当中(至少)有两种可区分的错误:语法错误异常

语法错误


语法错误也叫解析错误,这可能在学习编码中最容易遇到的错误:

>>> while True print('Hello World')
  File "<stdin>", line 1
    while True print('Hello World')
                   ^
SyntaxError: invalid syntax

当出现错误时,解释器会输出出现语法错误的那行,同时会显示 ^ 符号标记检测到的错误。符号标记的位置表示错误出现在此处(或表示至少错误是在此处被检测出)。在这个示例中,print() 函数被检测出错误,这是因为前面的条件语句少了个冒号(:)。在错误信息中,会出现行号以及文件名,这都能方便在出现错误的时候到相应的位置检查。

异常


有时候,检查编写的代码发现并没有什么语法错误,但在执行时,还是会引发错误。这种在执行时检测到的错误被称为异常。大多数异常并不会被程序处理,会显示如下的错误信息:

>>> 10 / 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero

>>> x + 4
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined

>>> 1 + '1'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'

如上示例,错误信息最后一行会告诉我们程序遇到的是什么类型的错误。在上面的例子中,出现的异常的类型分别是:ZeroDivisionErrorNameErrorTypeError。类型后面部分的信息则表示根据异常类型及其原因提供详细的信息。

异常处理

python 内置 try...except... 错误处理机制。

用例子看下 try...except... 机制:

try:
    r = 10 / 0
    print('r=', r)
except ZeroDivisionError as e:
    print('except:', e)

print('END')

程序运行结果如下:

except: division by zero
END

在这里说明一下 try 语句的工作原理:

  • 首先,执行 try 子句(也就是 try 和 except 关键字之间的语句)。
  • 如果这个时候没有异常产生,则会跳过 except 子句,完成 try 语句的执行。
  • 如果在执行 try 子句的时候发生异常,则会跳过该子句中剩下的部分(例如这个例子中,10 / 0 会产生异常,后面的 print('r=', r) 语句并没有执行)。这个时候,如果异常的类型与 except 关键字后面的异常类型匹配,则会执行 except 子句。最后继续执行整个 try 语句之后的代码(例如本例子中 print('END'))。
  • 如果发生的异常和 except 关键字后面指定的异常不匹配,则会向外部进行传递;若是没有找到处理程序,则认定为未处理异常,执行的时候将会停止,并显示前面提及的错误信息。

在这个例子当中, try 子句中的 10 / 0 出现异常,故后面的 print() 子句的内容并没有输出。然后,与 except 后面的异常匹配,所以输出异常信息 except: division by zero,最后输出整个 try 语句之后的 print('END') 语句。

这就是 try...except... 捕获异常处理异常的大致原理。

try...except 语句后面还有一个可选的 else 子句,使用时放在 except 子句后面。在 try 子句不发生异常时执行。对前面的例子进行一些修改,比如:

try:
    r = 10 / 1
    print('r=', r)
except ZeroDivisionError as e:
    print('except:', e)
else:
	print('No error!')

print('END')

最终输出的结果:

r= 10.0
No error!
END
抛出异常

raise 语句允许编码者强制抛出指定异常。例如:

>>> raise NameError('An error occurred here')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: An error occurred here

raise 唯一的参数就是要抛出的异常。这个参数必须是一个异常实例或者是一个异常类(派生自 Exception 的类)。

如果是打算确定是否发生了异常,但不打算处理的情况下,可以使用简单的 raise 语句形式重新引发异常。比如:

>>> try:
...     raise NameError('An error occurred here.')
... except NameError:
...     print('An exception flew by!')
...     raise
...
An exception flew by!
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
NameError: An error occurred here.
自定义异常类

程序可以通过创建新的异常类来命名自己的异常。自定义异常类通常直接或间接从 Exception 类派生。

class Error(Exception):
    '''基类
    '''
    pass

class InputError(Error):
    '''输入表达式错误时引发异常

    Attributes:
        expression -- 输入表达式
        message -- 异常输出的信息
    '''
    def __init__(self, expression, message):
        self.expression = expression
        self.message = message

class TransitionError(Error):
    '''状态转换不被允许时抛出异常

    Attributes:
        previous -- 转换前的状态
        next -- 转换后新的状态
        message -- 状态转换不被允许的说明
    '''
    def __init__(self, previous, next, message):
        self.previous = previous
        self.next = next
        self.message = message

一般情况下,自定义异常名称都以 Error 结尾,类似于标准异常的命名。

定义清理操作

try 语句还有一个可选子句 finally。用于定义必须在所有情况下都执行的清理操作。例如:

>>> def divide(a, b):
...     try:
...         res = a / b
...     except ZeroDivisionError:
...         print('division by zero!')
...     else:
...         print('res =', res)
...     finally:
...         print('executing finally clause')
...
>>> divide(10, 1)
res = 10.0
executing finally clause

>>> divide(10, 0)
division by zero!
executing finally clause

>>> divide('10', '1')
executing finally clause
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'

在这里,可以看到,无论什么情况,finally 最终都会被执行。最后的例子中,由于两个字符串相除并没有被 except 子句处理,这里也在执行 finally 子句后,抛出 TypeError 异常。

finally 子句对于释放外部资源(比如读写文件或者网络连接)非常有用,无论是否成功使用资源。

预定义的清理操作

先看一段示例代码:

for line in open('file.txt'):
    print(line, end='')

其实这段代码有一个问题,它在这部分代码执行结束之后,会在一段不确定的时候,让 file 对象一直保持打开的状态。这里可能对简单的脚本并没有太大的影响,但若是程序足够大的话,这里将会是个问题。

python 提供一个 with 语句,允许支持上下文管理协议的对象能够确保它们得到及时和正确的清理:

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

在这里,文件 f 始终会被关闭,不至于浪费资源。

参考资料


来源
  1. “8. Errors and Exceptions”.docs.python.org.Retrieved 24 February 2020.
  2. “Exception hierarchy”.docs.python.org.Retrieved 25 February 2020.

以上就是关于程序错误与异常的相关内容。


欢迎关注微信公众号《书所集录》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值