Python中的可迭代对象、迭代器和生成器的异同点

最近在学Python里异步IO中的协程,协程最初是由生成器变形的yield而来,感觉学完理解有些困难,故此再来回顾一下之前学习的生成器。在Python学习之初比较容易混淆可迭代对象、迭代器和生成器。因此,做以总结以辨析其中关键的区别。以下仅为习后个人理解,如有偏差,还请指出!

Talk is cheap,show me the picture!

解释之前,我们先来看一张概览图,下面是一张关于容器(container)、可迭代对象(Iterable)、迭代器(iterator)、生成器(generator)、生成器函数和生成器表达式的概览图。

11
它们之间的从属关系图如下:在这里插入图片描述

一、容器(container)

【概述】:容器就是一个用来存储多个元素的数据结构
【特点】:
①容器中的元素可通过迭代获取。
②所有容器中的元素存储内存中。
【举例】:以下都是常见容器对象,他们都可以通过迭代获取,所以它们也是可迭代对象(Iterable),我们暂且理解可迭代对象就是可以被迭代获取的对象。(注:并非所有容器对象都是可迭代对象)
首先引入如下模块:

from collections import Iterable  # 可迭代对象
from collections import Iterator  # 迭代器

1.列表(list)

title = ['Python','Java','C++']
for i in title:            # 通过for循环,迭代获取
    print(i)               
isinstance(title,Iterable) # True

打印顺序:Python,Java,C++,True
2.元组(tuple)

title = ('Python','Java','C++')
for i in title:            # 通过for循环,迭代获取
    print(i)
isinstance(title,Iterable) # True

打印顺序:同上述列表
3.字典(dict) (注:此处打印的是字典中的key而不是value)

title = {'Python':3,'Java':1.9,'C++':2}
for key in title:            # 通过for循环,迭代获取
    print(key)
isinstance(title,Iterable) # True

打印顺序:同上述列表
4.集合(set)

title = {'Python','Java','C++'}
for i in title:            # 通过for循环,迭代获取
    print(i)
isinstance(title,Iterable) # True

打印顺序:C++,Java,Python,True
5.字符串(str)

str = 'abc'
for i in str:            # 通过for循环,迭代获取
    print(i)
isinstance(str,Iterable) # True

打印顺序:a,b,c,True

二、可迭代对象(Iterable)

【简述】:可迭代对象就是可以被迭代获取对象
【特点】:Iterable定义了可返回迭代器的__iter__()方法
【举例】:

title = ['Python','Java','C++'] # 列表是一个可迭代对象
isinstance(title,Iterable)      # True
a = iter(title) # 由可迭代对象的iter方法返回一个迭代器
>>> next(a)
Python
>>> next(a)
Java
>>> next(a)
C++ 
>>> next(a)  # 抛出StopIteration异常

如果我们写出以下代码:

x = [1, 2, 3]
for elem in x:
    ...

则实际内部运行过程是这样的,列表x是一个可迭代对象,在for循环中经过iter()方法变为迭代器,然后遍历x实际就是内部调用elem = next(x)
在这里插入图片描述

三、迭代器(Iterator)

【简述】:迭代器是一个带状态的对象。之所以说是带状态的对象是因为迭代器内部持有一个状态,该状态用于记录当前迭代所在的位置,以方便下次迭代的时候获取正确的元素。迭代器可以通过next()方法来迭代获取下一个值。
【特点】:
①Iterator实现了__iter__()__next__()方法。
②迭代器不会一次性把所有元素加载到内存,而是需要的时候才生成返回结果
(不同于容器)。
【举例】:和上面例子一样,下面的a就是一个迭代器,和可迭代对象title不同,a可以通过next(a)来逐个获取其中的每个元素。

title = ['Python','Java','C++'] # 列表是一个可迭代对象
isinstance(title,Iterable)      # True
a = iter(title) # 由可迭代对象的iter方法返回一个迭代器
>>> next(a)
Python
>>> next(a)
Java
>>> next(a)
C++ 
>>> next(a)  # 抛出StopIteration异常

如果使用next(title)则会报TypeError错,显示列表对象不是一个迭代器。

>>> next(title)  
TypeError: 'list' object is not an iterator

迭代器每次调用next()方法的时候做两件事:

  1. 为下一次调用next()方法修改状态
  2. 生成当前调用的返回结果

四、生成器(generator)

【概述】:生成器(generator)是一种特殊的迭代器。
【特点】:
①生成器拥有迭代器的迭代传出数据的功能,但用关键字yield替换迭代器中的__next__()方法来实现,而拥有yield关键字的函数就是生成器函数。
②生成器可以传入数据进行计算(不同于迭代器),并根据变量内容计算结果后返回。
③迭代器不会一次性把所有元素加载到内存,而是调用的时候才生成返回结果(相同于迭代器,不同于容器)。
④可以通过for循环进行迭代(因为生成器是迭代器)
综上所述:生成器是迭代器的衍生物,但迭代器不是生成器,因为迭代器没有传入数据功能。

4.1 生成器函数(generator function)

【概述】:含有yield关键字的函数就是生成器函数。
【举例】:
1.生成器可以通过for循环迭代,这一点和迭代器中的next()函数功能相同,如下我们使用一个有限序列作为例子,如果是无限序列,则会无休止的循环下去

def d():
    yield 1
    yield 2
    yield 3
    yield 4
for i in d():
    print(i)
type(d()) # generator
isinstance(d(),Iterator) # True
isinstance(d(),Iterable) # True

得到如下结果:(也可以看出生成器即是迭代器,也是可迭代对象)

1
2
3
4
generator

2.以下我们展示一个传入和传出数据的例子。

def d():
    print('初始化')
    sum = 0
    value = yield sum
    sum = sum + value
    print('sum的值是:%d' % sum)
    value = yield sum
    sum = sum + value
    print('sum的值是:%d' % sum)
    value = yield sum
    sum = sum + value
    print('sum的值是:%d' % sum)
    return sum+1
c = d()          # c是一个生成器,此行代码并不运行d()内容
a = c.send(None) # 启动生成器,遇到d()的第一个yield时中断
print('生成器传出的值:%d' % a)
a = c.send(1)
print('生成器传出的值:%d' % a)
a = c.send(1)
print('生成器传出的值:%d' % a)

yield有中断的功能:

当运行a = c.send(None)时,启动生成器函数,在第一个yield中断,此时这行程序仅仅运行了yield sum并没有开始赋值,而yield sum就相当于return sum,即向函数外传出sum,所以函数外接收值的变量a存储的值是0

当运行a = c.send(1)时,我们继续启动生成器函数开始运行value = yield,并向生成器函数的第一个中断点yield传递了值1,然后通过yield1传递给了value并通过后续计算累加sum。程序直到第二个yield中断,向函数外返回第二个sum。以此类推。

所以执行结果如下:

初始化
生成器传出的值:0
sum的值是:1
生成器传出的值:1
sum的值是:2
生成器传出的值:2

【注】:
1)第一个send(None)填入的参数必须是None,因为在启动生成器函数到第一次中断,程序只运行到第一个yield sum,没有赋值语句,所以只能填None
2)对于生成器函数最后的return sum语句并不向函数外传递sum,而是会在迭代结束时报错StopIteration: 3,返回值sum包含在StopIteration的value中,也就是3,可以捕获StopIteration在函数外得到这个值。把上面代码中的a = c.send(None)之后的代码改成如下代码即可。

while True:
	print('生成器传出的值:%d' % a)
    try:  
        a = c.send(1)
    except StopIteration as e:
        print('生成器传出最后的值:%d' % e.value)
        break

如果不加返回值的话直接break就行,没有外循环的话就直接pass

while True:
	print('生成器传出的值:%d' % a)
    try:  
        a = c.send(1)
    except StopIteration:
        break

4.2 生成器表达式(generator expression)

生成器表达式是列表生成式的生成器版本,看起来像列表生成式,但是它返回的是一个生成器对象而不是列表对象。当然,它既然是生成器,也就可以上个例子一样通过send()函数来迭代。

>>> [x*x for x in range(10)] # 列表生成式
[0, 1, 4, 9, 16]
>>> (x*x for x in range(10)) # 生成器表达式
<generator object <genexpr> at 0x401f08>

总结:

对象特点功能
Iterable实现__iter__()方法返回一个Iterator
Iterator①实现__iter__()__next__()方法
②数据需要的时候才存储到内存中
逐个传出数据
generator①通过yield替换迭代器两个方法并有中断功能
②数据需要的时候才存储到内存中
①逐个传出数据
②逐个传入数据
container①容器中的元素可通过迭代获取
②所有容器中的元素被存储在内存中
——

编一则小故事:

在Python中有一种可迭代对象(Iterable),他声称自己可以通过迭代来获取值(如for循环),但怎么迭代他说那是迭代器(Iterator)的事,他只负责把自己变成迭代器。于是当他揽下一个需要迭代的活时,他就通过Iter()把自己变成了迭代器。

另外一个容器兄弟开始呼应:”我就是可迭代对象呢,我包含常见的列表、元组、字典、集合和字符串,我将这些序列存储在内存中,需要的时候可以一并取出“

迭代器有点不屑,开始怼容器说:“你将所有序列都存储在内存中,对于少量有限序列是可以的,那如果是大量序列或无限序列,内存都让给你存岂不是很败家?我是迭代功能的实干家,我可以通过next()进行迭代,但并不是把所有序列放在内存中再迭代取值,而是仅仅将迭代到的某个值取到内存中,做到按需存储。

生成器对迭代器说:“我就是另一个你,但我们不一样。大兄弟你的确是个实干家,但你仅仅只能迭代取出数据,而我除了有你的功能还可以通过send()传入数据,传入的数据可在生成器内进行计算呢。”

最后,生成器和迭代器和容器说:“我们都是可迭代对象,Iterable对于我们来说更像一种特点,表示我们是可迭代的,但是功能的实现是我们自己完成的”
【参考文献】:
[1] Vincent Driessen,Iterables vs. Iterators vs. Generators
[2] liuzhijun,完全理解Python迭代对象、迭代器、生成器
[3] 深入理解Python中的生成器
[4] 廖雪峰.生成器

另,对yield关键字还不理解的,可参考这篇文章:深入理解yield
微信

  • 35
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 13
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SL_World

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值