python基础之异常处理、生成器对象、生成器表达式
A-Z 65 -90
a-z 97-122
迭代取值 = for循环取值(每次取值都依赖于上一次取值)
目录
一、异常处理语法结构
1.异常的常见类型
1.语法错误
if 后面没东西
SyntaxError:invalid syntax
2.名字错误
name 没有赋值
NameError
3.索引错误
l1 = [1,3,4]
l1[100]
IndexError
4.字典关键字错误
d1={'name':'duo'}
d1['age']
KeyError关键字错误
5.缩进错误
IndentationError缩进错误: unexpected indent
2.异常处理语法结构
1.语法结构
try:
待监测的代码(可能会出错的代码) # 监测代码会占用一定的资源
except 错误类型:
针对上述错误类型制定的方案
# 举例
try:
name
except NameError:
print('名字没被定义')
2.查看错误的类型
try:
待监测的代码(可能会出错的代码)
except 错误类型 as e: # e 系统提示的错误信息
针对上述错误类型制定的方案
print(e) # 会打印提示的错误信息
3.针对不同的错误类型制定不同的解决方案**
# 把能想到的错误类型都写上
try:
待监测的代码(可能会出错的代码)
except 错误类型1 as e: # e 系统提示的错误信息
针对上述错误类型1制定的方案
except 错误类型2 as e: # e 系统提示的错误信息
针对上述错误类型2制定的方案
except 错误类型3 as e: # e 系统提示的错误信息
针对上述错误类型3制定的方案
4.万能异常**
try:
待监测的代码(可能会出错的代码)
except Exception as e: # e 系统提示的错误信息
# 或者 except BaseException as e:
# Exception 等价于 BaseException
print(e)
针对上述不同的错误类型统一处理
5.try 结合 else使用:
try:
待监测的代码(可能会出错的代码)
except Exception as e: # e 系统提示的错误信息
print(e)
针对上述不同的错误类型统一处理
else:
try的子代码正常运行结束没有任何报错后,再执行else子代码
6.try 结合 finally使用
finally:
无论try的子代码是否报错,最后都要执行finally子代码
3.异常处理补充
1.assert 断言
assert 提前判断是否符合要求,是的话就继续往下走,不是的话就会报错
# 判断是否是字符串
name = 'duo'
assert isinstance(name, int)
print('is str')
name = 'duo'
assert isinstance(name, str)
print('is str')
2.raise 主动抛异常
关键字 raise
name = 'duo'
if name == 'duo':
raise Exception('主动报错')
else:
print('代码继续走')
4.异常处理实战应用
1.异常处理能尽量少用(try子代码监测会占用一定的资源)
2.被try监测的代码越少越好
3.当代码中可能会出现一些无法控制的情况报错 才应该考虑使用
# 举例:编写网络爬虫程序请求数据 断网
课堂练习:
使用while 循环+ 异常处理+迭代器对象,完成for循环迭代取值的功能
# for循环底层代码
l1 = [11, 22, 33, 44, 55, 66, 77, 88, 99]
res = l1.__iter__()
while True:
try:
res1 = res.__next__()
print(res1)
except StopIteration as e:
break
# 详细版
l1 = [11, 22, 33, 44, 55, 66, 77, 88, 99]
# 1 先将l1调用 __iter__方法变为迭代器对象
# 1.1 迭代器对象需要用变量名接收
res = l1.__iter__()
# 2 while 循环获取l1中的值
while True:
# 3 __next__方法取值没有会报错,再加上异常处理
try:
print(res.__next__())
# 4 迭代取不到值就是StopIteration,可以将万能异常Exception表达更清楚
except StopIteration as e:
break
三、生成器对象
1.生成器对象的本质
1.本质
还是内置有__iter__和 __next__的迭代器对象
生成器对象定义了一个__next__方法,它要么返回迭代中的下一项,要么引发一个特殊的StopIteration异常来终止迭代,一个可迭代对象的迭代器用iter内置函数来接收值。
生成器是python中的一个对象(按照某种规律,来生成元素的对象),生成器不是列表,保存了产生元素的算法,同时会记录游标的位置(现在拿到第几个元素了)
,为了下次继续拿数据,而不是从头开始拿数据。可以通过一直调用next()方法获取值,这个对象不保存数据,每次调用会返回一个值,即做到了列表的好处,又不占用空间。
2.语法
2. 迭代器对象 和 生成器对象的 区别
1)迭代器对象是解释器自动提供的
数据类型、文件对象 >>>: 迭代器对象
2)生成器对象是程序员自己写出来的
代码、关键字 >>>: 迭代器对象(生成器)
3.创建生成器的语法(生成器函数)
函数体代码中加一个 yield关键字 # 则可以用 __next__方法
# 举例
def my_iter():
print('duoduodudoudouodo')
yield
'''
1 函数体代码中如果有yield关键字,
那么函数名加括号并不会执行函数体代码
会生成一个生成器对象
'''
my_iter()
res = my_iter()
'''
2 使用加括号之后的就能够调用__next__才会执行函数体代码
'''
res.__next__()
'''
3 每次执行完__next__代码都会停在yield位置,
下次基于该位置继续往下找第二个yield
'''
res.__next__() # StopIteration
def my_iter():
print('duoduodudoudouodo')
yield 111
print('2222')
yield 222
print('3333')
yield 333
print('4444')
yield 444
res = my_iter()
r1 = res.__next__() # 不赋值给变量名的话,只返回为一个地址
print(r1)
res.__next__()
res.__next__()
res.__next__()
'''
4 yield有点类似于return 可以返回返回值
'''
3.生成器详细
1.若函数体包含yield关键字,再调用函数,并不会执行函数体代码,得到的返回值即生成器对象
# 举例
def my_range(start, stop, step=1):
print('start...')
while start < stop:
yield start
start += step
print('end...')
g = my_range(0, 3)
print(g) # <generator object my_range at 0x104622b30>
# 可以看到用g接收有yield关键字的函数后,得到的是一个生成器对象
2.生成器内置有__iter__和__next__方法,所以生成器本身就是一个迭代器
print(g.__iter__) # <method-wrapper '__iter__' of generator object at 0x100752b30>
print(g.__next__) # <method-wrapper '__next__' of generator object at 0x10302ab30>
3.可以触发函数执行直到yield停止,将yield后的值返回,并将当前函数挂起,再次调用__next__方法,函数从上次暂停的位置继续执行
r1 =g.__next__() # start...
print(r1) # 0
r2 =g.__next__()
print(r2) # 1
r3 =g.__next__()
print(r3) # 2
r4 = g.__next__() # end... StopIteration
四、用生成器对象实现range方法
自定义生成器对标range功能(一个参数 两个参数 三个参数 利用迭代器对象)
1.先写两个参数的
def my_range(start_num, end_num):
while start_num < end_num:
yield start_num
start_num += 1
# 方法1
for i in my_range(1, 10):
print(i)
# 方法2
res = my_range(1, 10).__iter__()
while True:
try:
print(res.__next__())
except StopIteration:
break
2.增加参数和判断条件,实现range有一个参数和三个参数的功能
def my_range(start_num, end_num=None, step=1):
# 判断end_num是否有值,没有值说明用户只给了一个值,起始数字应该是0,终止位置应该是传的值
if not end_num:
end_num = start_num
start_num = 0
while start_num < end_num:
yield start_num
# step 默认为1 ,用step代替加的数字,则可以控制步长
start_num += step
for i in my_range(1, 10, 2):
print(i)
五、yield冷门用法
1. send方法 :send方法是生成一些列结果的下一个元素
通过调用send方法,将send的参数发送给 调用了next__方法产生了的生成器,并且生成器中的yield表达式返回了发送给send函数的值
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__
# jason准备吃饭
# jason准备吃汉堡
# 之后再次调用__next__方法,yield则返回None
res.__next__() # jason准备吃None
六、生成器表达式
1.生成器表达式
1.什么是生成器表达式:就是生成器的简化写法,结果是生成器对象
'''
生成器里面的代码,只有
1 调用__next__方法
2 for循环的时候才会执行
'''
l1 = (i ** 2 for i in range(100)) # 1 生成器表达式
print(l1) # <generator object <genexpr> at 0x100776900>
for i in l1: # 2 for循环的时候才会执行
print(i)
2.生成器表达式是一个能够将函数的灵活性与推导语法的简洁性结合到一起的工具
# 举例
def ups(line):
for i in line.split(','):
yield i.upper()
# 生成器关键字加括号(),激活生成器
res = tuple(ups('aaa,bbb,ccc'))
print(res) # AAA BBB CCC
d1 = {i: j for (i, j) in enumerate(ups('aaa,bbb,ccc'))}
print(d1)
2.生成器表达式面试题
# 大致知道流程即可
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]
1.迭代对象和迭代器对象
1)迭代器对象一定是可迭代对象,可迭代对象不一定是迭代器
2.迭代器对象可以用 __next__方法不断获取迭代器的下一个值,如果没有下一个值了,就会抛出异常 StopIteration
3.for循环的本质,可以看作是不断的调用__next__ 方法获取下一个值来实现的
# for语句可以循环可迭代对象,迭代器和生成器
4.生成器可以看作是一个特殊的迭代器,生成器具有和迭代器一样的特性
创建生成器的两种方法: 1)生成器表达式 2)生成器函数
1)生成器表达式
生成器表达式和列表推导式差不多,我们只需要把列表推导式的[]改为(),列表推导式返回的结果是列表对象,生成器表达式返回的是一个生成器对象
2)生成器函数
普通函数用 return来返回值,生成器函数用 yield来返回值
# 生成器函数和普通函数的区别
生成器函数和普通函数在执行流程上是有点区别的。
对于普通函数,按顺序执行时遇到 return 或最后一行函数语句就会返回;
对于有 yield的生成器函数,每次调用 __next__ 方法遇到 yield 语句才返回,如果再次调用 __next__ 方法,那么就会从上次返回的 yield 语句位置继续执行
生成器还可以用于处理海量数据的场景,比如读取大文件时,如果直接一次性读取可能会导致内存溢出,这个时候我们就可以借助 yield 生成器来灵活控制读取,防止内存占用过大。