总体 要讲的大纲内容 如下
今天我们开始学习一个比较关键的一个概念叫 异常。 你可能没有接触 过什么 叫异常,它是所有 编程语言都比不可少的话题。 程序 有时候 并没有 我们想象的那么强大,有时候 也会出现 一些报错,导致程序 停止了。 这些报错 我们 就称为 程序抛出了一个异常。
简单理解 就是 我们说在代码执行的时候 发生了错误,这个时候就产生了一个异常, 程序就被迫中断了。
不知道 我们之前介绍的 一些基础的数据类型,我会说 某某方法 如果怎么样,就报错 IndexError 。 这些 报错 就是异常,当异常发生的时候,如果我们采取任何 措施,程序将会停止运行。
举几个 常见的报错的例子
>>> nums = ["one", "two", 'three', 'four', 'five']
>>> nums[10]
Traceback (most recent call last):
File "", line 1, in <module>
IndexError: list index out of range
IndexError 这里 就是一个异常,这个异常发生在 访问了一个 不存在的位置,python 解释器不知道如何做,所以就报错了。
>>> 'frank'.index('name')
Traceback (most recent call last):
File "", line 1, in <module>
ValueError: substring not found
ValueError 是一个异常, 这里想在 在字符串中寻找 一个子串name
发现没有找到,也报错了。这里就是抛出了一个异常 .
>>> 1/0
Traceback (most recent call last):
File "", line 1, in <module>
ZeroDivisionError: division by zero
ZeroDivisionError 这里也报错了, 这里很明显 1 是不能除以0 的,这个一般人都知道。 所以 你强行 给计算机算这个值,python解释器 也没有办法算出来。 这个 也会抛出异常。 ZeroDivisionError
>>>
... person = {
... 'weight': 60,
... 'name': 'frank',
... 'height': 165,
... 'hobby': 'swimming'
... }
>>>
>>> person['aaa']
Traceback (most recent call last):
File "", line 1, in <module>
KeyError: 'aaa'
在字典中 强行访问不存在的key , 这个时候 python 解释器 也不知道如何处理 ,然后也报错了。 这次报错叫 KeyError .
上面的这些例子 都叫异常。而且 不同的报错 有不同的异常。 也就是异常是可以分类的。就是 有不同类型的异常,通过 不同的异常,我们可以大概知道 程序发生了什么问题。 方便 我们 更好的排查问题。
当异常发生的 时候, 程序 就会中断,之后 的代码 将不会执行。
def operate():
print("operate begin")
nums = ["one", "two", 'three', 'four', 'five']
print(nums[1])
# 这里要发生异常
print(nums[10])
print(nums[-1]) #1
print("operate end") #2
operate()
运行这段代码 看看 会发生什么, 这个函数一开始先 打印 index=1 元素, 然后打印index=10的元素,显然不存在, 之后打印 最后一个元素。 最后输出 一句话 代表函数结束了。
来看下运行结果
operate begin
two
Traceback (most recent call last):
File "C:/Users/changfx/PycharmProjects/python-study/myerror.py", line 20, in
operate()
File "C:/Users/changfx/PycharmProjects/python-study/myerror.py", line 14, in operate
print(nums[10])
IndexError: list index out of range
Process finished with exit code 1
从结果可以看出 开始 正常打印, 当发生 IndexError 的报错后, 程序就退出了。并没有执行 后面 的语句。
没有打印最后的结束语,也没有打印 最后一个元素。
总结一下: 当异常发生的时候,如果我们没有对异常进行处理。程序 就中断 ,然后 程序就会异常的退出了。
try except finally else
可以通过这个 来 捕获可能发生的异常。
try:
pass
# 可能发生异常的代码段
except IndexError as e:
pass
来修改一下 上面的函数
def operate():
print("operate begin")
nums = ["one", "two", 'three', 'four', 'five']
print(nums[1])
try:
# 这里要发生异常
print(nums[10])
except IndexError as e:
print(e)
print(nums[-1])
print("operate end")
operate()
"""结果
operate begin
two
list index out of range
five
operate end
"""
再次运行 这个代码,发现 已经可以正常运行了。 程序正常退出了,这个时候 当发生 异常的时候 我们可以尝试捕获这个异常, 只要 这个异常被 捕获了, 那么程序就可以正常运行了。
对于 异常捕获 try 语句 还有其他的形式 。 简单说一下
有时候我们并不知道 哪些代码段会发生异常,也有可能不发生异常。 这个时候就要考虑 用 try 把可能发生的异常 进行捕获。
比如下面的函数, 我想 访问 下标为10 的元素,但是这个array 是作为一个函数的参数传入进来,我并不知道 这个array 是否有 下标 为10 的元素, 所以 我用 try 尝试捕获异常。这里我使用 这种结构 .
try:
pass
except IndexError as e:
pass
else:
pass
这里 else 语句代表 如果没有发生异常,会执行 else 语句的内容,如果发生了异常则不会执行else 语句的内容。
def print_nums(array: list):
print('print_nums begin')
try:
print(array[10])
except IndexError as e:
print(f"e={e}")
else:
print('end')
在console演示一下:
>>> def print_nums(array: list):
... print('print_nums begin')
... try:
... print(array[10])
... except IndexError as e:
... print(f"e={e}")
... else:
... print('end')
...
>>> array = list(range(5))
>>> print_nums(array) # 发生异常
print_nums begin
e=list index out of range
>>> array2 = list(range(11))
>>> print_nums(array2) #没有发生异常
print_nums begin
10
end
发生的异常的时候 else 的语句没有被执行,如果没有发生异常, else 的语句正常执行了。 这个就是else 语句的作用。 只有当 try 的代码段 没有发生异常的时候, else 的语句才会被执行。
在介绍一种 异常捕获的形式
finally 子句, 这里 finally 的语句 代表 无论 try 捕获 这段代码 是否发生异常, finally 永远被执行。 这里一般会做一些 资源释放, 数据库关闭连接之类的操作。
def print_nums(array: list):
print('print_nums begin')
try:
# 可能发生的异常的代码段
print(array[10])
except IndexError as e:
print(f"e={e}")
else:
print('else end')
finally:
print("print_nums end")
在 console 演示
>>> def print_nums(array: list):
... print('print_nums begin')
... try:
... # 可能发生的异常的代码段
... print(array[10])
... except IndexError as e:
... print(f"e={e}")
... else:
... print('else end')
... finally:
... print("print_nums end")
...
>>>
>>> print_nums(array=list(range(11))) # 不会发生异常
print_nums begin
10
else end
print_nums end
>>> print_nums(array=list(range(5))) # 会发生异常
print_nums begin
e=list index out of range
print_nums end
可以看出 无论发生 或者不发生异常,都会 执行 finally 子句的代码段。这个就是 finally 的作用。
可以使用 raise 语句 抛出一个异常. 这里 可以用来检测 一些用户输入,如果输入了一些不合法的值, 我们就可以主动 抛出一个异常。给出用户一些错误提示信息。
raise IndexError("xxxxxxx")
手动 抛出一个异常,比如 人的年龄 不可能超过 100,和小于0 。这里 手动抛出一个 ValueError 的异常
>>>
>>>
... age = 150
... if age > 100 or age < 0:
... raise ValueError("age >100 or age<0 ")
...
Traceback (most recent call last):
File "", line 4, in <module>
ValueError: age >100 or age<0
你注意到了吗? 这里我抛出的异常 是 ValueError , 而不是 KeyError ,IndexError 。 所以这里要提醒一下,异常 是有分类的,每一种异常 都有特定的含义,你需要了解这些 特定含义,才进行抛出特定的异常。 因为 异常信息 是用来帮助我们来排查程序出现的问题,所以 抛出异常信息 要尽量准确,这样才能方便排查 问题。
在python3 中 内置很多的异常,并且 异常也是 可以通过继承的。 继承的概念 属于面向对象的概念。 如果不是很理解,简单理解 的子承父业 差不多,下面以及的异常 肯定含有上面异常的所有的东西。 之后 我会单独 解释面向对象的知识。
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
所有的异常 都是 由 BaseException 派生出来的。 然后 对应 4个平级的异常, 我们通常 情况 只是 从 Exception 中 继承 来重写 自己定义的异常。 这里我们 经常 遇见的 也就10 来种左右。 作为了解知道即可。
我们 有时候 可能不需要系统系统的异常,或者满足不了我们的需要。 我们通常的做法是 继承 Exception 来实现自己的异常。
1.如何自定义自己的 异常对象
大部分情况 我们只要继承 Exception 即可。 有一个关键字 class
还记得 我说的,异常实际上一个类。所以我们想要实现自己的异常,一般 会写一个 class
然后继承 Exception 即可。
class InvalidAge(Exception):
pass
age = int(input("请输入你的年龄: "))
if age > 100 or age < 0:
raise InvalidAge("你的年龄应该是 [0-100] 之间 ")
>>> class InvalidAge(Exception):
... pass
...
...
... age = int(input("请输入你的年龄: "))
... if age > 100 or age < 0:
... raise InvalidAge("你的年龄应该是 [0-100] 之间 ")
...
请输入你的年龄: >? 123
Traceback (most recent call last):
File "", line 7, in <module>
InvalidAge: 你的年龄应该是 [0-100] 之间
异常 是什么? 异常是不是一种例外情况呢?
为了 更好的解释异常,我举两个数字相除的例子。来看下面的两个函数
def divide(number, divisor):
try:
print(f"{number} / {divisor} = {number / divisor}")
except ZeroDivisionError:
print("You can't divide by zero ")
def divide2(number, divisor):
if divisor == 0:
print("You can't divide by zero ")
return
print(f"{number} / {divisor} = {number / divisor}")
这两个函数 功能 是相同的,计算两个数 相除。 应该知道 除数不能 作为分母。 第一种方式 通过捕获这个异常 来提示用户 不要把除数变成0 , 第二种方式 是直接判断除数 是否为0 。
第二种方法 好不好呢? 其实 我不能告诉你 这样做好不好 。
但是 对于Python 程序员 更加细化追随 请求谅解,而不是许可的原则
。 就是说 python鼓励程序员先去做,然后 在去解决问题。 显然 python 是鼓励 大家勇于尝试的语言,实际上看 内置的库,也是遵守这个原则的。
当然还有另一种 原则三思而后行的原则
这种做法也有一定好处, 第一 减少了不必要的cpu 的开销,减少了一些不必要执行的代码。 这里对于例外情况 是非常有效的。
比如说 举个例子,在一个超市销售系统里面,有一个货物卖完了,显然这个 时候 我们应该返回什么呢?
空,缺货的字符串,一个负数 ? 我们通过if 语句判断 或取得数量 显然 这个逻辑 写起来 比较繁琐。
为啥 这个时候 我们不可以 自定义一个异常呢?
class OutOfStack(Exception):
pass
只要在每次用户调用购买的时候 ,我们try 进行捕获这段购买的逻辑。然后在购买的时候 ,如果判断缺货了,直接抛出 这个 我们自定义的异常。 这样的话,是不是看起来 更加好一点呢。
这里你可能对 面向对象 不理解,之后我们 慢慢解释一下 什么是面向对象编程。
class OutOfStack(Exception):
pass
class Goods:
def __init__(self, stock: int):
self.stock = stock
def purchase(self):
if self.stock == 0:
raise OutOfStack("库存为0,无法购买了!")
self.stock = self.stock - 1
print(f"购买成功。 当前库存容量:{self.stock}")
在控制台 执行
>>> class OutOfStack(Exception):
... pass
...
...
... class Goods:
...
... def __init__(self, stock: int):
... self.stock = stock
...
... def purchase(self):
... if self.stock == 0:
... raise OutOfStack("库存为0,无法购买了!")
... self.stock = self.stock - 1
... print(f"购买成功。 当前库存容量:{self.stock}")
...
>>>
>>> goods = Goods(3)
>>> goods.purchase()
购买成功。 当前库存容量:2
>>> goods.purchase()
购买成功。 当前库存容量:1
>>> goods.purchase()
购买成功。 当前库存容量:0
>>> goods.purchase()
Traceback (most recent call last):
File "", line 1, in <module>
File "", line 12, in purchase
OutOfStack: 库存为0,无法购买了!
当前库存为0 ,然后程序 抛出一个异常。告诉用户 库存不够了。
用异常 来进行流程控制 可以完成一些 非常好用的程序设计。 还有要明白,发生异常的时候并不意味着 你要努力阻止 这个这种情况发生。 相反 ,它是一种 无法直接 交流的代码 进行 信息沟通的方式而已。
什么是第三方包,就是一些别人觉得 非常有用的功能,写成一个工具包,这样 我们遇到 类似问题的时候,就不要重头开始写这些工具包,直接别人写好的工具就可以了。 是不是很好啊,直接窃取别人的劳动果实。哈哈,其实 这就是一种开源精神。 每个有追求的程序员都应该有这份追求。
那么问题来了 如何寻找 这些 工具包呢?
有一个网站 pypi.org 所有python的第三方包都会放到这个网站上面,可以自行搜索 查找。
下面 我们来安装 一下 这几个包
celery attrs requests redis
首先 你要知道 这些包的名称, 然后安装它 。
第一种方法 使用pip 工具安装
pip install package_name
打开git bash ,找到你的项目路径
$ cd python-study/
(venv)
CHANGFX@xxxxxxx ~/PycharmProjects/python-study
$ pwd
/c/Users/changfx/PycharmProjects/python-study
(venv)
CHANGFX@xxxxxx ~/PycharmProjects/python-study
# 这里是激活 虚拟环境
$ source venv/Scripts/activate
(venv)
CHANGFX@xxxxxx ~/PycharmProjects/python-study
$
(venv)
CHANGFX@xxxxxx ~/PycharmProjects/python-study
$ pip install redis
$ pip install requests
第二种 是pycharm 自带的安装工具
导航栏 中 File > Settings
找到 Project 然后点击 Project ,看到下面的画面,点击 下图中的加号
直接 在搜索栏 搜索 redis , 就能够看到 redis 相关的package ,点击 install package 就可以下载了。
同理 可以下载 ,把下面四个包 下载一下, 注意不要下载错了哦。
celery attrs requests redis
当你安装 完成后,你可以查看 自己的venv 这个目录下面 就会发现 已经有下面的路径了。
我让你安装 这些包,主要让你 看下,别人 如何定义自己的异常的。 然后感受一下。
当下载完成之后 要自己查看 一下 这些文件,可以看到都有自己定义一些异常类。
主要看下面的文件,都是 第三方包 自己定义的一些异常。
venv/Lib/site-packages/celery/exceptions.py
venv/Lib/site-packages/attr/exceptions.py
venv/Lib/site-packages/requests/exceptions.py
venv/Lib/site-packages/redis/exceptions.py
今天 主要讲解了 python 中的异常,一些概念。 异常实际上是一个对象。 我们根据自己的需要 定义自己的异常,方便 我们 更好的定位问题,查找错误等。 异常 是一个非常好的工具,之后慢慢体会。 先要明白如何处理异常,如何定义异常等。 加油!
exceptions
Python3 错误和异常
python3 面向对象编程