学习python的迭代器与生成器的时候,需要了解容器(Container)、可迭代对象(iterable)、迭代器(iterator)和生成器(generator),这样就可以知道一些数据类型为什么可以被for循环,自己如何写出可以迭代的方法等。
0x00 可迭代对象与迭代器的发现
通过for循环可以很方便的遍历一些字符串、列表、元祖及字典等,而当你遍历一个int类型的时候,IDE会报出一个TypeError: ‘int’ object is not iterable的错误,告诉你这不是一个可迭代对象,因而无法遍历它。
通过dir([])(以列表为例),可以查看列表的“双下方法”。其中__iter__表示其为一个可迭代对象,通过该方法可以生成一个迭代器。
#查看列表的双下方法 dir_list = dir([]) #生成列表的迭代器 g = dir([]).__iter__() #查看迭代器的方法 iter_list = dir(g) #查看迭代器与可迭代对象的双下方法交集 print(set(iter_list) - set(dir_list)) ''' {'__length_hint__', '__next__', '__setstate__'} '''
可以看见迭代器比可迭代对象多了那么几个方法,其中最重要的就是__next__。
- 只要含有__iter__方法的都是可迭代对象——可迭代协议
- 内部含有__iter__和__next__方法的就是迭代器——迭代器协议
因而可迭代对象可以变遍历,被for循环。其中迭代器最大的有点,不仅仅是方便遍历,并且可以聪明的处理数据,比较省内存。
0x01 生成器函数与生成器
生成器函数是生成生成器的方法之一。其主要表现在函数中有关键件“yield”。
def generator(): print(1) yield 'a' print(2) yield 'b' print(3) g = generator() ret = g.__next__() print(ret) ret = g.__next__() print(ret) ret = g.__next__() print(ret)
其执行结果为1 a 2 b 3 StopIteration。
其执行顺序(数字为行号)为:1 8 9 2 3 10 4 5 13 6 抱错。每次__next__后就执行到yield,之后就跳出函数,出来执行外部函数,直到执行下一次__next__,又跳入函数,执行到下一个yield,知道最后没有找到yield,因而抱错StopIteration。
生成器函数并不能直接遍历,需要先生成一个生成器(8),之后才可以用for去遍历生成器。
Ps.生成器可以通过list()强制转换为列表,当然没有什么实质性的用,并且还会浪费内存。
0x02 生成器的进阶使用
迭代器的方法__next__,其还有一个类似方法__send__,不过__send__传入一个参数。
def generator(): print(1) content = yield 'a' print(content,2) yield 'b' print(3) yield 'c' g = generator() ret = g.__next__() print(ret) ret = g.send('Hello') print(ret) ret = g.__next__() print(ret) ''' 1 a Hello 2 b 3 c '''
当代码执行到第一个g.__next__()的时候,跳入生成器函数执行到yield ‘a’,由于先只想yield ‘a’就跳出了函数,=的复制因而无法完成就跳出函数执行外部函数代码;执行到g.send(‘Hello’)的时候,给上一个yield的位置传入参数’hello’之后赋值给content,再执行代码到yield ‘b’。
其__send__的作用为在获得下一个值的同时,给上一个yield的位置传递一个数据。
0x03 生成器表达式
生成器的产生出了可以通过生成器函数生成外,还可以通过生成器表达式生成。
在学习列表的时候,可以通过列表解析来生成一个列表。如下:
list = [i for i in range(1,101) if i%2 == 0]
这样就可以生成一个100以内的偶数列表,也可以使用近乎同样的方法生成一个生成器表达式,仅需将[]改为()。
generator = (i for i in range(1,101) if i%2 == 0)
这样就生成一个生成器。
0x04 生成器与生成器表达式的运行流程
a.求输入数字的移动平均值。
def wrapper(func): def inner(): g = func() g.__next__() return g return inner @wrapper def average(): sum = 0 counter = 0 avg = 0 while True: num = yield avg sum += num counter += 1 avg = sum/counter g = average() while True: num = int(input('>>>')) print(g.send(num))
该例子,由于第一次yield返回值为无效值(第一次yield值无法传入send()中的数据,所以第一次为无效返回)。因而先需要__next__()一次,因而将该过程丢入到了装饰器中,并预激生成器的装饰器,这样用户在输入时候就自动执行了一次__next__()忽略了无效值。
b.装饰器的执行流程1
def demo(): for i in range(4): yield i g=demo() g1=(i for i in g) g2=(i for i in g1) print(list(g1)) print(list(g2)) ''' [0, 1, 2, 3] [] '''
在考虑执行顺序的时候,一定要清楚迭代器只能用一次,用完后之后在调用,该生成器就为空了,所以即使10行11行代码互换,执行结果也一样的。生成器表达式与生成器函数一样,不取值的时候,不会执行函数或者表达式中的代码。
c.装饰器的执行流程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) print(list(g)) ''' [20, 21, 22, 23] '''
主体思路和上一个代码的考虑方式一样,不过这段代码中,循环较为复杂,可以将最后的for循环进行代码优化后再看。
n = 1 g = (add(n,i) for i in g) n = 10 g = (add(n,i) for i in g)
代码还可以改为:
n = 10 g=(add(n,i) for i in (add(n,i) for i in g))
这样就可以算出最终结果了。
博主,博客文章不错,想要一下网站的rss地址,是否能提供一下?
谢谢支持!!rss地址:https://www.xzymoe.com/feed/