Python语法速查: 10. 异常
本篇索引
(1)内置异常
(2)自定义异常
(3)主动引发异常
(4)捕捉异常
(5)error模块
(6)with语句
(1)内置异常
● 异常的基类:
以下这些异常作为具体异常的基类,都不会被显式引发,但是可以使用它们捕捉某种错误。
基类名称 | 说明 | ||
---|---|---|---|
BaseException |
所有内置异常的基类,其他所有内置异常都派生自该类。 |
||
Exception | 所有内置的非系统退出异常都派生自此类(即除了:SystemExit, GeneratorExit, KeyboardInterrupt之外的所有内置异常)。所有用户自定义异常也应当派生自此类。 | ||
ArithmeticError | 算术运算异常的基类,包括溢出、除零等等。 | ||
LookupError | 索引和键错误的基类。 | ||
BufferError | 当与缓冲区相关的操作无法执行时引发,一般由用户自行继承使用。 |
● 具体异常
以下异常属于经常被引发的异常。
具体异常名称 | 说明 |
---|---|
以下异常直接继承自:BaseException | |
GeneratorExit | 由生成器的.close()方法引发。 |
KeyboardInterrupt | 由键盘中断(通常为 Ctrl-C)生成。 |
SystemExit | 程序退出,一般系统函数sys.exit()引发。 |
以下异常继承自:BaseException -> Exception | |
StopIteration | 引发后可停止迭代。 |
StopAsyncIteration | 由异步迭代器引发停止迭代。 |
AssertionError | 当 assert 语句失败时引发。 |
AttributeError | 当属性引用或属性赋值失败时引发。 |
EOFError | 当 input() 函数未读取任何数据即达到文件结束条件 (EOF) 时将被引发。(文件操作的诸如read()和readline()方法等I/O操作在遇到EOF时会返回空字符串,而不是引发异常) |
ImportError | 当 import 语句无法找到模块或者 from 无法在模块中找到名称时引发。Python3.3版本后,对此异常添加了name和path属性,用来表示“尝试导入的模块名称”和“触发异常的文件所在的路径”。 |
ModuleNotFoundError | ImportError的子类,Python3.6版本新加入。当一个模块无法找到时由import引发。 |
MemoryError | 可用内存不足(但仍可挽救时)时将被引发。 |
NameError | 在局部或全局命名空间中未找到某个名称时引发。 |
UnboundLocalError | NameError的子类。引用了未绑定的局部变量时引发。 |
OSError | Python3.3起,以下都是OSError的别名:IOError、EnvironmentError、WindowsError(仅限Windows中)。 操作系统错误,主要由os模块中的函数引起。 |
BlockingIOError | OSError的子类。当一个操作阻塞一个设置为非阻塞操作的对象时引发。 |
ChildProcessError | OSError的子类。当对子进程进行操作失败时将引发,对应于Linux系统调用时的errno中的 ECHILD |
ConnectionError | 与连接相关问题的基类。 |
BrokenPipeError | ConnectionError的子类。当试图写入另一端已被关闭的管道,或是试图写入已关闭写入的套接字时引发。对应于Linux系统调用时的errno中的 EPIPE 和 ESHUTDOWN。 |
ConnectionAbortedError | ConnectionError的子类。当连接尝试被对端中止时将被引发。对应于Linux系统调用时的errno中的 ECONNABORTED。 |
ConnectionRefusedError | ConnectionError的子类。当连接尝试被对端拒绝时将被引发。对应于Linux系统调用时的errno中的 ECONNREFUSED。 |
ConnectionResetError | ConnectionError的子类。当连接被对端重置时将被引发。对应于Linux系统调用时的errno中的 ECONNRESET。 |
FileExistsError | OSError的子类。当试图创建一个已存在的文件或目录时将被引发。对应于Linux系统调用时的errno中的 EEXIST。 |
FileNotFoundError | OSError的子类。当所请求的文件或目录不存在时引发。对应于Linux系统调用时的errno中的 ENOENT。 |
InterruptedError | OSError的子类。当系统调用被输入信号中断时引发。对应于Linux系统调用时的errno中的 EINTR。 |
IsADirectoryError | OSError的子类。当请求对一个目录执行文件操作时引发。对应于Linux系统调用时的errno中的 EISDIR。 |
NotADirectoryError | OSError的子类。当请求对一个非目录对象执行目录操作时引发。对应于Linux系统调用时的errno中的 ENOTDIR。 |
PermissionError | OSError的子类。当没有对相应文件操作权限的时候引发。对应于Linux系统调用时的errno中的 EACCESS和 EPERM |
ProcessLookupError | OSError的子类。当给定的进程不存在时引发。对应于Linux系统调用时的errno中的 ESRCH。 |
TimeoutError | OSError的子类。当一个系统函数发生系统级超时的情况下将被引发。对应于Linux系统调用时的errno中的 ETIMEDOUT。 |
ReferenceError | 在弱引用访问某个已被垃圾回收的属性时引发,关于弱引用可参见werkref模块。 |
RuntimeError | 当检测到一个不属于任何其他类别的错误时引发。 |
NotImplementedError | RuntimeError的子类。当基类的抽象方法需要派生类实现,而派生类未实现时引发。 |
RecursionError | RuntimeError的子类。Python解释器检测发现超过最大递归深度时引发。 |
SyntaxError | 解析器语法错误。 |
IndentationError | SyntaxError的子类。缩进错误时引发。 |
TabError | IndentationError的子类。当缩进包含对制表符和空格符不一致的使用时引发。 |
SystemError | Python解释器中的内部错误。 |
TypeError | 当一个操作或函数被应用于类型不适当的对象时引发。 |
ValueError | 当操作或函数接收到具有正确类型但值不适合的参数时引发。 |
UnicodeError | ValueError的子类。Unicode编码或解码错误时引发。 |
UnicodeEncodeError | UnicodeError的子类。Unicode编码错误。 |
UnicodeDecodeError | UnicodeError的子类。Unicode解码错误。 |
UnicodeTranslateError | UnicodeError的子类。在转换过程中产生的与Unicode相关的错误。 |
以下异常继承自:BaseException -> Exception -> ArithmeticError | |
FloatingPointError | 目前未被使用。 |
OverflowError | 当一个运算结果大到无法表示时将被引发。 |
ZeroDivisionError | 当除法或取余运算的第二个参数为零时将被引发。 |
以下异常继承自:BaseException -> Exception -> LookupError | |
IndexError | 序列的下标超出范围时引发。 |
KeyError | 映射(字典)中未找到键时引发。 |
以下警告继承自:BaseException -> Exception -> Warning | |
DeprecationWarning | 与已弃用特性相关警告的基类。 |
PendingDeprecationWarning | 对于已过时并预计在未来弃用,但目前尚未弃用的特性相关警告的基类。 |
RuntimeWarning | 与模糊的运行时行为相关的警告的基类。 |
SyntaxWarning | 与模糊的语法相关的警告的基类。 |
UserWarning | 用户代码所产生警告的基类。 |
FutureWarning | 与已弃用特性相关警告的基类。 |
ImportWarning | 与在模块导入中可能的错误相关的警告的基类。 |
UnicodeWarning | 与 Unicode 相关的警告的基类。 |
BytesWarning | 与 bytes 和 bytearray 相关的警告的基类。 |
ResourceWarning | 与资源使用相关的警告的基类。 会被默认的警告过滤器忽略。 |
(2)自定义异常
可以通过继承Exceptions类而定义自己的异常。定义完之后,可以使用raise语句引发这个新的异常,如下例所示:
class NetworkError(Exception): pass raise NetworkError('Cannot find host')
下例显示了如何在自定义异常中带有多个异常值:
class NetworkError(Exception): def __init__(self, errno, msg): self.args = (errno, msg) # 赋值给args是必须的,否则用户无法看到自定义异常的任何细节提示信息 self.errno = errno self.msg = msg raise NetworkError(1, 'No response')
使用继承机制将异常组成一个层次结构:
class NetworkError(Exception): pass class HostnameError(NetworkError): pass class TimeoutError(NetworkError): pass # 以下为引发并捕捉自定义异常 try: raise TimeoutError('Time out') except NetworkError as e: if type(e) is TimeoutError: pass
(3)主动引发异常
● Assert(断言)
断言的基本目的是:与其让程序在在将来不知某个时候崩溃,不如在程序中不符合某个预判条件时,主动让程序崩溃。 当断言条件不满足时,会引发AssertionError异常。assert的格式为:
assert condition [,msg]
其中,condition是一个表达式,其值若为False时,assert语句就会引发AssertionError异常。 可以在assert语句后添加字符串msg,用来在发生异常时,提示预先设置的字符串信息。
使用举例:
age = -1 assert 0 < age < 100 'The age is fantastic.' # 执行本句时,会引发AssertionError异常,并会提示:The age is fantastic.
assert语句常和 if __debug__语句一起使用。在调试模式中,只读变量__debug__的值为True,可以随意地在代码中加入assert和调试检查。 在最优模式中(通过-O指定),__debug__为False,将省略所有这些额外的检查。
● raise
使用raise语句可主动引发异常,raise语句的格式为:
raise Exception([value])
如果raise语句没有带任何参数,将会再次引发最近一次生成的异常。
raise使用举例:
raise KeyError('abc') # 引发KeyError异常,并提示字符串 'abc'
(4)捕捉异常
通常,使用捕捉异常的代码结构要比使用多个if-else语句判断来得更清晰,而且执行效率也几乎没什么损失,故应该在程序中尽可能使用try/except语句来查错。 一般except语句捕捉并处理完异常后,程序将继续执行跟在最后一个except代码块中的语句,程序并不会返回发生异常时的位置。
如果异常在函数内引发而未被处理,它就会向上传递到函数调用的地方,如果在那里也没有被处理,就会继续向上传递,直到主程序(全局作用域)。 如果主程序里也没有处理,程序会带着堆栈跟踪停止。
如果需要,也可以把未捕捉的异常传递给用户自定义的函数sys.excepthook()进行处理。
● try-except结构:
捕捉单个异常:
try: x = 2/0 except ZeroDivisionError: print 'The divisor is zero'
捕捉多个异常:
# 方法一: try: x = 2/'a' except ZeroDivisionError: pass except TypeError: pass # 方法二: try: x = 2/'a' except (ZeroDivisionError, TypeError, NameError): print('oops!')
捕捉所有异常:
try: x = 2/0 except Exception: # 这里使用基类Exception可捕捉除了“键盘中断”和“程序退出”的所有异常 pass
捕捉异常并访问异常对象:
try: x = 2/0 except Exception as e: print(e)
这里生成的异常实例e具有一些标准属性,列举如下:
e.args:引发异常时提供的参数元组,一般包含有描述该错误的字符串。
e.__cause__:使用显式关联异常时的前一个异常。
e.__context__:使用隐式关联异常时的前一个异常。
e.__traceback__:与异常相关的跟踪对象。
● try-except-else结构
当try中的语句没有发生异常请跨下,运行else语句块中内容。
示例:
try: f = open('foo', 'r') except IOError as e: print(e) else: data = f.read() f.close()
● try-except-finally结构
finally语句块用于无论try是否有异常,都要运行的代码。如果没有引发异常,finally子句中的代码将在try的代码块执行完后立即执行。 如果捕捉到了异常,则finally中的内容先运行,然后再运行except语句块中的内容。
示例:
try: f = open('foo', 'r') data = f.read() except IOError as e: print(e) finally: f.close() # 无论前面发生什么,都会关闭文件
● try-except-else-finally结构
else和finally也可以组合在一起使用。
示例:
try: f = open('foo', 'r') except IOError as e: print(e) else: data = f.read() finally: f.close()
(5)error模块
errno模块为各种操作系统调用返回的整数错误代码定义了符号名称,这些整数代码通常可在OSError(别名IOError)异常的errno属性中找到。
os.strerror()函数可以将整数的错误代码转换为字符串信息。
errno模块的errorcode字典,记录了当前操作系统支持的错误代码和POSIX符号名称的对应关系,下表列举了部分常用的错误代码。
OSError中的错误代码信息:
try: f = open(r'xxxxxx', 'r') except Exception as e: print(e.errno) print(e.args) # 运行结果为: 2 (2, 'No such file or directory')
os.strerror()使用示例:
import os import errno print(os.strerror(2)) # 运行结果为错误代码2的字符串含义:No such file or directory'
● 常见POSIX错误代码
错误代码 | 名称 | 描述 |
---|---|---|
1 | EPERM | 操作未得到许可 |
2 | ENOENT | 文件或目录不存在 |
3 | ESRCH | 进程不存在 |
4 | EINTR | 系统调用被中断 |
5 | EIO | I/O错误 |
6 | ENXIO | 设备或地址不存在 |
7 | E2BIG | 参数列表过长 |
8 | ENOEXEC | 访问被拒绝 |
9 | EBADF | 错误的文件编号 |
10 | ECHILD | 无子进程 |
11 | EAGAIN | 再试 |
12 | ENOMEM | 内存不足 |
13 | EACCESS | 访问被拒绝 |
14 | EFAULT | 错误的地址 |
15 | ENOTBLK | 需要块设备 |
16 | EBUSY | 设备或资源方面 |
17 | EEXIST | 文件存在 |
18 | EXDEV | 跨设备链接 |
19 | ENODEV | 没有这个设备 |
20 | ENOTDIR | 不是一个目录 |
21 | EISDIR | 是一个目录 |
22 | EINVAL | 无效参数 |
23 | ENFILE | 文件表溢出 |
24 | EMFILE | 打开文件过多 |
25 | ENOTTY | 不是一个终端 |
26 | ETXTBSY | 文本文件忙 |
27 | EFBIG | 文件过大 |
28 | ENOSPC | 设备上无剩余空间 |
29 | ESPIPE | 非法寻址 |
30 | EROFS | 只读文件系统 |
31 | EMLINK | 链接过多 |
32 | EPIPE | 管道已损坏 |
33 | EDOM | 数学参数在函数作用域之外 |
34 | ERANGE | 无法表示的数学结果 |
35 | EDEADLOCK | 文件锁定死锁错误 |
36 | ENAMETOOLONG | 文件名过长 |
37 | ENOLCK | 无可用记录锁定 |
38 | ENOSYS | 函数无法实现 |
39 | ENOTEMPTY | 目录不为空 |
40 | ELOOP | 遇到过多的符号链接 |
84 | EILSEQ | 非法的字节序列 |
85 | ERESTART | 中断系统调用需重启 |
86 | ESTRPIPE | 流管道错误 |
87 | EUSERS | 用户过多 |
88 | ENOTSOCK | 非套接字上的套接字操作 |
89 | EDESTADDRREQ | 需要目的地址 |
90 | EMSGSIZE | 消息过长 |
91 | EPROTOTYPE | 套接字的协议类型错误 |
92 | ENOPROTOOPT | 协议不可用 |
93 | EPROTONOSUPPORT | 不支持协议 |
94 | ESOCKTNOSUPPORT | 套接字类型不受支持 |
95 | ENOTSUP | 操作被远端支持 |
96 | EPFNOSUPPORT | 不支持协议族 |
97 | EAFNOSUPPORT | 协议不支持地址族 |
98 | EADDRINUSE | 地址已使用 |
99 | EADDRNOTAVAIL | 无法分配请求的地址 |
100 | ENETDOWN | 网络已关闭 |
101 | ENETUNREACH | 网络不可到达 |
102 | ENETRESET | 网络由于重置中断连接 |
103 | ECONNABORTED | 软件导致连接中断 |
104 | ECONNRESET | 对等端已将连接重置 |
105 | ENOBUFS | 无可用缓存空间 |
106 | EISCONN | 传输端点已经连接 |
107 | ENOTCONN | 传输端点未连接 |
108 | ESHUTDOWN | 无法在传输端点关闭后发送 |
109 | ETOOMANYREFS | 引用过多:无法连接 |
110 | ETIMEDOUT | 连接超时 |
111 | ECONNREFUSED | 连接被拒绝 |
112 | EHOSTDOWN | 主机已关闭 |
113 | EHOSTUNREACH | 无路由通向主机 |
114 | EALREADY | 操作已经在进行中 |
115 | EINPROGRESS | 操作正在进行 |
116 | ESTALE | 失效的NFS文件句柄 |
125 | ECANCELED | 操作取消 |
126 | ENOKEY | 无此键 |
127 | EKEYEXPIRED | 键过期 |
128 | EKEYREVOKED | 键被撤回 |
129 | EKEYREJECTED | 键被服务拒绝 |
130 | EOWNERDEAD | 拥有者已不存在 |
131 | ENOTRECOVERABLE | 状态不可恢复 |
132 | ERFKILL | 操作由于RF-KILL无法进行 |
(6)with语句
with语句支持在“上下文管理器”对象的控制下,执行一系列语句。常用于管理各种系统资源(如文件、锁、连接等)。 当程序中发生异常,而导致脱离正常的释放资源语句时,只要用了with,就可以保证在离开with语句块时自动释放这些资源。下面是2个简单的例子:
自动关闭文件对象
with open('a.txt', 'w') as f: f.write('xyz\n') ...... f.write('done\n') # 当程序离开with语句块时,with语句会自动关闭已打开的文件
自动释放锁
import threading lock = threading.Lock() with lock: ...... # 当程序离开with语句块时,with语句会自动释放这个锁
● with语句的一般语法:
with obj [as var] statements
obj对象需要实现__enter__()方法和__exit__()方法来支持with语句。当执行with obj语句时,会自动调用obj.__enter__()方法, 该方法的返回值将被放入指定变量var中。
当程序离开with语句块时,会自动调用obj.__exit__()方法,其入参形式为:__exit__(type, value, traceback), 三个入参分别为:当前异常的类型、值、跟踪信息。__exit__()方法返回True或False(表示被引发的异常是否得到了处理)
以下为一个用户自定义类支持with的例子,这个类支持对已有列表进行一系列修改, 但这些修改只有在没有发生异常时才会生效,否则原始列表将保持不变。
class ListTransaction(object): def __init__(self, thelist): self.thelist = thelist def __enter__(self): self.workingcopy = list(self.thelist) return self.workingcopy def __exit__(self, type, value, tb): if type is None: self.thelist[:] = self.workingcopy return # 使用with语句: items = [1,2,3] try: with ListTransaction(items) as working: working.append(4) working.append(5) raise RuntimeError() except RuntimeError: pass print(items) # 由于在离开with语句块时发生了异常,因此__exit__()的入参type不为None,最终结果为:[1,2,3]
● 使用 contextlib 模块
使用 contextlib 模块可以更加方便地编写上例那样的支持 With 上下文管理语句的类。 它提供了一个 @contextmanager 装饰器,用户使用它装饰某个“函数”,达到上面类似的效果。 下面是一个使用 @contextmanager 装饰器的例子:
from contextlib import contextmanager @contextmanager def ListTransaction(thelist): workingcopy = list(thelist) yield workingcopy # 仅在没有出现错误时,才会修改原始列表 thelist[:] = workingcopy
上面的程序中,装饰器将传递给 yield 的值用作了 __enter__() 方法的返回值(即提供给外部的 With 语句)。在退出 With (即调用 __exit__() 方法)时,执行将在 yield 语句后恢复 (即把原始的 thelist 列表的值改掉)。
如果 With 上下文中引发了异常,它将以异常形式出现在生成器函数中。