迭代器和生成器
https://taizilongxu.gitbooks.io/stackoverflow-about-python/content/1/README.html
Stack Overflow上面关于这个问题非常好的一个回答的中文版。
先说结论:
迭代器一定是可迭代的,但是可迭代的不一定是迭代器。
生成器就是一种迭代器。迭代器有一个特点就是可以被next()
函数调用并不断返回下一个值的对象,并且只能迭代它们一次。原因很简单,因为它们不是全部存在内存里,它们只在要调用的时候在内存里生成。
定义generator(生成器)的另一种方法。如果一个函数定义中包含yield(相当于其他函数的return)
关键字,那么这个函数就不再是一个普通函数,而是一个generator。
而可迭代对象指的是所有可以用在for...in...
语句中的都是可迭代对象:Iterable,
比如lists,strings,files,dict...因为这些可迭代的对象你可以随意的读取所以非常方便易用,但是你必须把它们的值放到内存里,当它们有很多值时就会消耗太多的内存。
但list
、dict
、str
虽然是Iterable
,却不是Iterator
。把list
、dict
、str
等Iterable
变成Iterator
可以使用iter()
函数。
为什么list
、dict
、str
等数据类型不是Iterator
?
这是因为Python的Iterator
对象表示的是一个数据流,Iterator对象可以被next()
函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration
错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()
函数实现按需计算下一个数据,所以Iterator
的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator
甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
这里有个关于生成器的创建问题面试官有考: 问: 将列表生成式中[]改成() 之后数据结构是否改变? 答案:是,从列表变为生成器
>>> L = [x*x for x in range(10)] >>> L [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] >>> g = (x*x for x in range(10)) >>> g <generator object <genexpr> at 0x0000028F8B774200>
通过列表生成式,可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含百万元素的列表,不仅是占用很大的内存空间,如:我们只需要访问前面的几个元素,后面大部分元素所占的空间都是浪费的。因此,没有必要创建完整的列表(节省大量内存空间)。在Python中,我们可以采用生成器:边循环,边计算的机制—>generator
以下名词解释来自廖老师的博客:
迭代和迭代器:
迭代:如果给定一个list或tuple,我们可以通过for
循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)。
在Python中,迭代是通过for ... in
来完成的,而很多语言比如C或者Java,迭代list是通过下标完成的。如下所示java代码:
Python的for
循环不仅可以用在list或tuple上,还可以作用在其他可迭代对象上。list这种数据类型虽然有下标,但很多其他数据类型是没有下标的,但是,只要是可迭代对象,无论有无下标,都可以迭代。
因为dict的存储不是按照list的方式顺序排列,所以,迭代出的结果顺序很可能不一样。
默认情况下,dict迭代的是key。如果要迭代value,可以用for value in d.itervalues()
,如果要同时迭代key和value,可以用for k, v in d.iteritems()
。
当我们使用for
循环时,只要作用于一个可迭代对象,for
循环就可以正常运行,而我们不太关心该对象究竟是list还是其他数据类型。
如何判断一个对象是可迭代对象呢?方法是通过collections模块的Iterable类型判断:(这里有用到python的自省)
如果要对list实现类似Java那样的下标循环怎么办?Python内置的enumerate
函数可以把一个list变成索引-元素对,这样就可以在for
循环中同时迭代索引和元素本身:
迭代器:
可以被next()
函数调用并不断返回下一个值的对象称为迭代器:Iterator
。
可以使用isinstance()
判断一个对象是否是Iterator
对象:
列表生成式:
在原 python 中各种数据结构的常用函数这篇博客中也总结过,再熟悉一下:
生成器:
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器(Generator)。
第二种方法是用函数生成一个generator。
如实现斐波那契数列,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, ...
generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()
的时候执行,遇到yield
语句返回,再次执行时从上次返回的yield
语句处继续执行。
当你调用函数的时候,函数里的代码并没有运行.函数仅仅返回生成器对象。每当for
语句迭代生成器的时候你的代码才会运转。
当for
语句第一次调用函数里返回的生成器对象,函数里的代码就开始运作,直到碰到yield
,中断函数,然后会返回本次循环的第一个返回值.所以下一次调用也将运行一次循环然后返回下一个值,直到没有值可以返回.
一旦函数运行并没有碰到yeild
语句就认为生成器已经为空了.原因有可能是循环结束或者没有满足if/else
之类的。
def generate():
print 'begin'
print 'step 1'
yield 1
print 'step 2'
yield 3
print 'step 3'
yield 5
print 'step 4'
yield 7
print 'step 5'
yield 9
print 'over'
if __name__ == "__main__":
ge = generate()
print ge.next()
print ge.next()
Stack Overflow上的一段代码分享:
用下面的代码可以控制输出限定次数的生成器中的值,使用close()函数可以关闭生成器,关闭后再访问生成器时,会StopIteration异常。
my = (x * x for x in range(10))
l = [my.next() for i in range(3)]
my.close()
print l
小结:
面试中经常会问到迭代器和生成器的区别,很基本的一块东西,需要好好理解。
来源:CSDN
作者:婶婶world_peace
链接:https://blog.csdn.net/baidu_33718858/article/details/82978348