Python之路迭代器协议、for循环机制、三元运算、列表解析式、生成器

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异常。

上一篇:Python入门篇-解析式、生成器


下一篇:[C#] 一款代码注释清理工具