Python之路迭代器协议、for循环机制、三元运算、列表解析式、生成器
a迭代的含义
迭代器即迭代的工具,那什么是迭代呢?
#迭代是一个重复的过程,每次重复即一次迭代,并且每次迭代的结果都是下一次迭代的初始值
b为何要有迭代器?
对于序列类型:字符串、列表、元组,我们可以使用索引的方式迭代取出其包含的元素。但对于字典、集合、文件等类型是没有索引的,若还想取出其内部包含的元素,则必须找出一种不依赖于索引的迭代方式,这就是迭代器
c可迭代对象
可迭代对象指的是内置有iter方法的对象,即字符串、元组、列表、集合、字典、文件,
'hello'.__iter__
(1,2,3).__iter__
[1,2,3].__iter__
{'a':1}.__iter__
{'a','b'}.__iter__
open('a.txt').__iter__
d迭代器对象
可迭代对象执行obj.__iter__()得到的结果就是迭代器对象
而迭代器对象指的是即内置有__iter__又内置有__next__方法的对象
可迭代对象(字符串、元组、列表、集合、字典、文件)通过调用
__iter__()
方法,这里是遵循迭代器协议,将可迭代对象转为一个迭代器,这时既可以调用
__iter__()方法又内置有__next__()方法
即为迭代器对象。迭代器对象是一个内存地址。
迭代器对象本身也可以使用__iter__()
方法
迭代器对象再次使用__iter__()
方法生成的还是迭代器对象。
例子
dic = {"k1":"v1","k2":"v2","k3":"v3","k4":"v4"}
iter_dic = dic.__iter__()
print(iter_dic)
v =iter_dic.__iter__()
print(v)
输出结果
<dict_keyiterator object at 0x02191600>
<dict_keyiterator object at 0x02191600>
分析:这里可以看到,对字典dic调用了__iter__()
方法,使其变成迭代器对象,再次对这个迭代器对象使用__iter__()
方法还是其本身。
e迭代器协议
1.迭代器协议是指:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代 (只能往后走不能往前退)
2.可迭代对象:实现了迭代器协议的对象(如何实现:对象内部定义一个iter()方法)
3.协议是一种约定,可迭代对象实现了迭代器协议,python的内部工具(如for循环,sum,min,max函数等)使用迭代器协议访问对象。
f注意:
迭代器对象一定是可迭代对象,而可迭代对象不一定是迭代器对象。
例子
s = "hello"
iter_s = s.__iter__() #将字符串用__iter__()方法转换为迭代器对象
print(iter_s.__next__()) #调用__next__()方法依次按照顺序打印每个字符
print(iter_s.__next__())
print(iter_s.__next__())
print(iter_s.__next__())
print(iter_s.__next__())
print(iter_s.__next__()) #抛出异常StopIteration,或者说结束标志
输出结果
h
e
l
l
o
#抛出异常StopIteration,或者说结束标志,StopIteration
这里等同于用for循环打印
s = "hello"
for i in s: #for i in s.__iter__()
print(i) #print(iter_s.__next__())直到出现StopIteration,然后结束循环
分析:这里的for 循环里的for i in s,s调用了__iter__()
方法,将s变为一个迭代器对象,同时对这个迭代器对象使用
__next__()方法
打印出来,循环访问,并处理了最后的StopIteration,结束了循环。
小知识
next()方法是调用python解释器的,等同于某个可迭代对象下的__next__()
方法
即
#print(next(iter_s))等同于print(iter_s.__next__())
例子2
dic = {"k1":"v1","k2":"v2","k3":"v3","k4":"v4"}
iter_dic = dic.__iter__()
print(iter_dic.__next__())
print(iter_dic.__next__())
print(iter_dic.__next__())
print(iter_dic.__next__())
# print(iter_dic.__next__()) 产生StopIteration停止标志
输出结果
k1
k2
k3
k4
改成for循环
dic = {"k1":"v1","k2":"v2","k3":"v3","k4":"v4"}
for i in dic: #dic调用了__iter__方法,将其改成迭代器对象
print(i) #使用__next__()方法挨个去打印,直到出现StopIteration结束
用while循环实现
dic = {"k1":"v1","k2":"v2","k3":"v3","k4":"v4"}
iter_dic = dic.__iter__()
while True:
try:
print(iter_dic.__next__())
except StopIteration:
print("迭代结束了,循环终止")
break
输出结果
k1
k2
k3
k4
#迭代结束了,循环终止
g迭代器的优缺点
优点:
-
提供一种统一的、不依赖于索引的迭代方式
-
惰性计算,节省内存
缺点:
-
无法获取长度(只有在next完毕才知道到底有几个值)
-
一次性的,只能往后走,不能往前退
二、三元运算
三元表达式的格式
为真时的结果 if 判定条件 else 为假时的结果
如果条件成立,返回if前面的结果,否则else 返回else后的结果
例子
a = 2
b = 3
s = a if a < b else b #这里的if语句后不加冒号
print(s)
输出结果
2
例子2
name = input('姓名>>: ')
res = 'SB' if name == 'ken' else 'NB'
print(name,res)
三、列表解析式
列表解析是Python迭代机制的一种应用,它常用于实现创建新的列表,返回的是一个列表,因此用在[]中。
例子
生成1-100以内的偶数
普通使用for循环的方式
li = []
for i in range(1,101):
if i % 2 == 0:
li.append(i)
else:
pass
print(li)
使用列表解析式
li = []
res = [i for i in range(1,101)if i % 2 == 0 ]
print(res)
例子2
将字符串变大写组成列表
普通方式
s = "nicholas"
li = []
for i in s :
res = i.upper()
li.append(res)
print(li)
列表解析式
s = "nicholas"
li = [i.upper() for i in s ]
print(li)
四、生成器
如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
要创建一个generator,有很多种方法。
第一种方法很简单,只要把一个列表生成式的[]
改成()
,就创建了一个generator,即生成器表达式:
生成器表达式能做的事情列表解析基本都能处理,只不过在需要处理的序列比较大时,列表解析比较费内存。
例子
li = [i*i for i in range(10) ]
print(li)
输出结果
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
分析:这里是生成了一个0到9的平方的列表。
这里要改成生成器只要把列表生成式的[]
改成()
li = [i*i for i in range(10) ]
g = (i*i for i in range(10) )
print(li)
print(g)
输出结果
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
<generator object <genexpr> at 0x02221600>
分析:这里的<generator object <genexpr> at 0x02221600>就是一个生成器。这是生成器的第一种形式。
生成器是包含有__iter__()
和__next__()
方法的,所以可以直接使用for来迭代
在这里调用__next__()方法
或者用next()直接打印
li = [i*i for i in range(5) ]
g = (i*i for i in range(5) )
print(li)
print(g)
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(next(g))
print(next(g))
#print(next(g)) #执行到此处就会产生一个StopIteration错误,类似可迭代对象调用__next__()一样
输出结果
这里的g生成器就是一个迭代器。
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
<generator object <genexpr> at 0x02211600>
0
1
4
9
16
这里也可以用for循环直接打印
li = [i*i for i in range(5) ]
g = (i*i for i in range(5) )
print(li)
for i in g:
print(i)
输出结果
[0, 1, 4, 9, 16]
0
1
4
9
16
分析:同样的这里的对生成器g的for循环自动处理了StopIteration错误,结束了for循环。与处理可迭代对象的方式类似。
第二种是生成器函数:
在函数中如果出现了yield关键字,那么该函数就不再是普通函数,而是生成器函数。生成器函数可以生产一个无线的序列,这样列表根本没有办法进行处理。
yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator。函数名+括号就变成了生成器。
例子
下面为一个可以生产奇数的生成器函数。
def num():
n=1
while True:
print("函数内的第一处",n)
yield n
print("函数内奇数", n)
n+=2
print("函数内的第二处", n)
new_num = num()
print(new_num)
next(new_num)
print("函数外面的",next(new_num))
输出结果
<generator object num at 0x02421660>
函数内的第一处 1
函数内奇数 1
函数内的第二处 3
函数内的第一处 3
函数外面的 3
分析:执行print(new_num)语句可以看到,这里的自定义函数是一个生成器,通过next()方法调用执行函数内部语句,这里的yield相当于return的功能,每次next()返回一个迭代值,下次next()继续执行循环,于是函数继续执行,直到再次遇到 yield,再次返回。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。而不是在while True语句下一次性执行完语句。
yield 与 return
return 是返回并中止函数
yield 是返回数据,并冻结当前的执行过程
next唤醒冻结的函数执行过程,继续执行,直到遇到下一个yield
例子
def g():
yield 1
yield 2
yield 3
new_g = g()
print(next(new_g))
#第一次调用next(new_g)时,会在执行完yield语句后挂起,所以此时程序并没有执行结束,函数返回数据1,并冻结当前执行过程,等待下一个next()唤醒执行过程
print(next(new_g))
#通过next()唤醒执行过程,yield返回数据2,冻结执行过程,等待下一个next()
print(next(new_g))
# print(next(new_g))
#这里如果运行上面这条语句,程序试图从yield语句的下一条语句开始执行,发现已经到了结尾,所以抛出StopIteration异常。
输出结果
1
2
3
分析:在一个生成器中,如果没有return,则默认执行到函数完毕时返回StopIteration;
如果遇到return,如果在执行过程中 return,则直接抛出 StopIteration 终止迭代。
例子
def g():
yield 1
yield 2
return "a"
yield 3
new_g = g()
print(next(new_g))#通过next()执行函数,得到返回数据1,冻结当前过程,等待下一个next()唤醒
print(next(new_g))#通过next()执行函数,得到返回数据1,冻结当前过程,等待下一个next()唤醒
print(next(new_g))#通过next()执行函数,遇到return语句,直接抛出StopIteration 终止迭代,这样yield '3'语句永远也不会执行。
print(next(new_g))
输出结果
1
Traceback (most recent call last):
2
File "D:/exercise/test1.py", line 11, in <module>
print(next(new_g))
StopIteration: a #如果在return后返回一个值,那么这个值为StopIteration异常的说明,不是程序的返回值。
如果在return后返回一个值,那么这个值为StopIteration异常的说明,不是程序的返回值。
生成器没有办法使用return来返回值。
例子
与上面的例子基本相同,只是修改了return的返回值
def g():
yield 1
yield 2
return "some"
yield 3
new_g = g()
print(next(new_g))
print(next(new_g))
print(next(new_g))
print(next(new_g))
输出结果
1
Traceback (most recent call last):
2
File "D:/exercise/test1.py", line 11, in <module>
print(next(new_g))
StopIteration: some #生成器return返回的值是为StopIteration异常的说明,不是程序的返回值。
生成器支持的方法
close()
手动关闭生成器函数,后面的调用会直接返回StopIteration异常。
例子
def g():
yield 1
yield 2
yield 3
new_g = g()
print(next(new_g))
print(next(new_g))
new_g.close() #这里通过cloes()方法直接关闭了生成器,后续通过next()也无法唤醒生成器继续执行返回数据,也就是说无法返回数据3,在这里直接抛出StopIteration异常
print(next(new_g))
print(next(new_g))
输出结果
1
Traceback (most recent call last):
2
File "D:/exercise/test1.py", line 11, in <module>
print(next(new_g))
StopIteration
send()方法
生成器函数最大的特点是可以接受外部传入的一个变量,并根据变量内容计算结果后返回。
例子
def gen():
value = 0
while True:
receive = yield value
if receive == "stop":
break
value = 'got: %s' % receive
g=gen()
print(g.send(None))
print(g.send('aaa'))
print(g.send(3))
print(g.send('stop'))
输出结果
Traceback (most recent call last):
File "D:/exercise/test6.py", line 18, in <module>
print(g.send('stop'))
StopIteration
0
got: aaa
got: 3
分析:
执行过程:
1、首先g.send(None)或者g.next()唤醒生成器,并执行到receive = yield value语句,冻结执行过程,等下一个next()、g.send()、g.close(),此时,执行完了yield语句,但是没有给receive赋值。
注意:在启动生成器函数时只能send(None)或者next(g),如果试图输入其它的值都会得到错误提示信息。
2、通过g.send('aaa'),会传入aaa,并赋值给receive,然后计算出value的值,并回到while头部,执行yield value语句有停止。
此时yield value会输出"got: aaa",然后冻结执行状态。
3、通过g.send(3),会重复第2步,最后输出结果为"got: 3"
4、通过g.send('stop'),传入生成器函数,赋值给receive,执行if receive == "stop":break 退出循环,整个函数执行完毕,最后出现StopIteration异常
throw()
throw()用来向生成器函数送入一个异常,可以结束系统定义的异常,或者自定义的异常。throw()后直接抛出异常并结束程序,或者消耗掉一个yield,或者在没有下一个yield的时候直接进行到程序的结尾。
例子
def gen():
while True:
try:
yield 'normal value'
yield 'normal value 2'
print('here')
except ValueError:
print('we got ValueError here')
except TypeError:
break
g=gen()
print(next(g))
print(g.throw(ValueError))
print(next(g))
print(next(g))
print(next(g))
print(g.throw(TypeError))
输出结果
normal value
we got ValueError here
normal value
normal value 2
here
normal value
normal value 2
Traceback (most recent call last):
File "D:/exercise/test6.py", line 22, in <module>
print(g.throw(TypeError))
StopIteration
分析:
执行过程
1、通过print(next(g))唤醒生成器,执行到yield 'normal value',返回"normal value"被输出,冻结生成器函数执行过程
2、执行print(g.throw(ValueError))语句,出现了ValueError,这里直接执行了except ValueError下的内容,循环继续,返回try语句,执行了yield 'normal value',这里又输出了一个“normal value”,之后冻结执行过程
3、执行了print(next(g)),函数里执行yield 'normal value 2'语句,输出“normal value 2”,冻结执行过程,
4、执行了print(next(g)),函数里开始继续执行,输出“here”,之后调到开始执行yield 'normal value',输出“normal value”,冻结执行过程
5、执行了print(next(g)),函数里执行yield 'normal value 2',输出“normal value 2”,冻结执行过程
6、通过print(g.throw(TypeError)),跳出try语句,出现TypeError,直接执行except TypeError:
跳出while循环,到达生成器函数结尾,所以抛出StopIteration异常。