博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
生成器、迭代器、可迭代对象
阅读量:429 次
发布时间:2019-03-06

本文共 5398 字,大约阅读时间需要 17 分钟。

 

 

1. 生成器(Generator)

通过列表生成式,我们可以直接创建一个列表。但由于受到内存限制,列表容量肯定是有限的。并且,如果创建一个包含了100万个元素的列表,却仅仅需要访问前面几个元素,那么后面绝大多数元素占用的空间都白白浪费了。

所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。

在Python中,这种一边循环一边计算的机制,称为生成器(generator)。

 

1.1 生成器的创建方式1

创建一个生成器,有多种方法。第一种方法很简单,只要把一个列表生成式的 [ ] 改成 ( ) 。

1 >>> l = [x*2 for x in range(5)]2 >>> l3 [0, 2, 4, 6, 8]4 >>> g = (x*2 for x in range(5))5 >>> g6 
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 811 >>> next(g)12 Traceback (most recent call last):13   File "
", line 1, in
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 06 27 48 69 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 112 113 214 315 516 '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 113 >>> next(f)14 115 >>> next(f)16 217 >>> next(f)18 319 >>> next(f)20 521 >>> next(f)22 Traceback (most recent call last):23   File "
", line 1, in
24 StopIteration: done

在上面 fib 的例子,我们在循环过程中不断调用 yield ,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。

同样的,把函数改成 generator 后,我们基本上从来不会用 next() 来获取下一个返回值,而是直接使用 for 循环来迭代:

1 >>> for i in fib(5):2 ...     print(i)3 ...4 15 16 27 38 59 >>>

但是在用 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 ...         break10 ...11 value: 112 value: 113 value: 214 value: 315 value: 516 生成器的返回值为: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)None1>>> next(t)None2>>> t.send("haha")  # send与next一样可以调用 yield i 的结果,并把"haha"传递作为 yield i 整体的返回值,即赋值给了temphaha3>>> next(t)  # 注意,此时并没有为 yield i 传递返回值,因此temp又为NoneNone4

注意第一次调用就使用 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 "
", line 1, in
12 TypeError: can't send non-None value to a just-started generator13 >>> 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 None10         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 
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 True10 >>> isinstance(100, Iterable)11 False

生成器不仅可以作用于 for 循环,还可以被 next() 函数不断调用并返回下一个值,直到最后抛出 StopIteration 错误表示无法继续返回下一个值了。

iter() 函数

生成器都是迭代器对象(Iterator),但 list、tuple 等数据类型虽然是可迭代对象(Iterable),但不是 Iterator。

若想将 list、tuple 等可迭代对象变成迭代器对象,可以使用 iter() 函数:

1 >>> isinstance(iter([]), Iterator)2 True3 >>> isinstance(iter({}), Iterator)4 True5 >>> isinstance(iter("abc"), Iterator)6 True

 

4. 总结

  • 可迭代对象(Iterable):可以直接作用于 for 循环的对象。
  • 迭代器(Iterator):可以作用于 next() 函数的对象。例如生成器(generator)就属于迭代器对象。
  • list、tuple、str 等数据类型虽然是可迭代对象,但不是迭代器,不过可以使用 iter() 函数变成一个迭代器对象。

 

转载地址:http://aubyz.baihongyu.com/

你可能感兴趣的文章