【Python】 迭代器&生成器
迭代器
任何一个类,只要其实现了__iter__方法,就算是一个可迭代对象。可迭代对象的__iter__方法返回的对象是迭代器,迭代器类需要实现next方法。一般来说,实现了__iter__方法的类肯定还会顺便实现next方法,也就是说这个类既是一个可迭代对象也是个迭代器。
一个迭代器ite可用ite.next()方法来返回其定义好的以某种算法找到的下一个元素,内建的iter(...)函数可把可迭代对象转化为迭代器。最常见的利用可迭代对象和迭代器的就是for语句了:
for item in iterable这句话,首先调用了iterable这个对象的__iter__方法,返回一个迭代器(which在很多情况下就是iterable对象本身,就像上面说的,一个可迭代对象的类趋向于实现next让它成为自身的迭代器)。根据迭代器的next方法的算法一个接一个的提供值,把这个值赋给item,然后让item去进行循环体的操作。
如果要自定义一个迭代器or可迭代对象的类,可以这样来:
class test(object):
def __init__(self,num=10):
self.num = num
self.li = range(num)
self.count = 0 def __iter__(self): #一般做法就是让__iter__直接返回自身就好了
return self def next(self):
try:
if self.count == 0:
return self.li[0]
elif self.count <= self.num-1:
return self.li[self.count]
else:
raise StopIteration #调用迭代器的语句并不知道迭代什么时候才能完成,其往往是按照next给出的逻辑一直迭代下去直到出错。
#想让迭代器在某个边际下停止继续迭代的话,就需要在合适的条件时raise StopIteration来停止迭代。
#这个raise出来的并不是会输出到stderr的异常或错误,而是让调用迭代器的语句知道,可以不用再调用next方法来获取下一个值了
finally:
self.count+=2 #这里还有一个比较tricky的,和迭代器本身没啥关系的点。。之前我在写count+=2的时候发现,无论写哪里都不太好。
#因为按照现有语句的逻辑,最好是在next方法返回值后count能再加一次2,这样下次进next万一超过了num-1的限制就可以不用再return。
# 但是一般而言,return之后的语句在本次调用函数的过程中时肯定不会再被执行了,除了try/finally结构。
# 就是说,finally中的语句,即便是在return执行了之后还是会被执行的。这点比较难想到! if __name__ == '__main__':
test = test()
for item in test:
print item
生成器
任何带有yield语句的函数都是生成器。yield通常用在这样一个场景:在(多重)循环中,每遍历出一个单位,不立刻对其做处理,而是统一整合在一起再来处理。生成器是一种创建起来较为简单的迭代器。
从流程上来说,相比于传统的res=[]然后循环地res.append这样的做法,生成器的特点是每次执行到yield语句就会暂停执行并将当前函数执行的上下文保存下来,然后先给出一个暂时的、局部的结果,不继续往下执行。比如yield出现在一个循环里的话不马上进行下一轮循环。那么下轮循环什么时候才会做?要等到外部调用这个生成器的next方法时执行。通过下面这个闭包的例子可以比较好地说明yield语句的这种性质:
def main():
flist = []
for i in range(3):
def foo(x):
print x + i
flist.append(foo) for func in main():
func(2)
这段代码的结果是什么?可能我们会下意识的以为是2,3,4。但是由于python函数的“后期绑定”特点(详情可看闭包那篇),foo中的i的具体的值并不是在函数定义的过程中确认,而是在foo被调用的时候才被最终确认。所以,第一次foo被调用的时候,此时的i已经是2了,所以最终结果是4,4,4。而如果是用生成器的机制,那么可以将这段代码改正:
def main():
for i in range(3):
def foo(x):
print x+i
yield foo for func in main():
func(2)
由于main()是一个生成器,第一次执行到yield foo的时候,i确实还只是0,此时并不直接往下执行,而是暂停了运行并保存运行上下文。此时通过func(2)调用foo函数,i是0,所以可以得到正确结果。然后for func in main()进入下一轮循环,相当于调用了main()生成器的next方法,于是生成器激活了之前保存着的上下文继续运行,此时i得以加一,又开始一个类似的流程。最终,通过生成器搞出来的这个是可以生成2,3,4这样符合预期的结果的。
调用具有生成器性质的函数后,返回的是一个可迭代对象(一般函数有return语句的话返回的就是一个实在的东西)。所以可以对他进行for item in 生成器 的操作。
比如如下示例:
def flatten(nested_thing):
for element in netsed_thing:
if not isinstance(element,list):
yield element
else:
for subelement in flatten(element):
yield subelement nested = [[1,2],[3,4],5,[[6,7],8]]
result = list(flatten(nested))
print result #结果是[1,2,3,4,5,6,7,8]
这个函数的功能是把很多重嵌套的列表展开铺平成一个列表。可以看到flatten函数是一个生成器,返回一个可迭代对象(所以可以在递归那里有for subelement in flatten(element))。从这个示例中我们还可以看出,在递归中,yield掉的东西是不分递归层级统一放置的,所以在第一层级yield到的5,和第二层级(一次递归)yield到的1234等,以及第三层级(二次递归)yield到的东西全部都放到了同一个可迭代对象里面了。
■ 生成器的send方法
自Python2.5版本之后,生成器拥有了send方法。
上面用了很多的yield语句,但是似乎没有明确,yield语句本身返回的内容是什么。潜意识中,我们将yield语句等同于return语句,而执行到return语句的时候就会结束函数执行,因此无所谓return语句会返回什么内容。然而yield语句并不完全这样。send方法就是在某个合适的时候手工指定生成器中yield语句的返回值,并要求生成器继续执行(相当于调用next方法)。这样就可以使得生成器从原先只能通过next读取其内容的“只读”模式变成了next和send结合操作生成器的“读写”模式。另外,在下面的代码中我们可以看到,其实加上send之后,凸显出了生成器具有一定的协程的性质。
下面是示例代码:
def main():
strValue = None
for i in range(10):
if strValue:
print strValue.format(i)
strValue = yield i if __name__ == '__main__':
numGen = main()
for num in numGen:
print num
if num % 2 == 0:
numGen.send('next is odd number: {}')
main()是一个比较简单的生成器。在__main__中,我们首先将main()这个生成器对象叫做numGen方便后面进行基于生成器的操作。main()被调用后首先yield到的i是0,由于此前的strValue并没有值,所以不输出,然后生成器执行暂停,返回上层。在上层中接下来我们通过for num in numGen开始进入循环,首先第一个num是0,所以打印出0后,还要进入if条件块。在这里就用到了生成器的send方法。调用send方法,第一步是将send参数中的字符串作为上次暂停时yield语句的返回值写到生成器中。其次就是要求生成器继续执行,相当于调用了next方法。结合这两点,因此send之后我们再次回到生成器中,strValue被赋予了一个字符串值。然后生成器继续执行,进入下一个循环。此时i变成了1,而strValue通过format方法的内容填充之后,打印出了next is odd number: 1。这样一个字符串。下面,又要yield,因此生成器再次暂停,回到上层。此时注意,回到上层的地方,是在numGen.send方法之后。如果send后面还有一些其他处理,那么要继续执行。这里没有,直接进入for循环的下一轮。进入循环下轮,其实又执行了一次生成器的next方法。我们再次进入了生成器。此时由于不是send方法要求的继续执行,所以strValue被重新赋值为None。生成器中循环进入下轮,i变成2,if因为条件不符合直接跳过,所以接下来又是yield……如此周而复始。最终获得到的stdout的输出是这样的:
0
next is odd number: 1
2
next is odd number: 3
4
next is odd number: 5
6
next is odd number: 7
8
next is odd number: 9
如果读懂了上面一大堆文字描述,那么其实可以感觉得到,生成器的代码和外层调用生成器的“主函数”,两者形成了一个协程关系、我不执行的时候你继续执行,你不执行的时候我继续执行。另外可以注意到,在这段代码中,要求生成器调用next获取下一个值的入口可以有两个,一个是之前常用的for...in...循环,另一个就是send方法。一个比较有趣的现象是,如果将生成器中的for i in range(10)中10改成一个奇数,比如9,会导致报错StopIteration。我觉得这主要是因为迭代器的next方法会在所有可用元素都被使用但再次被调用的时候raise起StopIteration这个异常。而for...in...形式的调用的next对这个错误进行了处理。但是send并没有,因此会导致假如调用next的发起方刚好是send的时候这个错误被抛出。解决办法也很简单,就是在send外面包一层错误处理,当碰到StopIteration的时候直接break出for num in numGen这个循环。
■ 关于递归中的yield
自己编写带有yield的递归函数时,很容易走进这么一个坑。还是以上面的函数为例,我们很可能会写成:
def flatten(nested):
for element in nested:
if not isinstance(element,list):
yield element
else:
flatten(element) # 注意这里
这么写很明显,是期望在递归调用flatten函数的过程中,下层执行时的yield也能将结果收割出来到上层。但是生成器的结果是暂存在本层级函数调用栈中的(https://www.cnblogs.com/coder2012/p/4990834.html这篇是从Python源码角度分析生成器的文章,虽然看不懂,但是值得强行看一看【捂脸】),因此这个函数的最终结果就是只能返回一个去掉了所有非简单元素的列表。比如[5]。
另一方面,上面正确的函数中,将下层生成器中的结果通过一个for/in语句,然后再度yield,相当于将下层生成器中的结果又收割到了本层的调用栈中。自然,最终就可以返回所有的结果了。
【Python】 迭代器&生成器的更多相关文章
- Python迭代器生成器与生成式
Python迭代器生成器与生成式 什么是迭代 迭代是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果.每一次对过程的重复称为一次"迭代",而每一次迭代得到的结果会作为下一次迭 ...
- Python 迭代器&生成器
1.内置参数 Built-in Functions abs() dict() help() min() setattr() all() dir() hex() next() slice ...
- python 迭代器 生成器
迭代器 生成器 一 什么是迭代器协议 1.迭代器协议是指:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代 (只能往后走不能往前 ...
- Python 迭代器&生成器,装饰器,递归,算法基础:二分查找、二维数组转换,正则表达式,作业:计算器开发
本节大纲 迭代器&生成器 装饰器 基本装饰器 多参数装饰器 递归 算法基础:二分查找.二维数组转换 正则表达式 常用模块学习 作业:计算器开发 实现加减乘除及拓号优先级解析 用户输入 1 - ...
- python迭代器,生成器,推导式
可迭代对象 字面意思分析:可以重复的迭代的实实在在的东西. list,dict(keys(),values(),items()),tuple,str,set,range, 文件句柄(待定) 专业角度: ...
- 4.python迭代器生成器装饰器
容器(container) 容器是一种把多个元素组织在一起的数据结构,容器中的元素可以逐个地迭代获取,可以用in, not in关键字判断元素是否包含在容器中.通常这类数据结构把所有的元素存储在内存中 ...
- python迭代器生成器
1.生成器和迭代器.含有yield的特殊函数为生成器.可以被for循环的称之为可以迭代的.而可以通过_next()_调用,并且可以不断返回值的称之为迭代器 2.yield简单的生成器 #迭代器简单的使 ...
- Python迭代器生成器,私有变量及列表字典集合推导式(二)
1 python自省机制 这个是python一大特性,自省就是面向对象的语言所写的程序在运行时,能知道对象的类型,换句话说就是在运行时能获取对象的类型,比如通过 type(),dir(),getatt ...
- Python迭代器生成器,模块和包
1.迭代器和生成器 2.模块和包 1.迭代器 迭代器对象要求支持迭代器协议的对象,在Python中,支持迭代器协议就是实现对象的__iter__()和__next__()方法. 其中__it ...
- python迭代器生成器-迭代器和list区别
迭代 生成 for循环遍历的原理 for循环遍历的原理就是迭代,in后面必须是可迭代对象 为什么要有迭代器 对于序列类型:字符串.列表.元组,我们可以使用索引的方式迭代取出其包含的元素.但对于字典.集 ...
随机推荐
- 一句python,一句R︱python中的字符串操作、中文乱码
先学了R,最近刚刚上手python,所以想着将python和R结合起来互相对比来更好理解python.最好就是一句python,对应写一句R. pandas可谓如雷贯耳,数据处理神器. 以下符号: = ...
- 机器学习中应用到的各种距离介绍(附上Matlab代码)
转载于博客:各种距离 在做分类时常常需要估算不同样本之间的相似性度量(SimilarityMeasurement),这时通常采用的方法就是计算样本间的"距离"(Distance). ...
- 【javascript】jQuery判断用户右击事件
jquery 判断用户是鼠标是右击还是左击, // 1 = 鼠标左键 left; 2 = 鼠标中键; 3 = 鼠标右键 $(document).mousedown(function(e) { if(3 ...
- Java Web项目(Extjs)报错一
1.Java Web(Extjs)项目报错一 usage: java org.apache.catalina.startup.Catalina [ -config {pathname} ] [ -no ...
- linux下insmod lsmod rmmod
insmod(install module) 功能说明:载入模块 install loadable kernel module 语法:insmod [-fkmpsvxX][-o <模块名称> ...
- Duplicate <http> element detected
1.错误描述 org.springframework.beans.factory.parsing.BeanDefinitionParsingException:Configuration pro ...
- TypeError: Error #1034: 强制转换类型失败:无法将 mx.controls::DataGrid@9a7c0a1 转换为 spark.core.IViewport。
1.错误描述 TypeError: Error #1034: 强制转换类型失败:无法将 mx.controls::DataGrid@9aa90a1 转换为 spark.core.IViewport. ...
- Apache Hive 基本理论与安装指南
一.Hive的基本理论 Hive是在HDFS之上的架构,Hive中含有其自身的组件,解释器.编译器.执行器.优化器.解释器用于对脚本进行解释,编译器是对高级语言代码进行编译,执行器是对java代码的执 ...
- MPTCP iperf 发包方式
之前用的发包方式是发送大文件,用NC监测. 今天改了另外一种发包方式iperf,简单记录下. iperf发包,具体方法: 1.在终端中运行拓扑脚本: 运行py脚本:sudo python topy.p ...
- 从AngularJS2谈到前台开发工程化
才刚刚对AngularJS有些了解,又听闻AngularJS2早就铺天盖地了,AngularJS3刚刚夭折,AngularJS4今年已经发布了,还是学习先下AngularJS2吧,据说更加适合以移动为 ...