要完全理解透生成器,需要我们先掌握三个概念:
放一张图来理解,来自这里
额外提到了容器(container),说的是我们的集合类对象,如 list、set、dict,它们将多个元素组织在一起,这些对象就可以称为 container。
可迭代对象:
可直接作用于for循环的对象统称为Iterable 。具体的实现是,Python 中的对象只要定义了__iter__方法(该方法返回一个迭代器对象),或者定义了支持下标索引的__getitem__方法,那么这个对象就是可迭代对象。
>>> from collections import Iterable >>> isinstance([], Iterable) True >>> isinstance({}, Iterable) True >>> isinstance([x for x in range(10)], Iterable) True
迭代器:
可作用于next()函数的对象都是Iterator。具体的实现是,任何对象只要定义了__iter__和__next__方法,那就是迭代器对象;迭代器表示一个惰性计算的序列,需要__iter__返回迭代器自身,__next__返回迭代器中的下一个值,迭代到结尾时引发 StopIteration 异常;也就是说迭代器在遍历集合时,并不是将所有的元素事先都准备好,而是迭代到某个元素时才去计算该元素,利用这一特性我们可以去遍历一些巨大的集合,之前总结的函数式编程中,map,reduce,filter函数返回的就是一个新的迭代器。
还有一点需要明确的,迭代器都是可迭代对象,可迭代对象可以通过iter()返回一个新的迭代器。
>>> L = [1, 2, 3, 4, 5] >>> '__iter__' in dir(L) True >>> '__next__' in dir(L) False >>> newL = iter(L) >>> '__next__' in dir(newL) True >>> newL.__next__() 1 >>> newL.__next__() 2 # 定义斐波拉契数的迭代器 >>> class fib(object): ... def __init__(self): ... self.prev = 0 ... self.curr = 1 ... def __iter__(self): ... return self ... def __next__(self): ... value = self.curr ... self.curr += self.prev ... self.prev = value ... return value ... >>> f = fib() >>> for i in f: ... if i > 20: break ... print(i) ... 1 1 2 3 5 8 13
从上面的迭代操作中,可以看出 for 循环其实是调用__iter__获得迭代器,再调用__next__获取元素,迭代器内部状态保存在当前实例对象的prev以及cur属性中,在下一次调用中将使用这两个属性。每次调用next()方法都会执行以下两步操作:
迭代器的使用非常普通,Python的内置库itertools就是专门返回迭代器对象的,这篇博文专门介绍itertools库的,我从中列举了一些:
# 累加 >>> import itertools >>> a = itertools.accumulate(range(10)) >>> a <itertools.accumulate object at 0x7fb030907388> >>> print(list(a)) [0, 1, 3, 6, 10, 15, 21, 28, 36, 45] # 连接列表或迭代器 >>> c = itertools.chain(range(3), range(4), [0, 1, 2, 3, 4]) >>> print(list(c)) [0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4] # 按照真值表筛选元素 >>> x = itertools.compress(range(5), (True, False, True, True, False)) >>> print(list(x)) [0, 2, 3] # 计数器,可以指定起始位置和步长 >>> x = itertools.count(start=20, step=-1) >>> print(list(itertools.islice(x, 0, 10, 1))) [20, 19, 18, 17, 16, 15, 14, 13, 12, 11] # 按照分组函数的值对元素进行分组 >>> x = itertools.groupby(range(10), lambda x: x < 5 or x > 8) >>> for condition, numbers in x: ... print(condition, list(numbers)) True [0, 1, 2, 3, 4] False [5, 6, 7, 8] True [9] # 类似map >>> x = itertools.starmap(str.islower, 'aBCDefGhI') >>> print(list(x)) [True, False, False, False, True, True, False, True, False]
生成器:
有了前面的铺垫,我们就能更好地理解生成器了。生成器是什么?说白了生成器就是一种特殊的迭代器,不过它的实现方式更为简单优雅,同样我们可以明确的是,任何生成器都是迭代器,生成器也是一个惰性计算的序列。
我们来看看生成器的两种定义方式:
1、生成器表达式:
>>> [i * i for i in range(5)] # 注意 Python3 中 range函数是迭代器 [0, 1, 4, 9, 16] # 根据列表生成式,只需要简单修改就可以定义生成器 >>> (i * i for i in range(3)) <generator object <genexpr> at 0x7f59ed8fc3b8>
2、另一种定义复杂推导算法的生成器需要引入一个强大的关键字yield:
# 斐波那契序列的生成器函数 >>> def fib(): ... prev = curr = 1 ... yield prev #1 ... yield curr #2 ... while True: ... prev, curr = curr, prev + curr ... yield curr ... >>> f = fib() >>> f <generator object fib at 0x7f85cca9d410> >>> for i in f: # 还可以使用 next() 遍历生成器 ... if i > 20: break ... print(i) ... 1 1 2 3 5 8 13
分析一下流程:
对比迭代器和生成器,实现同样的功能,生成器会显得更加优雅简洁。
迭代
一句话总结迭代:按照一定的顺序逐个访问容器中每一个元素的过程;也就是我们折腾斐波那契序列的过程
限于篇幅,生成器就介绍到这里,但生成器的威力远不止此,下一篇将通过生成器和 yield 引出协程和异步IO等。