ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

生成器、迭代器、可迭代对象

2020-02-24 20:59:28  阅读:215  来源: 互联网

标签:... 迭代 对象 生成器 send next isinstance


1. 生成器

1.1 生成器创建方式1

1.2 生成器创建方式2

1.3 生成器总结

2. 迭代器

3. 可迭代对象

4. 总结

 

 

1. 生成器介绍

通过列表生成式,我们可以直接创建一个列表。但由于受到内存限制,列表容量肯定是有限的。并且,如果创建一个包含了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 生成器总结

生成器是这样的一种函数,它记住上一次返回(调用)时所在函数体中的位置,而上次调用时的所有局部变量都保持不变。

生成器不仅记住了它的数据状态,还记住了它在流控制构造(在命令式编程中,这种构造不只是数据值)中的位置。

生成器的特点:

  • 节约内存。
  • 迭代下一次调用时,函数内部所使用的参数都是从第一次调用时就开始保留下来的,而不是新创建的。

 

2. 迭代器

可以被 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. 可迭代对象

可以直接作用于 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() 函数变成一个迭代器对象。

 

标签:...,迭代,对象,生成器,send,next,isinstance
来源: https://www.cnblogs.com/juno3550/p/12358709.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有