异常 Exception
-
错误 Error
- 逻辑错误 : 算法写错, 例如加法写成了减法
- 笔误 : 例如变量名写错, 语法错误
- 函数或者类使用错误, 其实这也属于逻辑错误
- 错误是可以避免的
-
异常 Exception
-
本意指意外情况
-
这有个前提, 没有上面说的错误, 也就是说程序写的没有问题, 但是在某些情况下会出现一些意外, 导致程序无法正常的执行下去
-
open 函数操作一个文件, 文件不存在或者创建一个文件时已经存在, 或者访问一个网络文件突然断网, 这就是异常是意外情况
-
异常不可能避免
-
错误和异常
- 在高级语言编程中, 一般都是有错误和异常的概念, 异常是可以捕获并处理的, 但是错误是不能被捕获的
- 一个健壮的程序尽可能避免错误, 尽可能的捕获、处理各种异常
产生异常
产生
- raise 语句显示的抛出异常
- Python 解释器自己检测到并引发它
def foo():
print('before')
print(1/0)
print('after')
foo() # 除零异常
def bar():
print('before')
raise Exception('my exception')
print('after') # raise主动抛出异常
bar()
程序会在异常抛出的地方中断执行, 如果不捕获, 就会提前结束程序(其实是终止当前线程的执行)
异常的捕获
try:
待捕获异常的代码块
except [异常类型]:
异常的处理代码块
def foo():
try:
print('before')
c = 1/0
print('after')
except:
print('error')
print('catch the excepttion')
foo()
print('============ end =============')
运行结果
before
error
catch the excepttion
============ end =============
上例执行到c = 1/0 时产生异常并抛出, 由于使用了try…except 语句块则捕捉到了这个异常, 异常生成位置之后语句将不再执行, 转而执行对应的except 部分的语句, 最后执行try…except语句块之外的语句
捕获指定类型的异常
def foo():
try:
print('before')
c = 1/0
print('after')
except ArithmeticError: # 指定捕获的类型
print('error')
print('catch the excepttion')
foo()
print('============ end =============')
异常类及继承层次
Python 异常的继承
- BaseException
- SystemExit
- KeyboardInterrupt
- GeneratorExit
- Exception
- RuntimeError
- RecursionError
- MemoryError
- NameError
- StopIteration
- StopAsyncIteration
- ArithmeticError
- FloatingPointError
- OverflowError
- ZeroDivisionError
- LookupError
- IndexError
- KeyError
- SyntaxError
- OSError
- BlockingIOError
- ChildProcessError
- ConnectionError
- BrokenPipeError
- ConnectionAbortedError
- ConnectionRefusedError
- ConnectionResetError
- FileExistsError
- FileNotFoundError
- InterruptedError
- IsADirectoryError
- NotADirectoryError
- PermissionError
- ProcessLookupError
- TimeoutError
- RuntimeError
BaseException 及子类
BaseException
- 所有内见异常类的基类是Baseexception
SystemExit
- 通过例子来看
import sys
print('before')
sys.exit(1)
print('SysExit')
print('outer')
运行结果
before
Process finished with exit code 1
import sys
try:
sys.exit(1)
except SystemExit:
print('SysExit')
print('outer')
运行结果
SysExit
outer
Process finished with exit code 0
如果except 语句捕获了该异常, 则继续向后面执行, 如果没有捕获住该异常SystemExit, 解释器直接退出程序. 注意捕获前后程序退出状态码的变化
KeyboardInterrupt
对应的捕获用户中断行为Ctrl + C
try:
import time
while True:
time.sleep(1)
print('~~~')
except KeyboardInterrupt:
print('Ctrl + C')
print('========= end =========')
在Terminal 终端运行此文件
(venv) C:\Users\hyl\PycharmProjects\untitled>python test.py
运行结果
~~~
~~~
~~~
~~~
~~~
~~~
~~~
~~~
Ctrl + C
========= end =========
Exception 及子类
- Exception 是所有内建的、非系统的异常的基类, 自定义异常类应该继承自它
SystaxError 语法错误
- Python 将这种错误也归到异常类下面的Exception下的子类, 但这种错误是不可捕获的
def a():
try:
00a = 5
except:
pass
运行结果
File "C:/Users/hyl/PycharmProjects/untitled/Excise/test.py", line 3
00a = 5
^
SyntaxError: invalid syntax
ArithmeticError
- 所有算数计算引发的异常, 其子类有除零异常
LookupError
使用映射的键或序列的索引无效时引发的异常的基类 : Index, KeyError
自定义类异常 - 从Exception 继承的类
class MyException(Exception):
pass
try:
raise MyException()
except MyException:
print('catch the exception')
运行结果
catch the exception
Process finished with exit code 0
多种捕获
- except 可以指定捕获的类型, 捕获多种异常
import sys
class MyException(Exception):
pass
try:
a = 1/0
raise MyException()
open('t')
sys.exit(1)
except ZeroDivisionError:
print('zero')
except ArithmeticError:
print('ari')
except MyException:
print('catch my exception')
except Exception:
print('exception')
except:
print('sysexit')
运行结果
zero
Process finished with exit code 0
捕获规则
- 捕获是从上之下依次比较, 如果匹配则执行except 语句块
- 如果被一个except 语句捕获, 其他语句就不再捕获
- 如果没有任何一个except 语句捕获到这个异常, 则该异常向外抛出
- 捕获原则从小到大, 从具体到宽泛
as 子句
先来看一个例子
class A: pass
try:
# 1/0
raise 1
# raise 'abc'
# raise {}
# raise A
# raise A()
except:
print('catch the except')
被抛出的异常应该是异常类的实例, 如何获取这个对象呢 ? 使用 as子句
class MyException(Exception):
def __init__(self, code, message):
self.code = code
self.message = message
try:
raise MyException
except MyException as e:
print('catch the exception')
except Exception as e:
print(e)
运行结果
__init__() missing 2 required positional arguments: 'code' and 'message'
Process finished with exit code 0
- raise 后跟类名是无参构造实例, 因此需要2个参数
- 下面我们把参数补上
class MyException(Exception):
def __init__(self, code, message):
self.code = code
self.message = message
try:
raise MyException(200, 'ok')
except MyException as e:
print('catch the exception')
except Exception as e:
print(e)
运行结果
catch the exception
Process finished with exit code 0
raise 语句
- raise 后要求应该是BaseException 类的子类或者实例, 如果是类, 将被无参实例化
- raise 后面什么都没有表示抛出最近一个被激活的异常, 如果没有被激活的异常则抛出类型异常, 这种方式相对用的较少
finally 子句
- finally 最终, 即最后一定要执行的, try…finally 语句块中, 不管是否发生异常, 都要执行finally 的部分
try:
f = open('test.txt')
except FileNotFoundError as e:
print(e.__class__, e.errno, e.strerror)
finally:
print('清理工作')
f.close() # 注意
运行结果
<class 'FileNotFoundError'> 2 No such file or directory
清理工作
Traceback (most recent call last):
File "C:/Users/hyl/PycharmProjects/untitled/Excise/test.py", line 7, in <module>
f.close()
NameError: name 'f' is not defined
Process finished with exit code 1
- 在在执行到open 的时候就抛出异常, 所以没有执行到f
- 注意上例中f 的作用域, 解决办法是在外部定义f
- finally 中一般放置资源的清理, 释放工作的语句
f = None
try:
f = open('test.txt')
except FileNotFoundError as e:
print(e.__class__, e.errno, e.strerror)
finally:
print('清理工作')
if f:
f.close()
也可以在finally 中再次捕捉异常
try:
f = open('test.txt')
except FileNotFoundError as e:
print(e.__class__, e.errno, e.strerror)
finally:
print('清理工作')
try:
f.close()
except Exception as e:
print(e)
finally 执行时机
def foo():
try:
return 3
finally:
return 5
print('finally')
print('==')
print(foo())
运行结果
5
执行过程 : 进入try 执行return 3, 虽然函数要返回, 但是finally一定要执行, 所以return 5, 函数返回. 5被压在栈顶, 所以返回5, 简单说函数的返回值取决于最后一个return 语句, 而finally 是try…finally 语句中最后执行的语句块
异常的传递
def foo1():
return 1/0
def foo2():
print('foo2 start')
foo1()
print('foo2 stop')
foo2()
运行结果
foo2 start
Traceback (most recent call last):
File "C:/Users/hyl/PycharmProjects/untitled/Excise/test.py", line 9, in <module>
foo2()
File "C:/Users/hyl/PycharmProjects/untitledExcise/test.py", line 6, in foo2
foo1()
File "C:/Users/hyl/PycharmProjects/untitled/Excise/test.py", line 2, in foo1
return 1/0
ZeroDivisionError: division by zero
Process finished with exit code 1
- foo2调用了foo1, foo1产生的异常传递到foo2中
- 异常总是想外层抛出, 如果外层没有处理这个异常就会继续想外抛出
- 如果内层捕获并处理了这个异常, 外部就不能捕获到了
- 如果到了最外层还没有被处理, 就会中断异常所在的线程的执行, 注意整个程序结束时的状态返回值
# 线程中异常测试
import threading
import time
def foo1():
return 1/0
def foo2():
time.sleep(3)
print('foo2 start')
foo1()
print('foo2 stop')
t = threading.Thread(target=foo2)
t.start()
while True:
time.sleep(1)
print('Everything is OK')
print(threading.enumerate()) # 枚举当前所有线程
运行结果
Everything is OK
[<_MainThread(MainThread, started 968)>, <Thread(Thread-1, started 8556)>]
Everything is OK
[<_MainThread(MainThread, started 968)>, <Thread(Thread-1, started 8556)>]
Everything is OK
[<_MainThread(MainThread, started 968)>, <Thread(Thread-1, started 8556)>]
foo2 start
Exception in thread Thread-1:
Traceback (most recent call last):
File "C:\Users\hyl\AppData\Local\Programs\Python\Python36\lib\threading.py", line 916, in _bootstrap_inner
self.run()
File "C:\Users\hyl\AppData\Local\Programs\Python\Python36\lib\threading.py", line 864, in run
self._target(*self._args, **self._kwargs)
File "C:/Users/hyl/PycharmProjects/untitled/Excise/test.py", line 10, in foo2
foo1()
File "C:/Users/hyl/PycharmProjects/untitled/Excise/test.py", line 5, in foo1
return 1/0
ZeroDivisionError: division by zero
Everything is OK
[<_MainThread(MainThread, started 968)>]
Everything is OK
[<_MainThread(MainThread, started 968)>]
Everything is OK
[<_MainThread(MainThread, started 968)>]
Everything is OK
[<_MainThread(MainThread, started 968)>]
Process finished with exit code 1
try 嵌套
try:
try:
ret = 1/0
except KeyError as e:
print(e)
finally:
print('inner fin')
except:
print('outer catch')
finally:
print('outer fin')
运行结果
inner fin
outer catch
outer fin
Process finished with exit code 0
- 内部捕获不到异常会想外层传递
- 如果内层有finally 且其中有return、break语句, 则异常就不会向外层抛出
def foo():
try:
ret = 1/0
except KeyError as e:
print(e)
finally:
print('inner fin')
return # 异常被丢弃
try:
foo()
except:
print('outer catch')
finally:
print('outer fin')
运行结果
inner fin
outer fin
Process finished with exit code 0
异常的捕获时机
1. 立即捕获
- 需要立即返回一个明确的结果
def parse_int(s):
try:
return int(s)
except:
return 0
print(parse_int('s'))
运行结果
0
Process finished with exit code 0
2. 边界捕获
-
封装产生边界
- 例如 : 写了一个模块, 用户用这个模块的时候捕获异常, 模块内部不需要捕获、处理异常, 一旦内部处理了, 外部调用者就无法感知了
- 自己写了一个类, 使用了open函数, 但是出现了异常不知道如何处理, 就继续想外抛出, 一般来说做外层也是边界, 必须处理这个异常了, 否则线程退出
else 子句
try:
ret = 1 * 0
except ArithmeticError as e:
print(e)
else:
print('OK')
finally:
print('fin')
运行结果
OK
fin
Process finished with exit code 0
- else 子句, 没有任何异常发生, 则执行
总结
try :
< 语句 > # 运行别的代码
except < 异常类 > :
< 语句 > # 捕获某种异常类型
except < 异常类 > as < 变量名 > :
< 语句 > # 捕获某种类型的异常并获得对象
else :
< 语句 > # 如果没有异常发生执行
finally :
< 语句 > # 退出try 时总会执行
敲代码的单身汉子, 最后祝大家情人节快乐 ❤