函数进阶:异常常见类型、异常处理语法、补充、实战、生成器对象、yield冷门用法、生成器表达式的面试题
一、异常常见类型
当我们在编写python代码的时候会出现各种原因导致的异常,这里我们可以举一些常见的例子:
1、AssertionError
当 assert 语句失败时将被引发。用户利用断言语句检测异常时,如果断言语句检测的表达式为假,则会引发这种异常。
2、KeyError
KeyError是关键字错误,当在现有键集合中找不到指定的映射(字典)键时就会引发错误。这个异常主要发生在字典中,比如当用户试图访问一个字典中不存在的键时会被引发。
3、NameError
NameError是当某个局部或全局名称未找到时将被引发,也就是指变量名称发生错误,比如用户试图调用一个还未被赋值或初始化的变量时会被触发。
4、ValueError
当操作或函数接收到具有正确类型但值不适合的参数,也就是值错误,比如想获取一个列表中某个不存在值的索引。
5、SystemError
当解释器发现内部错误,但情况看起来尚未严重到要放弃所有希望时将被引发。 关联的值是一个指明发生了什么问题的字符串(表示为低层级的符号)。
6、SyntaxError
SyntaxError主要是因为当解析器遇到语法错误,比如少个冒号、多个引号之类的,编程时稍微疏忽大意一下就会出错,应该是最常见的一种异常错误了。
7、TypeError
TypeError是类型错误,当一个操作或函数被应用于类型不适当的对象时将被引发。比如在要求 int 时却传入了 list就会导致错误。
8、IndexError
当序列抽取超出范围时将被引发,也就是索引超出范围,比如最常见下标索引超出了序列边界,比如当某个序列m只有三个元素,却试图访问m[4]。
9、StopIteration
StopIteration为迭代器错误,由内置函数 next() 和 iterator 的 __next__() 方法所引发,用来表示该迭代器不能产生下一项。当访问至迭代器最后一个值时仍然继续访问,就会引发这种异常。
10、AttributeError
AttributeError是属性错误,当属性引用或赋值失败时就会出现。比如列表有index方法,而字典却没有,所以对一个字典对象调用该方法就会引发该异常。
11、AttributeError
访问的对象属性不存在
12、ImportError
无法导入模块或者对象,主要是路径有误或名称错误
13、IndentationError
代码没有正确对齐,主要是缩进错误
14、IOError
输入/输出异常,主要是无法打开文件
15、OverflowError
数值运算超出最大限制
16、TabError
Tab和空格混用
17、ZeroDivisionError
除法运算中除数0 或者 取模运算中模数为0
二、异常处理语法结构
在python中我们有专门的语法结构来应对一些将要出错的代码:
语法结构:
try:
待监测的代码(可能会出错的代码)
except 错误类型 as e:
针对上述错误类型制定的方案
功能讲解:
1、我们也可以根据这里面单词的意思大概看出来这个语法结构的作用,第一个单词在这里可以看成是尝试的意思,相应的我们把需要监测的代码放里面运行,第二个单词是期望的意思,就是说如果代码出现的错误是我们所期望的这个,那么就用下面的子代码进行处理,如果期望类型错了就会报错,对了就不会报错。
2、except后面的as e可以不写,写了的话会返回系统提示的报错信息。
3、当我们使用try监测代码的时候只能监测一个错误,当代码检测到第一个错误的时候就会输出提示信息然后停止代码的运行,后面的错误就不会被检测到。
4、这里我们也发现了,一个except只能应对一种错误类型,如果错误类型很多的话我们有两种选择,一种是写很多个except把错误类型写全,第二种选择是在错误类型处写上Exception/BaseException,就是后面要说的万能异常。
try:
待监测的代码(可能会出错的代码)
except 错误类型1 as e: # e就是系统提示的错误信息
针对上述错误类型1制定的方案
except 错误类型2 as e: # e就是系统提示的错误信息
针对上述错误类型2制定的方案
except 错误类型3 as e: # e就是系统提示的错误信息
针对上述错误类型3制定的方案
...
万能异常
对应的异常类型是:Exception/BaseException
两个异常类型作用一样,都包含所有的错误类型,但是BaseException是Exception的父代码。
try:
待监测的代码(可能会出错的代码)
except Exception as e: # e就是系统提示的错误信息
针对各种常见的错误类型全部统一处理
try语法结构结合else和finally
try结构结合else和finall功能很简单:
1、else后面跟的子代码是在try的子代码正常运行的时候才会执行的
2、finally是不管try的子代码运行是否正确都会运行的代码
try:
待监测的代码(可能会出错的代码)
except Exception as e: # e就是系统提示的错误信息
针对各种常见的错误类型全部统一处理
else:
try的子代码正常运行结束没有任何的报错后 再执行else子代码
finally:
无论try的子代码是否报错 最后都要执行finally子代码
注意事项
1、虽然异常处理可以防止代码报错停止运行,但是这并不意味着我们可以多用异常处理减少异常。
2、异常处理应该尽量少用,当出现无法控制的情况报错才考虑使用,如手机软件在无法访问网络的时候使用异常处理进行提示现在没网不能运行。
3、try语法结构下监测的代码应该尽量少一些。
三、异常处理补充
1、断言
断言就是对一段代码进行判断,如果判断结果正确就继续运行,如果判断错误了就抛出AssertionError异常,直接中断代码的运行
name = 'jason'
# assert isinstance(name, int)
assert isinstance(name, str)
print('哈哈哈 我就说吧 肯定是字符串')
name.strip()
这里我们用isinstance判断数据的类型,如果把name绑定的字符串换成列表就会中断代码的运行抛出AssertionError异常。
2、主动抛出异常
跟上面的断言原理相似,当我运行代码检测到指定的数据值或条件的时候就直接抛出异常中断代码的运行。主动抛出异常需要用到raise这个方法,或是断言中的assert方法。
raise 异常类型名称(‘异常描述')
name = 'jason'
if name == 'jason':
raise Exception('老子不干了')
else:
print('正常走')
当name绑定的值是字符串jason的时候就抛出异常中断代码运行。
四、异常处理实战应用
课堂练习:
使用while循环+异常处理+迭代器对象 完成for循环迭代取值的功能
l1 = [11, 22, 33, 44, 55, 66, 77, 88, 99]
die_qi = l1.__iter__()
while True:
try:
res = die_qi.__next__()
print(res)
except Exception as e:
print('数据取完了')
break
这里的Exception错误类型范围太广了,我们换成StopIteration异常类型也是一样的作用。
五、生成器
1、生成器对象的本质
生成器对象其实本质上就是迭代器对象,内置有双下户线iter和双下划线next方法
2、区别
迭代器对象就是python解释器提供的各种数据类型或是文件对象,但是生成器对象是程序员编写的代码或是函数之类的关键字。
3、生成器的两种形式
1.使用def定义函数然后内部使用yield关键字
在使用函数+yield关键字定义生成器的时候需要注意当我们使用括号调用函数的时候并没有运行这个函数,而是把这个函数体代码变成了一个生成器对象,之后我们想要调用函数产生数据就需要跟迭代器一样使用双下划线的next方法,并且每次运行的时候如果遇到yield关键字就会停止到那个位置,如果下方代码还有yield关键字,下一次运行双下划线的next方法就会运行到下一个yield关键字处停止。
def my_iter():
print('哈哈哈 椰子汁很好喝')
yield
'''1.函数体代码中如果有yield关键字
那么函数名加括号并不会执行函数体代码
会生成一个生成器对象(迭代器对象)
'''
res = my_iter()
print(res)
# <generator object my_iter at 0x00000204CEDEBBA0>
'''2.使用加括号之后的结果调用__next__才会执行函数体代码'''
res.__next__()
# 哈哈哈 椰子汁很好喝
下面是多个yield的使用场景:
'''3.每次执行完__next__代码都会停在yield位置 下次基于该位置继续往下找第二个yield'''
def my_iter():
print('哈哈哈 椰子汁很好喝')
yield 111, 222, 333
print('呵呵呵 从小喝到大')
yield 111, 222, 333
print('嘿嘿嘿 特种兵牌还可以')
yield 111, 222, 333
print('哼哼哼 千万别整多了 倒沫子 头发掉光光')
yield 111, 222, 333
res = my_iter()
r1 = res.__next__()
print(r1)
r2 = res.__next__()
print(r2)
r3 = res.__next__()
print(r3)
r4 = res.__next__()
print(r4)
'''4.yield还有点类似于return 可以返回返回值'''
'''结果如下:
哈哈哈 椰子汁很好喝
(111, 222, 333)
呵呵呵 从小喝到大
(111, 222, 333)
嘿嘿嘿 特种兵牌还可以
(111, 222, 333)
哼哼哼 千万别整多了 倒沫子 头发掉光光
(111, 222, 333)
'''
注:在上面的代码运行过程中我们也发现了yield关键字跟return一样可以返回返回值
2.使用生成器表达式
可以简化代码,节省内存
l1 = (i ** 2 for i in range(100)) # 生成器对象
print(l1)
# <generator object <genexpr> at 0x000001DFC07F7E40>
for i in l1:
print(i)
使用生成器生成对象后需要使用循环输出里面的数据值,或是转换类型后一次性输出。
4、自定义生成器对标range功能(一个参数 两个参数 三个参数 迭代器对象)
考虑到range功能复杂,所以我们这里要使用函数加yield关键字的方法来建立自定义生成器
def my_range(start_num, end_num=None, step=1):
# 判断end_num是否有值 没有值说明用户只给了一个值 起始数字应该是0 终止位置应该是传的值
is_back = False
if not end_num:
# 判断第二个结束为止是否有参数,如果没有就把传给起始位置的参数给结束为止,其实位置变成0
end_num = start_num
start_num = 0
if step == 0:
yield '位移量不能为0'
if step < 0:
# 判断取值的步频是否为反方向
# 如果是反方向的步频,判断起始位置和结束为止是否前面的大后面的小
if not start_num > end_num:
yield '参数大小错误'
else:
is_back = True
# 起始位置和结束为止是前面的大后面的小的时候就返回True
while start_num < end_num or is_back:
# 起始位置小于结束位置的时候或是步频为负数的时候判断正确的话就输出数据值
if start_num < end_num:
# 起始位置小于结束位置
print(start_num)
yield start_num
start_num += step
elif is_back:
# 步频为负数
if start_num > end_num:
yield start_num
start_num += step
else:
is_back = False
# 定义了模仿range方法的功能后,用for循环的方式调用内部数据值
for i in my_range(100, 50, 0):
print(i)
六、yield冷门用法
当我们使用yield关键字的时候可以用变量名绑定yield关键字,然后就可以使用send方法传参,然后会再次运行到yield关键字处停止运行。
def eat(name, food=None):
print(f'{name}准备用餐')
while True:
food = yield
print(f'{name}正在吃{food}')
res = eat('jason')
res.__next__()
res.send('汉堡') # 1.将括号内的数据传给yield前面的变量名 2.再自动调用__next__
res.send('包子')
res.send('面条')
'''
jason准备用餐
jason正在吃汉堡
jason正在吃包子
jason正在吃面条
'''
七、生成器表达式的面试题
"""
面试题(有难度)
大致知道流程即可
"""
def add(n, i): # 普通函数 返回两个数的和 求和函数
return n + i
def test(): # 生成器
for i in range(4):
yield i
g = test() # 激活生成器
for n in [1, 10]:
g = (add(n, i) for i in g)
"""
第一次for循环
g = (add(n, i) for i in g)
第二次for循环
g = (add(10, i) for i in (add(10, i) for i in g))
"""
res = list(g)
print(res)
#A. res=[10,11,12,13]
#B. res=[11,12,13,14]
#C. res=[20,21,22,23]
#D. res=[21,22,23,24]
'''不用深入研究 大致知道起始数即可'''"""
面试题(有难度)
大致知道流程即可
"""
def add(n, i):
return n + i
# 定义了一个add函数,作用求和
def test(): # 生成器
for i in range(4):
yield i
# 定义了一个生成器
g = test() # 激活生成器
# 激活了生成器,这样就而已用for循环取值了
for n in [1, 10]:
g = (add(n, i) for i in g)
"""
第一次for循环
这里就相当于给g重新绑定了一个生成器没有运行代码
g = (add(n, i) for i in g)
第二次for循环
这里就相当于用到for循环去(add(n, i) for i in g)里面取值了,这个时候的n是10,生成器给的四个值是0、1、2、3,然后运行add就等于c选项
g = (add(10, i) for i in (add(10, i) for i in g))
"""
res = list(g)
print(res)
# A. res=[10,11,12,13]
# B. res=[11,12,13,14]
# C. res=[20,21,22,23]
# D. res=[21,22,23,24]
'''不用深入研究 大致知道起始数即可'''