以前看阮一峰老师的《ES6入门》,说到ES6开始,语言针对Array(ArrayLike)、Map、Set数据结构提供了统一的接口机制--iterator来实现遍历,并配套了for...of循环。并提醒我们iteraor只是实现了接口,与所遍历的数据结构是分开的。
今天大致看了python中的迭代器部分,发现从概念、特性和实现机制上,二者都相当一致。(说白了就是一东西。。)
数据结构:
只要实现或继承了iterable接口的数据结构,都可以用iterator遍历。
ES6中数组、类数组(字符串、HTMLCollection)、Map、Set数据结构都部署有属性[Symbol.iterator],调用它时返回一个迭代器,迭代时默认从该数据结构索引位置0往Length-1遍历,对Map、Set则是按元素添加顺序遍历。
Python中的集合数据(字符串、列表、元组、集合、字典)也自带一个内部方法__iter__.当调用语言内置函数iter(s)时实际上执行了s.__iter__()返回一个迭代器。next(iterator)实际上执行了iterator.__next__()指向数据结构下一个成员,直到遇到终止条件。
终止条件:
日常遍历集合数据时我们很少需要手动调用迭代器,但是我们可以看看:
//ES6
let lis = [1,2,3,5]
let it = lis[Symbol.iterator]()
console.log(it)
console.log(it.next())
...
console.log(it.next())//第4次
console.log(it.next())//第5次
//输出:
Array Iterator {}
{value: 1, done: false}
...
{value: 5, done: false}
{value: undefined, done: true}
ES6的迭代器自带一个next方法,每次执行指向结构中下一个成员。当指针指向最后一个成员之后时,返回对象的done才为true,作为遍历结束的依据。
但是在python中手动调用迭代器时,遍历完最后一个元素再执行next,会直接抛出一个错误StopIteration表示遍历结束,比如下边:
list = [1, 2, 3]
it = iter(list) # 创建迭代器对象
try:
print(next(it))
print(next(it))
print(next(it))
print(next(it))
except StopIteration:
pass
finally:
print("遍历结束")
#输出:
1
2
3
遍历结束
自定义迭代器对象:
由于迭代器本身只是描述一个接口,并不与具体数据结构绑定,所以我们可以借助迭代器的模式构建自定义的迭代器。
-用函数实现:
#《python cookbook》4.2节的例子
def frange(start, stop, increment):
x = start
while x < stop:
yield x
x += increment
for n in frange(0, 2, 0.5):
print(n)
#输出:
0
0.5
1.0
1.5
-用class/对象实现:
#自定义一个iterable的class
class MyNumbers:
def __iter__(self):
self.a = 1
return self
def __next__(self):
x = self.a
self.a += 1
return x
myclass = MyNumbers()
myiter = iter(myclass) #执行myclass.__iter__()
print(next(myiter)) ##执行myclass.__next__()
print(next(myiter))
#输出:
1
2
generator函数:
顾名思义是可以产生一系列值的函数,或者说,在函数的不同执行阶段可以产生对应的状态。
python中的generator函数定义概念比较简单:凡是使用了yield语句的函数就是一个generator函数,generator函数返回的是一个迭代器对象。
ES6中generator的意义几乎一样,只是语法上稍有不同:function后必须加*符号,且yield关键字不是必须的。在ES中generator往往作为封装任务的容器。(反正我自己是很少用了,async比较舒服。。)
由于generator执行返回的是一个iterator,因而可以充当数据结构的iterator接口,不过这方面我倒是很少用到。
generator和iterator的很多实践在python和es6中都是通用的,毕竟这模式本来就和语言无关。讲真,如果我们需要动态遍历一个符合某种规则的序列,使用iterator比先生成一个序列实例再去逐一遍历其元素性能要好很多。