Python 生成器、迭代器、可迭代对象
1. 生成器(Generator)
2. 迭代器(Iterator)
3. 可迭代对象(Iterable)
4. 总结
1. 生成器(Generator)
通过列表生成式,我们可以直接创建一个列表。但由于受到内存限制,列表容量肯定是有限的。并且,如果创建一个包含了100万个元素的列表,却仅仅需要访问前面几个元素,那么后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。
在Python中,这种一边循环一边计算的机制,称为生成器(generator)。
1.1 生成器的创建方式1
创建一个生成器,有多种方法。第一种方法很简单,只要把一个列表生成式的 [ ] 改成 ( ) 。
1 >>> l = [x*2 for x in range(5)] 2 >>> l 3 [0, 2, 4, 6, 8] 4 >>> g = (x*2 for x in range(5)) 5 >>> g 6 <generator object <genexpr> at 0x0313F6B8>
我们可以直接打印出列表的每一个元素,但我们怎么打印出生成器的每一个元素呢?如果要一个一个打印出来,可以通过 next() 函数获得生成器的下一个返回值:
1 >>> next(g) # 也可以使用 g.__next__() 2 0 3 >>> next(g) 4 2 5 >>> next(g) 6 4 7 >>> next(g) 8 6 9 >>> next(g) 10 8 11 >>> next(g) 12 Traceback (most recent call last): 13 File "<stdin>", line 1, in <module> 14 StopIteration
生成器保存的是算法,每次调用 next(G) ,就计算出 G 的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出 StopIteration 的异常。
当然,这种不断调用 next() 实在是太变态了,正确的方法是使用 for 循环,因为生成器也是可迭代对象。
所以,我们创建了一个生成器后,基本上永远不会调用 next() ,而是通过 for 循环来迭代它,并且不需要关心 StopIteration 异常。
1 >>> g = (x*2 for x in range(5)) 2 >>> for i in g: 3 ... print(i) 4 ... 5 0 6 2 7 4 8 6 9 8
1.2 生成器的创建方式2
generator 非常强大。如果推算的算法比较复杂,用类似列表生成式的 for 循环无法实现的时候,还可以用函数来实现。
比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, ...
斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:
1 >>> def fib(times): 2 ... n = 0 3 ... a, b = 0, 1 4 ... while n < times: 5 ... print(b) 6 ... a, b = b, a+b 7 ... n += 1 8 ... return "done" 9 ... 10 >>> fib(5) 11 1 12 1 13 2 14 3 15 5 16 'done'
仔细观察,可以看出,fib() 函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似 generator。
也就是说,上面的函数和 generator 仅一步之遥。要把 fib() 函数变成 generator,只需要把 print(b) 改为 yield b 就可以了:
1 >>> def fib(times): 2 ... n = 0 3 ... a, b = 0, 1 4 ... while n < times: 5 ... yield b 6 ... a, b = b, a+b 7 ... n += 1 8 ... return "done" 9 ... 10 >>> f = fib(5) 11 >>> next(f) 12 1 13 >>> next(f) 14 1 15 >>> next(f) 16 2 17 >>> next(f) 18 3 19 >>> next(f) 20 5 21 >>> next(f) 22 Traceback (most recent call last): 23 File "<stdin>", line 1, in <module> 24 StopIteration: done
在上面 fib 的例子,我们在循环过程中不断调用 yield ,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。
同样的,把函数改成 generator 后,我们基本上从来不会用 next() 来获取下一个返回值,而是直接使用 for 循环来迭代:
1 >>> for i in fib(5): 2 ... print(i) 3 ... 4 1 5 1 6 2 7 3 8 5 9 >>>
但是在用 for 循环调用 generator 时,发现拿不到 generator 的 return 语句的返回值。如果想要拿到返回值,必须捕获 StopIteration 错误,返回值包含在 StopIteration 的 value 中:
1 >>> g = fib(5) 2 >>> 3 >>> while True: 4 ... try: 5 ... x = next(g) 6 ... print("value: {}".format(x)) 7 ... except StopIteration as msg: 8 ... print("生成器的返回值为:{}".format(msg.value)) 9 ... break 10 ... 11 value: 1 12 value: 1 13 value: 2 14 value: 3 15 value: 5 16 生成器的返回值为:done
send() 方法
send 方法与 next 方法一样可以调用 yield 的结果,并把 send() 中的参数传递作为 yield 整体的返回值。
>>> def test(): ... i = 0 ... while i < 5: ... temp = yield i ... print(temp) ... i += 1 ... >>> t = test() >>> next(t) 0 >>> next(t) None 1 >>> next(t) None 2 >>> t.send("haha") # send与next一样可以调用 yield i 的结果,并把"haha"传递作为 yield i 整体的返回值,即赋值给了temp haha 3 >>> next(t) # 注意,此时并没有为 yield i 传递返回值,因此temp又为None None 4
注意第一次调用就使用 send() 时 :
1 >>> def test(): 2 ... i = 0 3 ... while i < 5: 4 ... temp = yield i 5 ... print(temp) 6 ... i += 1 7 ... 8 >>> t = test() 9 >>> t.send("haha") # 由于先计算=号右边的表达式,并且调取完结果后会进行停止,无法将"haha"传递赋值给=号左边的temp,因此报错 10 Traceback (most recent call last): 11 File "<stdin>", line 1, in <module> 12 TypeError: can't send non-None value to a just-started generator 13 >>> t.send(None) # 若要第一次调用就使用send,那么就传递None。send(None)等价于next() 14 0
1.3 生成器的特点总结
生成器是这样的一种函数,它记住上一次返回(调用)时所在函数体中的位置,而上次调用时的所有局部变量都保持不变。
生成器不仅记住了它的数据状态,还记住了它在流程控制构造(在命令式编程中,这种构造不只是数据值)中的位置。
生成器的特点:
- 节约内存。
- 迭代下一次调用时,函数内部所使用的参数都是从第一次调用时就开始保留下来的,而不是新创建的。
1.4 生成器的应用场景
使用生成器完成多任务(即协程):
1 def test1(): 2 while 1: 3 print("----1----") 4 yield None 5 6 def test2(): 7 while 1: 8 print("----2----") 9 yield None 10 11 t1 = test1() 12 t2 = test2() 13 14 while 1: 15 next(t1) 16 next(t2)
执行结果:
----1----
----2----
----1----
----2----
----1----
----2----
……
2. 迭代器(Iterator)
可以被 next() 函数调用并不断返回下一个值的对象,统称为迭代器(Iterator)。迭代器是一个可以记住遍历的位置的对象。
迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完,迭代器只能往前访问,不能退后访问。
判断是否是迭代器对象
1 >>> from collections import Iterator 2 >>> g = (x for x in range(5)) 3 >>> type(g) 4 <class 'generator'> 5 >>> isinstance(g, Iterator) # 生成器是迭代器对象 6 True 7 >>> isinstance([], Iterator) 8 False 9 >>> isinstance("abc", Iterator) 10 False
3. 可迭代对象(Iterable)
可以直接作用于 for 循环的对象统称为可迭代对象(Iterable)。大致有以下两种:
- 一种是如 list、tuple、dict、set、str 等数据类型。
- 一种是生成器(两种创建方式均是)。
判断是否是可迭代对象
1 >>> from collections import Iterable
2 >>> isinstance([], Iterable)
3 True
4 >>> isinstance({}, Iterable)
5 True
6 >>> isinstance("abc", Iterable)
7 True
8 >>> isinstance((x for x in range(5)), Iterable)
9 True
10 >>> isinstance(100, Iterable)
11 False
生成器不仅可以作用于 for 循环,还可以被 next() 函数不断调用并返回下一个值,直到最后抛出 StopIteration 错误表示无法继续返回下一个值了。
iter() 函数
生成器都是迭代器对象(Iterator),但 list、tuple 等数据类型虽然是可迭代对象(Iterable),但不是 Iterator。
若想将 list、tuple 等可迭代对象变成迭代器对象,可以使用 iter() 函数:
1 >>> isinstance(iter([]), Iterator) 2 True 3 >>> isinstance(iter({}), Iterator) 4 True 5 >>> isinstance(iter("abc"), Iterator) 6 True
4. 总结
- 可迭代对象(Iterable):可以直接作用于 for 循环的对象。
- 迭代器(Iterator):可以作用于 next() 函数的对象。例如生成器(generator)就属于迭代器对象。
- list、tuple、str 等数据类型虽然是可迭代对象,但不是迭代器,不过可以使用 iter() 函数变成一个迭代器对象。