异常捕获与生成器
今日内容
- 异常捕获
- 生成器对象
- 生成器表达式
- 迭代取值与索引取值的差异
异常捕获
1.首先我们要了解什么是异常
程序在运行过程中如果出现了异常就会导致整个程序结束
而异常就是程序员空中所说的 bug
2.异常的结构
就是但我们的程序出bug时会出现什么
# 在整个py文件下面会出现多行红色的英文 Traceback (most recent call last): File "D:/MYpycharm/day18/yield.py", line 14, in <module> name NameError: name 'name' is not defined ''' 当出现bug时我们是有查看顺序的 ''' # 1.首先我们要看最后一行 name 'name' is not defined 有时候如果很简单的错误一看就知道是什么错误了 # 2.第二看关键字line, 它能精准的找到代码是哪行出错了, 鼠标点击链接就会跳转到bug处 # 3.第三看错误类型 就是最后一行冒号得到左侧的信息(NameError)他能告诉你是什么错误信息
3.异常的类型
异常的类型就是看错误的最后一行冒号左侧的名字
异常的类型有很多种:
NameError : 变量名错误
IndexError : 索引错误
KeyError : 字典key错误
SyntaxError : 语法错误
TypeError : 类型错误
......
4.异常的分类
异常分为两大类
1.语法错误
# 语法错误一旦出现就要立刻修改 在代码中不能出现 eg: if # if 后面什么也没有写的 while # while 后面也什么没有写的 .......
2.逻辑错误
# 逻辑错误是可以被允许的 出现修改即可 eg: l = [1, 2] print(l[10]) # 索引超出范围 修改即可 d = {'name': 'tony'} print(d['pwd']) # 字典中没有该键 修改即可
异常捕获的演练
1.什么时候我们需要编写代码处理异常
当我们不确定代码哪时候会报错的时候我们就需要编写代码提前写好措施
2.代码演示
2.1基本的语法结构
可以针对把不同的异常类型做出不同的措施
# 语法结构 try: 检测的代码 except 错误类型: 对应错误类型的解决措施 # except可以写多个 try: name l = [1, 2] print(l[10]) except NameError: print('根据NameError做出的措施') # 根据NameError做出的措施 except IndexError: print('根据IndexError做出的措施') # 根据IndexError做出的措施
还可以在错误类型后面加上as e
可以知道具体错误信息
try: name l = [1, 2] print(l[10]) d = {'name': 'tony'} print(d['pwd']) except NameError as e: print('根据NameError做出的措施') # 根据NameError做出的措施 print(e) # name 'name' is not defined except IndexError as e: print('根据IndexError做出的措施') # 根据IndexError做出的措施 print(e) # list index out of range except KeyError as e: print('根据KeyError做出的措施') # 根据KeyError做出的措施 print(e) # 'pwd' ''' e 就是具体的错误信息 '''
2.2 万能异常
就是比较笼统的处理方式
可以把所有的错误方式都可以捕获
try: # name l = [1, 2] print(l[10]) except Exception as e: print('针对都有的错误做出得到措施') # 针对都有的错误做出得到措施 print(e) # list index out of range ''' 这个语法不管是什么错误都可以捕捉并返回 '''
2.3 异常捕获的补充
2.3.1 try与else和finally的用法
try: # name l = [1, 2] print(l[10]) except Exception as e: print('针对都有的错误做出得到措施') # 针对都有的错误做出得到措施 print(e) # list index out of range else: print('try检测代码没有出错的情况下 才会执行else的子代码') finally: print('try检测代码不管有没有错误都会执行finally的子代码') ''' else就是当try检测的代码如果没有出错的情况下 就会执行else的子代码 如果出错了就会跳过else的子代码 finally就是当try检测的代码不管有没有错误 都会执行finally的子代码 '''
2.3.2 断言
name = 'tony' assert isinstance(name, list) # 就是判断获取的变量名是什么类型跳过不是第二个参数类型就会直接报错 print('name使用了列表的相关操作') # 如果是第二个参数的类型才会执行该行代码
2.3.4 主动抛异常
就是当我们输入了xxx我们检测就会报错
name = input('请输入名字>>>:').strip() if name == 'tony': raise NameError('就是不让tony正常') # raise Exception('是tony 就会报错') else: print('不是tony就不会报错') ''' 可以使用针对性的异常类型 也可以使用万能异常捕获 请输入名字>>>:tony Traceback (most recent call last): File "D:/MYpycharm/day18/yield.py", line 19, in <module> raise NameError('就是不让tony正常') NameError: 就是不让tony正常 '''
5.注意
捕获异常能少用就少用
被try检测的代码能少则少
实际代码的演练
l = [1, 2, 3, 4, 5, 6, 7] # 如何使用while循环模拟for循环取值 res = l.__iter__() while True: print(res.__next__()) ''' 如果就这样写的话当列表中值被取完的时候肯定会报错的 1 2 3 4 5 6 7 Traceback (most recent call last): File "D:/MYpycharm/day18/yield.py", line 16, in <module> print(res.__next__()) StopIteration 我们要加上异常捕获 ''' l = [1, 2, 3, 4, 5, 6, 7] # 如何使用while循环模拟for循环取值 res = l.__iter__() while True: try: print(res.__next__()) except Exception: print('值取完了 还想要你个死鬼') break ''' 这样就会正常结束不会报错 1 2 3 4 5 6 7 值取完了 还想要你个死鬼 '''
当出现bug时千万不要慌 我们慢慢找总能找到问题的 是在不行还可以问度娘嘛
生成器对象
1.生成器对象的本质
其实就是迭代器对象
只不过迭代器对象是python解释器自带的
而生成器是我们自己手写的(手写不觉的很有成就感嘛)
写完之后内置方法里肯定有 双下iter和双下next方法的 __iter__ 和__next__
2.学习生成器的目的
学习生成器的目的就是为了能够很好的优化代码
一种可以不依赖于索引取值的通用取值方法
可以节省数据类型的内存占用空间
3.代码演示
如果需要自己手写生成器那么我们需要一个关键字yield
3.1 yield关键字
def index(): print('你猜我怎么执行') yield index() ''' 调用index时是不会执行函数体代码的 因为函数体代码中有yield关键字 如果函数名加括号调用的话是不执行函数体代码的 而是会由普通的函数变成迭代器对象(生成器) 而调用迭代器对象需要使用__next__方法才能取到值 而且在函数中可以编写多个yield 并且yield还可以传返回值 ''' def index(): print('你猜我怎么执行') yield 11 print('第二次执行') yield 22 print('第三次执行') yield 33 print('第四次执行') yield 44 print(index().__next__()) print(index().__next__()) print(index().__next__()) print(index().__next__()) ''' 这个不管执行多少次都都是在执行迭代器对象的本身 因为从始至终都只产生一个 你猜我怎么执行 11 你猜我怎么执行 11 你猜我怎么执行 11 你猜我怎么执行 11 ''' def index(): print('你猜我怎么执行') yield 11 print('第二次执行') yield 22 print('第三次执行') yield 33 print('第四次执行') yield 44 res = index() print(res.__next__()) print(res.__next__()) print(res.__next__()) print(res.__next__()) ''' yield就是执行函数体代码时遇到第一个yield会暂停函数到yield后面 再次调用__next__的时候就会继续执行后续代码直到遇到第二个yield又会暂停到yield后面 再次调用__next__就又会继续执行 直到遇到最后一个 最后一个还要还要调用的话 就会报错 你猜我怎么执行 11 第二次执行 22 第三次执行 33 第四次执行 44 yield后面可以写返回值写啥返回啥 如果是多个数据就会组织成元祖返回 '''
3.2利用yield编写range
这样我们就可以yield编写一个类型range的迭代器
def my_range(start_num, end_num=None): if not end_num: end_num = start_num start_num = 0 while start_num < end_num: yield start_num start_num += 1 for i in my_range(1, 10): print(i) ''' 1 2 3 4 5 6 7 8 9 ''' # 因为真正的range是有第三个参数的所以我们还要写入第三个形参 def my_range(start_num, end_num=None, temp=1): # 因为真正的range的第三个参数如果是负数的话是不会执行的所以直接使用return结束即可 # 真正的range的第三个参数也不能为0 我们自己用print代替即可 if temp == 0: print('temp不能为0') return if temp < 0: return if not end_num: end_num = start_num start_num = 0 while start_num < end_num: yield start_num start_num += temp for i in my_range(1, 10, 0): print(i) # temp不能为0 for i in my_range(1, 10, 2): print(i) ''' 1 3 5 7 9 '''
3.3yield的其他用法
def func(name, food=None): print(f'{name}准备干饭了') while True: food = yield print(f'{name}准备吃{food}') res = func('tony') res.__next__() res.__next__() res.__next__() ''' tony准备干饭了 tony准备吃None tony准备吃None 我们可以发现food没有参数这个时候我们可以使用seed关键字传入参数 ''' def func(name, food=None): print(f'{name}准备干饭了') while True: food = yield print(f'{name}准备吃{food}') res = func('tony') res.__next__() res.send('鲍鱼') res.send('腰子') ''' tony准备干饭了 tony准备吃鲍鱼 tony准备吃腰子 seed可以自动调用__next__方法 就可以传值了 '''
生成器表达式
# 其实就是之前所说的列表生成式很像 # 列表生成式 res = [i for i in range(10) if i > 2] print(res) # [3, 4, 5, 6, 7, 8, 9] # 生成器表达式 res = (i for i in range(10) if i < 7) print(res) # <generator object <genexpr> at 0x000002394EBF2C50> # 其实就是迭代器对象 只要调用__next__方法取值即可 print(res.__next__()) print(res.__next__()) print(res.__next__()) print(res.__next__()) print(res.__next__()) print(res.__next__()) print(res.__next__()) ''' 0 1 2 3 4 5 6 其实就是迭代器 只不过能够简便代码 节省内存空间
取多了也是会报错的 '''
迭代取值与索引取值的差异
# 其实两者各有各的有点各有各的缺点主要用于不同的场景 # 索引取值 l1 = [1, 2, 3, 4, 5, 6] print(l1[0]) print(l1[1]) print(l1[2]) print(l1[3]) print(l1[4]) print(l1[5]) res = l1.__iter__() print(res.__next__()) print(res.__next__()) print(res.__next__()) print(res.__next__()) print(res.__next__()) print(res.__next__()) ''' 其实两者的结果都为 1 2 3 4 5 6 ''' l1 = [1, 2, 3, 4, 5, 6] print(l1[0]) print(l1[0]) print(l1[3]) print(l1[3]) # 但是索引取值可以随意反复的取值 # 这还是迭代取值所做不到的 ''' 索引取值: 优势: 可以随意反复的获取容器中任意数据 劣势: 针对无序容器的类型无法取值 迭代取值: 优势: 提供了一种通用的取值方式 劣势: 一旦开始取值就只能往前取值不能往后取值 '''
小小面试题
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) # 选C #A. res=[10,11,12,13] #B. res=[11,12,13,14] #C. res=[20,21,22,23] #D. res=[21,22,23,24] ps:掌握技巧即可 无需深究 纯粹为了考试而出 没有多少实际意义 嵌套不符合编程规范