目录
简介
如果你编写过C++程序,相信你应该有过被“烫烫烫”、“屯屯屯”支配的恐惧。作为程序员,我们每天就是在写代码bug,然后改bug。通过本文你可以看懂自己程序发生的错误或异常,如何自己定义异常,如何处理/避免异常。你需要注意是什么引起了某个异常,在编程时进行避免或处理。Python官方文档将语法错误单独拿了出来,称为错误,其它的称为异常。它可能考虑的是语法错误是在运行前,而其它是在运行时,本文仍把语法错误作为异常,按照类层次结构进行展开。如果要我分类的话,我会分为三类:
- 运行前——编译出错,语法错误
- 运行时——运行出错,逻辑错误
- 运行后——结果出错,逻辑错误
比如,有时刚编程的小白可能会写 if x=0 : ...,其实他的本意是判断x是否等于0,而不是赋值,由于少敲了一个等于号,导致程序结果出错,也有可能转为运行时出错。对于逻辑错误,就涉及到代码规范、代码优化以及设计模式等内容,代码规范本专栏有,其他的还没写。
异常-Exception
即使语句或表达式在语法上是正确的,但在尝试执行时,它仍可能会引发错误。 在执行时检测到的错误被称为异常,异常不一定会导致严重后果。
异常分为两个阶段,第一个阶段是发现错误后引发异常,这个动作也可以成为触发、抛出等。第二个阶段就是异常处理。
内置异常
内置异常类层级结构:
BaseException +-- SystemExit +-- KeyboardInterrupt +-- GeneratorExit +-- Exception +-- StopIteration +-- StopAsyncIteration +-- ArithmeticError | +-- FloatingPointError | +-- OverflowError | +-- ZeroDivisionError +-- AssertionError +-- AttributeError +-- BufferError +-- EOFError +-- ImportError | +-- ModuleNotFoundError +-- LookupError | +-- IndexError | +-- KeyError +-- MemoryError +-- NameError | +-- UnboundLocalError +-- OSError | +-- BlockingIOError | +-- ChildProcessError | +-- ConnectionError | | +-- BrokenPipeError | | +-- ConnectionAbortedError | | +-- ConnectionRefusedError | | +-- ConnectionResetError | +-- FileExistsError | +-- FileNotFoundError | +-- InterruptedError | +-- IsADirectoryError | +-- NotADirectoryError | +-- PermissionError | +-- ProcessLookupError | +-- TimeoutError +-- ReferenceError +-- RuntimeError | +-- NotImplementedError | +-- RecursionError +-- SyntaxError | +-- IndentationError | +-- TabError +-- SystemError +-- TypeError +-- ValueError | +-- UnicodeError | +-- UnicodeDecodeError | +-- UnicodeEncodeError | +-- UnicodeTranslateError +-- Warning +-- DeprecationWarning +-- PendingDeprecationWarning +-- RuntimeWarning +-- SyntaxWarning +-- UserWarning +-- FutureWarning +-- ImportWarning +-- UnicodeWarning +-- BytesWarning +-- ResourceWarning
上面是官方给的,我们可以自己写一个函数来展示类层级结构
def classtree(cls, indent=0): print('+'+'-' * (indent-1), cls.__name__) for subcls in cls.__subclasses__(): classtree(subcls, indent + 3) classtree(BaseException)
基类
名称 | 描述 |
---|---|
BaseException | 所有内置异常的基类。 它不应该被用户自定义类直接继承。 |
Exception | 所有内置的非系统退出类异常都派生自此类。 所有用户自定义异常也应当派生自此类。 |
ArithmeticError | 此基类用于派生针对各种算术类错误而引发的内置异常。 |
BufferError | 当与缓冲区相关的操作无法执行时将被引发 |
LookupError | 此基类用于派生当映射或序列所使用的键或索引无效时引发的异常 |
具体异常
SystemExit
此异常由sys.exit()函数引发。 它继承自BaseException而不是Exception
以确保不会被处理Exception
的代码意外捕获。 这允许此异常正确地向上传播并导致解释器退出。 如果它未被处理,则Python 解释器就将退出;不会打印任何栈回溯信息。
import sys print('lady') exit(0) print('killer')
结果:
lady
KeyboardInterrupt
当用户按下中断键 (通常为 Control-C 或 Delete) 时将被引发。
while True: print()
运行后按Ctrl+C,结果:
Traceback (most recent call last):
File "learnException.py", line 17, in <module>
print()
KeyboardInterrupt
GeneratorExit
它直接继承自BaseException
而不是Exception,因为从技术上来说它并不是一个错误。
---------------------------接下来是可以被捕获的-------------------------------
StopIteration
由内置函数next()和iterator的__next__()方法所引发,用来表示该迭代器不能产生下一项。
def myGenerator(): try: yield 1 yield 2 except GeneratorExit: print('GeneratorExit happened') gen = myGenerator() next(gen) next(gen) next(gen)
结果:
Traceback (most recent call last):
File "learnException.py", line 31, in <module>
next(gen)
StopIteration
StopAsyncIteration
必须由一个 asynchronous iterator 对象的 __anext__() 方法来引发以停止迭代操作。
没用过,有时间再说。
FloatingPointError
官网显示,Not currently used.不过,我记得在谋篇文章中提到numpy中有的,经过查找,找到了。

import numpy numpy.seterr(invalid='raise') numpy.sqrt(-1)
结果:
Traceback (most recent call last):
File "learnException.py", line 38, in <module>
numpy.sqrt(-1)
FloatingPointError: invalid value encountered in sqrt
OverflowError
当算术运算的结果大到无法表示时将被引发。 这对整数来说不可能发生(宁可引发MemorryError也不会放弃尝试)。 但是出于历史原因,有时也会在整数超出要求范围的情况下引发 OverflowError。 因为在 C 中缺少对浮点异常处理的标准化,大多数浮点运算都不会做检查。
print(float('9' * (2 ** 99)))
结果:
Traceback (most recent call last):
File "learnException.py", line 37, in <module>
print(float('9' * (2 ** 99)))
OverflowError: cannot fit 'int' into an index-sized integer
ZeroDivisionError
当除法或取余运算的第二个参数为零时将被引发。
print(2/0)
结果:
当除法或取余运算的第二个参数为零时将被引发。
AssertionError
当assert语句失败时将被引发。讲到assert语句时再说。
AttributeError
当属性引用或赋值失败时将被引发。
i = int(8) i.abc = 9
结果:
Traceback (most recent call last):
File "learnException.py", line 43, in <module>
i.abc = 9
AttributeError: 'int' object has no attribute 'abc'
BufferError
当与缓冲区相关的操作无法执行时将被引发。
没用过,有时间再说。
EOFError
当input()
函数未读取任何数据即达到文件结束条件 (EOF) 时将被引发。Linux上为Ctrl+d,Windows上为Ctrl+Z+Enter。
s = input('please input something:')
博主的电脑是Windows,所以按的Ctrl+Z+Enter,结果:
please input something:^Z
Traceback (most recent call last):
File "learnException.py", line 45, in <module>
s = input('please input something:')
EOFError
ModuleNotFoundError
当import
语句尝试加载模块遇到麻烦时将被引发。 并且当 from ... impor...t
中的 "from list" 存在无法找到的名称时也会被引发。
import abcd
结果:
Traceback (most recent call last):
File "learnException.py", line 47, in <module>
import abcd
ModuleNotFoundError: No module named 'abcd'
IndexError
当序列索引超出范围时将被引发。
lst = [1, 2, 3] print(lst[4])
结果:
Traceback (most recent call last):
File "learnException.py", line 50, in <module>
print(lst[4])
IndexError: list index out of range
KeyError
当在现有键集合中找不到指定的映射(字典)键时将被引发。
d = {"k": "y"} print(d["k"], d[1])
结果:
Traceback (most recent call last):
File "learnException.py", line 53, in <module>
print(d["k"], d[1])
KeyError: 1
MemoryError
当一个操作耗尽内存但情况仍可进行挽救(通过删除一些对象)时将被引发。 关联的值是一个字符串,指明是哪种(内部)操作耗尽了内存。 请注意由于底层的内存管理架构(C 的 malloc()
函数),解释器也许并不总是能够从这种情况下完全恢复;但它毕竟可以引发一个异常,这样就能打印出栈回溯信息,以便找出导致问题的失控程序。
还没碰到过,有时间再说。听做大数据或爬虫之类的朋友说,读取一个大文件之类的可能会造成这个异常。
我从stackoverflow上找了一个例子。
大概的意思是变量u是一个很大的矩阵,提问题的人使用了u = u + alpha * p,内存会保留u,开辟空间存储u + alpha * p,将u指向u + alpha * p,然后释放原来u的内存空间,如果不考虑alpha和p的内存空间,执行该语句时内存空间至少分配了两个u的大小,解决问题的人使用了非简单赋值操作符p *= alpha,u += p,减少了内存空间的使用。关于操作符,可查看:Python-操作符总结(逻辑、位、算术、比较、赋值操作符及操作符优先级)
我们验证一下:
>>> lst = [1]
>>> id(lst)
1821366910152
>>> lst = lst + [2]
>>> id(lst)
1821366870280
>>> lst += [2]
>>> id(lst)
1821366870280
确实使用 += 时,id不会改变。
NameError
当某个局部或全局名称未找到时将被引发。此异常仅用于非限定名称。 关联的值是一条错误信息,其中包含未找到的名称。
print(i)
结果:
Traceback (most recent call last):
File "learnException.py", line 55, in <module>
print(i)
NameError: name 'i' is not defined
UnboundLocalError
当在函数或方法中对某个局部变量进行引用,但该变量并未绑定任何值时将被引发。 此异常是NameError的一个子类。
def Hi(): hi += 1 print(hi) Hi()
结果:
Traceback (most recent call last):
File "learnException.py", line 63, in <module>
Hi()
File "learnException.py", line 59, in Hi
hi += 1
UnboundLocalError: local variable 'hi' referenced before assignment
BlockingIOError
当一个操作会被某个设置为非阻塞操作的对象(例如套接字)所阻塞时将被引发。 对应于 errno
EAGAIN
, EALREADY
, EWOULDBLOCK
和 EINPROGRESS
。
除了OSError已有的属性,BlockingIOError
还有一个额外属性:
characters_written
一个整数,表示在被阻塞前已写入到流的字符数。 当使用来自io模块的带缓冲 I/O 类时此属性可用。
还没有碰到过,继续stackoverflow。看了一页,总结一下:
- 发送缓冲区已满却想继续发送到缓冲区
- 接收缓冲区已空却想继续从缓冲区读取
也就是说你使用了非阻塞,那么就不会挂起去等待临界资源。
ChildProcessError
当一个子进程上的操作失败时将被引发。 对应于 errno
ECHILD
。
他exec里面的命令写的有问题,也就是说python调用第三方的exe等如果出错可能会显示这个错误,遇到了再更新。
BrokenPipeError
当试图写入另一端已被关闭的管道,或是试图写入已关闭写入的套接字时将被引发。 对应于 errno
EPIPE
和 ESHUTDOWN
。
从stackoverflow找了一个,也不是很合适,这个错误貌似比较玄学...
我使用了他的代码,但是并没有出现他的异常,他虽然关闭了,但是之后没有使用in_1变量。我修改了一下代码,文件关闭再读取,但是得到的是 ValueError: I/O operation on closed file.
ConnectionAbortedError
当连接尝试被对端中止时将被引发。 对应于 errno
ECONNABORTED
。
例如,TCP类型的socket,socket.connect()被对方终止,就会出现这个异常。
ConnectionRefusedError
当连接尝试被对端拒绝时将被引发。 对应于 errno
ECONNREFUSED
。
import socket IP = '127.0.0.1' PORT = 80 # 建立Tcp一个客户端 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect((IP, PORT)) hex_data = f"01000008" DATA = bytes.fromhex(hex_data) client.sendall(DATA) client.close()
结果:
Traceback (most recent call last):
File "learnException.py", line 71, in <module>
client.connect((IP, PORT))
ConnectionRefusedError: [WinError 10061] 由于目标计算机积极拒绝,无法连接。
当然,你的80端口不能开着并接收信息。
ConnectionResetError
当连接被对端重置时将被引发。 对应于 errno
ECONNRESET
。
还没碰到过,有时间再说。
FileExistsError
当试图创建一个已存在的文件或目录时将被引发。 对应于 errno
EEXIST
。
import os os.mkdir('dist')
结果:
Traceback (most recent call last):
File "learnException.py", line 79, in <module>
os.mkdir('dist')
FileExistsError: [WinError 183] 当文件已存在时,无法创建该文件。: 'dist'
注意:dist目录在运行前已存在
FileNotFoundError
当所请求的文件或目录不存在时将被引发。 对应于 errno
ENOENT
。
with open('Nothing', 'r') as f: f.read()
结果:
Traceback (most recent call last):
File "learnException.py", line 81, in <module>
with open('Nothing', 'r') as f:
FileNotFoundError: [Errno 2] No such file or directory: 'Nothing'
注意:当前目录下并没有Nothing这个文件
InterruptedError
当系统调用被输入信号中断时将被引发。 对应于 errno
EINTR。
还没碰到过,有时间再说。
IsADirectoryError
当请求对一个目录执行文件操作 (例如 os.remove()) 将被引发。 对应于 errno
EISDIR
。
还没碰到过,有时间再说。
NotADirectoryError
当请求对一个非目录对象执行目录操作 (例如os.listdir()
) 时将被引发。 对应于 errno
ENOTDIR
。
import os os.listdir('citys.csv')
结果:
Traceback (most recent call last):
File "learnException.py", line 88, in <module>
os.listdir('citys.csv')
NotADirectoryError: [WinError 267] 目录名称无效。: 'citys.csv'
PermissionError
当在没有足够操作权限的情况下试图执行某个操作时将被引发 —— 例如缺少文件系统权限。 对应于 errno
EACCES
和 EPERM
。
使用Python-记一次U盘中病毒及文件找回中的脚本,对插入的U盘进行权限限制,之后拔插U盘。
import os os.mkdir(r'G:\adirectory')
结果:
Traceback (most recent call last):
File "learnException.py", line 89, in <module>
os.mkdir(r'G:\adirectory')
PermissionError: [WinError 5] 拒绝访问。: 'G:\\adirectory'
ProcessLookupError
当给定的进程不存在时将被引发。 对应于 errno
ESRCH
。
import os os.kill(12345, 0)
结果:
Traceback (most recent call last):
File "learnException.py", line 89, in <module>
os.kill(12345, 0)
ProcessLookupError: [Errno 3] No such process
注意:博主在Linux测试是这样的,在Windows下是:
Traceback (most recent call last):
File "learnException.py", line 89, in <module>
os.kill(12345, 0)
OSError: [WinError 87] 参数错误。
TimeoutError
当一个系统函数发生系统级超时的情况下将被引发。 对应于 errno
ETIMEDOUT
。
还没碰到过,有时间再说。
ReferenceError
ReferenceError
此异常将在使用weakref.proxy()函数所创建的弱引用来访问该引用的某个已被作为垃圾回收的属性时被引发。
还没用过这个模块,有时间再说。
RuntimeError
RuntimeError
当检测到一个不归属于任何其他类别的错误时将被引发。
NotImplementedError
在用户自定义的基类中,抽象方法应当在其要求所派生类重载该方法,或是在其要求所开发的类提示具体实现尚待添加时引发此异常。
from abc import abstractmethod class People: @abstractmethod def say(self): raise NotImplementedError('function say not implemented!') class Man(People): pass man = Man() man.say()
结果:
Traceback (most recent call last):
File "learnException.py", line 109, in <module>
man.say()
File "learnException.py", line 101, in say
raise NotImplementedError('function say not implemented!')
NotImplementedError: function say not implemented!
经测试,只有抽象方法未实现,主动抛出异常,且该方法被调用时才会出现该异常。
RecursionError
它会在解释器检测发现超过最大递归深度(sys.getrecursionlimit())时被引发。
print(sys.getrecursionlimit()) def hello(i): if i == 1001: return 1 hello(i + 1) hello(0)
结果:
1000
Traceback (most recent call last):
File "learnException.py", line 120, in <module>
hello(0)
File "learnException.py", line 117, in hello
hello(i + 1)
File "learnException.py", line 117, in hello
hello(i + 1)
File "learnException.py", line 117, in hello
hello(i + 1)
[Previous line repeated 995 more times]
File "learnException.py", line 115, in hello
if i == 1001:
RecursionError: maximum recursion depth exceeded in comparison
博主的最大递归深度是1000。
SyntaxError
语法错误又称解析错误,语法错误导致不能被解释器解释或编译器无法编译,这些错误必须在程序执行前纠正。
>>> while True
File "<stdin>", line 1
while True
^
SyntaxError: invalid syntax
SystemError
当解释器发现内部错误,但情况看起来尚未严重到要放弃所有希望时将被引发。
还没碰到过,有时间再说。
TypeError
当一个操作或函数被应用于类型不适当的对象时将被引发。
i = 3 j = '4' print(i+j)
结果:
Traceback (most recent call last):
File "learnException.py", line 124, in <module>
print(i+j)
TypeError: unsupported operand type(s) for +: 'int' and 'str'
ValueError
当操作或函数接收到具有正确类型但值不适合的参数,并且情况不能用更精确的异常例如IndexError来描述时将被引发。
int('345abc')
结果:
Traceback (most recent call last):
File "learnException.py", line 126, in <module>
int('345abc')
ValueError: invalid literal for int() with base 10: '345abc'
提示:class int
(x, base=10),如果 x 不是数字,或者有 base 参数,x 必须是字符串、bytes、表示进制为 base 的整数字面值的bytearray
实例。
我传参确实为字符串,但是字符串"345abc”并不能表示为10进制的数字
UnicodeError
当发生与 Unicode 相关的编码或解码错误时将被引发。
with open('C:\Users\whatever.txt', 'r') as f: f.read()
结果:
File "learnException.py", line 128
with open('C:\Users\whatever.txt', 'r') as f:
^
SyntaxError: (unicode error) 'unicodeescape' codec can't decode bytes in position 2-3: truncated \UXXXXXXXX escape
python没有将Unicode Error归到ValueError,这和官方文档给的类层次结构图不一样呀,可能是Python维护人员忘记了,博主使用的是3.7版本,最近已经更新到了3.9,使用最新版本的小伙伴们可以看看是否改了。
UnicodeDecodeError
当在解码过程中发生与 Unicode 相关的错误时将被引发。
with open('file.txt', 'r') as f: f.read()
结果:
Traceback (most recent call last):
File "learnException.py", line 132, in <module>
f.read()
UnicodeDecodeError: 'gbk' codec can't decode byte 0x80 in position 18: illegal multibyte sequence
file.txt内容为:
这不是unicode呀
UnicodeEncodeError
当在编码过程中发生与 Unicode 相关的错误时将被引发。
记得把Windows下的python文件放到Ubuntu运行时出过这个错误,没有复现,遇到再说吧。
UnicodeTranslateError
在转写过程中发生与 Unicode 相关的错误时将被引发。
还没碰到过,有时间再说。
抛出异常与断言
使用关键字raise,强制抛出异常。在NotImplementedError中有使用,不再展示。
assert——断言,assert 之后的表达式为False时,抛出AssertError异常。在测试时经常使用,比如unittest框架
语法:
assert expression [, arguments]
等价于:
if not expression: raise AssertionError(arguments)
import sys assert 'linux' in sys.platform, "该代码只能在 Linux 下执行"
结果:
Traceback (most recent call last):
File "learnException.py", line 149, in <module>
assert 'linux' in sys.platform, "该代码只能在 Linux 下执行"
AssertionError: 该代码只能在 Linux 下执行
注:博主的sys.platform是win32。
def safe_int(x): assert isinstance(x, str) return int(x) safe_int([1, 2, 3])
结果:
Traceback (most recent call last):
File "learnException.py", line 147, in <module>
safe_int([1, 2, 3])
File "learnException.py", line 143, in safe_int
assert isinstance(x, str)
AssertionError
自定义异常
继承基类Exception,或者其他的异常类。我们定义一个空文件异常,认为用户想要读的文件为空时是异常的,并写一个函数包装open,空文件时抛出异常。
class EmptyFileError(OSError): pass def read_test(path): with open(path, 'r', encoding='utf-8') as f: if f.read() == '': raise EmptyFileError(f'{path} is an empty file!') return f.read() print(read_test('file.txt'))
结果:
Traceback (most recent call last):
File "learnException.py", line 165, in <module>
read_test('file.txt')
File "learnException.py", line 160, in read_test
raise EmptyFileError(f'{path} is an empty file!')
__main__.EmptyFileError: file.txt is an empty file!
异常捕获与处理
如果不进行异常捕获与处理,对于用户来说,看见“Traceback (most recent call last)...”这样的提示有一种很不好的体验。
当然,如果你写的代码是给与你合作的另一位程序员用的,那么你可以抛出异常,甚至不做处理,而在接口文档中详细地告诉他你的函数或类等该如何使用。
语法:
try
:
...
except ExceptionName [as AnotherName]:
...
finally:
...
一个try可以对应多个except,except和finally可选,但不能一个也没有。先运行try后面的语句块,如果捕获到异常,运行对应except后的语句块。不管有没有捕获到异常,均运行finally后的语句块。
def division(a, b): try: return int(a) / int(b) except ValueError as e: print('ValueError', e) except TypeError as e: print('TypeError', e) except ZeroDivisionError as e: print('ZeroDivisionError', e) finally: print('finish...') division('3a', '0') division('3', []) division('3', 0) division(4, 2)
结果:
ValueError invalid literal for int() with base 10: '3a'
finish...
TypeError int() argument must be a string, a bytes-like object or a number, not 'list'
finish...
ZeroDivisionError division by zero
finish...
finish...
在ValueError一节,我们提到int函数的x参数可以为字符串,但是如果字符串不能转为对应base的数字时就会抛出ValueError异常。我们包装一下int给用户使用,不抛出ValueError,如果传入的字符串含有非数字,我们就停止之后的转换,并提示用户首个非数字的字符。
def safe_int(x): try: assert isinstance(x, str) except AssertionError: print('不是说好仅传字符串的吗???') x = str(x) try: return int(x) except ValueError: s = [] for c in x: if c.isdecimal(): s.append(c) else: print('convert to int until ' + c) break return int(''.join(s)) if len(s) > 0 else 0 finally: print('finish...') print(safe_int('345abc'))
结果:
convert to int until a
finish...
345
博主编程量不多,有的异常也没有遇到过,如果您遇到过,能够复现,欢迎在评论区评论,我会及时更新,让这篇文章能够作为类似工具书/手册来帮助大家。
更多python相关内容:【python总结】python学习框架梳理
本人b站账号:lady_killer9
有问题请下方评论,转载请注明出处,并附有原文链接,谢谢!如有侵权,请及时联系。