来源:本人博客javascript
迭代器和生成器可能对于一些人来讲知道是什么东东,可是并无比较深刻的了解,那么今天,就跟随我来了解一下这二者的概念,关系及优势,我将使用python中的迭代器和生成器做为演示,若是你不懂python不要紧,明白了概念,剩下的就只是编程语言的差别了!这一点很关键,再啰嗦一句,不要为了编程而编程,也要明白一些概念性的东西,编程语言只是工具!html
想必你们在学习编程的时候,确定学到过for循环,while循环,do...while循环等等,那么咱们为何须要循环操做呢?由于有些时候咱们但愿计算机为咱们重复的执行一样的操做,好比我有一个“数组”,里面存储了100个同窗的id,那么我则会对这个数组进行循环操做,而后挨个输出。固然还有不少其余地方须要循环操做,这里我只是举个例子。java
因此,循环操做是计算机编程语言中必不可少的组成部分,那么请你们用几秒钟时间回想一下,咱们以前曾经写过的循环操做for循环,while循环。咱们每每须要初始化一个变量i,还得声明一个条件好比i<100,而后循环完每一步以后作什么,好比(下方伪代码):python
for(i = 0; i < 100; i++) { }
咱们能够很容易的用这种循环来遍历一个数组,但愿你们学过数据结构,由于数组在内存中的存储是连续的!咱们能够经过数组的“下标”(实际上是相对于数组第一个元素的位置)来进行访问数组中的元素,因此在不少时候,咱们经过for循环来遍历数组(下方伪代码):编程
for(i = 0; i < arrLength; i++) { }
那么若是我如今问你,你怎么进行遍历一个没有在内存中连续存储的“数据结构”呢,好比python中的“字典”,javascript中的”对象“,又好比你本身写了一个”树“结构的类,想遍历整个树的节点?那么传统的for循环,while循环就没法发挥他们的做用了,这个时候咱们就应该引入”迭代器“了。数组
因此,小结一下,”迭代器“其实目的也是为了”循环“,更严谨一些,是为了“遍历”,你能够把迭代器当作比普通循环更高级别的工具,普通循环能搞定的迭代器也能搞定,普通循环搞不定的迭代器还能搞定,而且使用迭代器比普通循环效率更高,这个咱们后面说到生成器的时候会提到。数据结构
我想大多数人可能和我同样,刚开始对这些概念/名词都很模糊,那么让咱们一块儿弄明白他们。app
你们先要知道“协议”(protocol)的意思,其实协议是用来“规范/标准化”你“创造的东西”的。好比,你开天辟地的发明了一种东西叫作“吧啦哔哩”,你给小明说:“小明,给我发一个吧啦哔哩过来”,若是小明不知道啥叫“吧啦哔哩”,那么小明会直接懵逼的。这时候你就要定一个“协议”以下:编程语言
1, "吧啦哔哩"一共有10个字
2, "吧啦哔哩"开头和结尾都是"#"号 (占两个字)
3, "吧啦哔哩"最后四位是"blbl"
4, 其余随便函数
那么咱们根据这个协议,能够很轻易的构造出“吧啦哔哩”来:#1234blbl# 或者 #8888blbl#
一样,咱们根据这份协议,就能够用来检测你获得的是否是“吧啦哔哩”,#1234blbl# -> 是,#1234blbl!-> 不是
明白了上面的东西,下面咱们就开始“迭代”之旅,迭代顾名思义,就是重复的的既定的任务,直到完成。因此,为了完成迭代,咱们须要一个迭代器!那么什么是迭代器呢?来看看迭代器的协议吧
从前有我的发明了迭代器,为了让你们明白什么是迭代器,他就写了这个协议,那么协议的内容简而言之就是一句话:若是一个对象包括一个叫"next"(python3 为__next__)的方法,那么这个对象就叫作“迭代器”。
好了,那么咱们根据这个协议能够建立一个迭代器(iterator)
class Counter: def __init__(self): self.index = 0 def __next__(self): i = self.index if i < 10: self.index += 1 return i
这个Counter就是一个迭代器,可是目前它没有什么太大的做用,由于咱们不可能每次经过手动调用__next__方法来进行操做。
好消息是,不少编程软件为咱们提供了一个“语法糖”(syntactic sugar),让这个语法糖来替咱们反复执行__next__方法,好比python中的"for.. in",可是,为了让这个反复执行的过程停下来,咱们一样须要定义一个终止信号,在python中,终止信号就是抛出一个StopIteration的“例外”(exception),来告知咱们的语法糖:”好啦,没东西能够迭代了,能够停了“,这样迭代就终止了。
因此咱们再进一步规范一下咱们建立的迭代器成以下形式:
class Counter: def __init__(self): self.index = 0 def __next__(self): i = self.index if i < 10: self.index += 1 return i else: raise StopIteration
好了,咱们来试一下:
counter = Counter() for i in counter: print(i)
不妙,报错了。。
TypeError: 'Counter' object is not iterable
错误显示说:这个Counter对象不是可迭代的!这是什么意思呢?
原来,为了使用这个for..in 迭代语法糖,咱们须要在in后面放能够迭代的“迭代器”,什么是能够迭代?你能够认为就是可使用for..in语法糖,让语法糖帮你重复调用next方法就行了。若是不能够迭代,
那么for..in这个语法糖就没法为咱们自动调用next方法。
因此说,为了使用for..in语法糖来进行迭代咱们的迭代器,你必须让你的迭代器可迭代(有点绕。。哈哈)。
这句话有两层含义:
1,为了使用for..in语法糖,你必须让你的迭代器可迭代
2,你若是不适用for..in语法糖,你就没必要让你的迭代器可迭代,你能够本身写一个语法糖,不断地调用next方法,当遇到StopIteration例外的时候中止罢了。
可是当你使用别人(编程语言)实现编写好的语法糖时,你就必须按照他们的规则走。
好了,咱们如今明白了,一般来说,当咱们要建立了一个迭代器时,咱们还“必须”(注意是必须)让迭代器可迭代,这样理解:由于一个不可迭代的迭代器是没有意义的!
因此,注意!从如今开始到文章结束,我所说的“迭代器”都是“可迭代”的迭代器!
那么怎么让个人迭代器可迭代呢?一样,来看什么是“可迭代协议”(iterable protocol)
在python中,为了使一个”对象“可迭代:
1,这个迭代器必须同时包含另外一个方法叫作“__iter__”
2,这个"__iter__"方法还得返回一个”迭代器“(可迭代)
请注意,上面我说的是:为了使一个”对象“可迭代,这里,对象能够指咱们刚刚建立的”Counter“迭代器,也能够是其余的对象。
来个栗子:
为了使咱们刚才建立的Counter迭代器对象“可迭代”,那么:
1,咱们就在这个Counter对象里面添加一个叫__iter__的方法 (可迭代化操做)
2, 让这个__iter__方法返回一个“可迭代的迭代器” (这里就是本身了!)
class Counter: def __init__(self): self.index = 0 def __iter__(self): return self def __next__(self): i = self.index if i < 10: self.index += 1 return i else: raise StopIteration counter = Counter() for i in counter: print(i)
Cool! 这个时候咱们获得了0,1,2,3,4,5,6,7,8,9的迭代!
这里简单说一些执行步骤,当咱们使用for..in语法糖的时候,它先调用__iter__方法,获得返回的迭代器,而后连续调用该迭代器的__next__方法,知道遇到StopIteration例外
我上面也提到了,咱们不只可使迭代器“可迭代”,咱们也可使普通的对象“可迭代”,只需给该对象添加一个__iter__的方法,而后返回一个可迭代的迭代器就行了!
这里顺便插一句!在python中,咱们可使用"iter"这个函数来返回一个“可迭代的迭代器”。
好比:
x = iter([1, 2, 3]) print(x) #<list_iterator object at 0x10c828550> x.__next__() # 返回 1 x.__next__() # 返回 2 x.__next__() # 返回 3 x.__next__() # 返回 StopIteration
因此,咱们可让一个普通对象可迭代,而不必定非得是迭代器。
class Name: def __iter__(self): return iter(['zhangsan', 'lisi', 'wangwu']) name = Name() for n in name: print(n)
不错!咱们获得了zhangsan, lisi, wangwu
如今逻辑不是很复杂的状况之下,这种建立迭代器的方式仍是可以接受的,可是若是逻辑复杂,以及用这种模式多了,每次这么定义就不是很方便,因而为了“简化”建立迭代器的过程,“生成器”generator就出现了。
生成器的出现,就是为了简化建立迭代器的繁杂,同时又要保证逻辑的清晰,说到底生成器就是为了更方便咱们使用迭代器而生的,生成器的特性以下:
1, 生成器的样子就是一个普通的函数,只不过return关键词被yield取代了
2, 当调用这个“函数”的时候,它会当即返回一个迭代器,而不当即执行函数内容,直到调用其返回迭代器的next方法是才开始执行,直到遇到yield语句暂停。
3, 继续调用生成器返回的迭代器的next方法,恢复函数执行,直到再次遇到yield语句
4, 如此反复,一直到遇到StopIteration
看以下例子:
def gFun(): print('before hello') yield 'hello' print('after hello') a = gFun() # 调用生成器函数,返回一个迭代器并赋给a print(a) # <generator object gFun at 0x104cd2a40> 获得一个生成器对象(迭代器) print(a.__next__()) # before hello # hello print(a.__next__()) # after hello # StopIteration
同时由于调用生成器函数返回的是一个迭代器,因此咱们可使用for..in语法糖对其进行迭代操做:
a = gFun() for x in a: print(x)
迭代返回了before hello, hello, after hello
首先快速看一段代码:
def firstn(n): num, nums = 0, [] while num < n: nums.append(num) num += 1 return nums sum_of_first_n = sum(firstn(1000000))
这段代码定一个了一个函数firstn,该函数接受一个参数n,返回n以前全部的整数,最后对这些整数进行求和。
这个代码使用了咱们传统的while循环,若是接受的参数n比较小还好,可是当接受的参数很大时,对内存的消耗就凸显出来了,由于在执行该函数的过程当中,
nums这个大的列表会所有存在于内存中。而且求和运算只有当nums列表彻底构建完成以后才能够进行运算,效率也高。
而用迭代器(生成器)的方法则会大大提升效率,一方面每次next循环都会yield出一个值,供sum函数累加使用,这样就不用占用很大的内存,另外一方面,使用迭代器/生成器也不用彻底等到前n个数所有遍历完再进行累加,效率更高!