Generator,Python和JS

第一次接触到yield这个关键字是在Python里面。伴随着Generator和Comprehension了解到的。当时一直没觉得它多重要,还以为它只是个另外一个用起来放便点的语法糖。后来接触了ES6,接触了co,我才意识到,这是个了不得的东西。

Python的Generator

Python对「Comprehension」的支持非常友好,相同的语法,可以用在List Comprehension, Set Comprehension, Dict Comprehension, Generator……

[i * 2 for i in range(10) if i ** 2 < 10]
#> [0, 2, 4, 6]

{i * 2 for i in range(10) if i ** 2 < 10}
#> {0, 2, 4, 6}

{k.upper(): v for k, v in {"a": 1, "b": 2}.items()}
#> {'A': 1, 'B': 2}

(i * 2 for i in range(10) if i ** 2 < 10)
#> <generator object <genexpr> at 0x7f9c53a99410>

上例中的最后一个,就是所谓的Generator。它还有另外一种产生方法。先定义一个特殊的函数

def blah():
  for i in range(10):
    if i ** 2 < 10:
      yield i * 2

然后就可以用如下方法产生一个Generator

blah()
#> <generator object blah at 0x7f9c53a99410>

通过__next__方法,我们可以看到,Generator以一种极为怪异的方式运作。最简单的理解方式,是把yield看作一个特殊的return,它们在某种程度上的确相似。

a = blah()
a.__next__()
#> 0
a.__next__()
#> 2
a.__next__()
#> 4
a.__next__()
#> 6
a.__next__()
#  Traceback (most recent call last):
#    File "<stdin>", line 1, in <module>
#  StopIteration

看上去很怪异,因为「Generator function」和「function」根本就是两种不同的东西。而在Python里,却使用同样的语法def name():来创建它们。

调用「Generator function」返回的是一个Generator对象,而不是那个“函数”的执行结果。

Javascript的Generator

在这一点上,Javascript比Python做得好。引入Generator的时候,Javascript定义了一个新的关键字function*,两者就被显式地区分开了。你一眼就能注意到,这不是一个普通的函数。

我觉得这是少数Javascript做得比Python好的地方。

function* blah() {
  for (var i = 0; i < 10; i++)
    if ((i * i) < 10)
      yield i * 2
}

和Python一样,通过一个特定的方法(next)来使用

a = blah()
//> Generator {  }
a.next()
//> Object { value: 0, done: false }
a.next()
//> Object { value: 2, done: false }
a.next()
//> Object { value: 4, done: false }
a.next()
//> Object { value: 6, done: false }
a.next()
//> Object { value: undefined, done: true }

对参数的支持情况

Generator的next方法支持参数这一点非常重要,它是让Generator能够包装异步调用的基础特性。JS里面,继续使用next就行了。

function* blah() {
  for (var i = 0; i < 10; i++) {
    t = yield i * 2
    console.log('t is ', t)
  }
}

传递参数给next,就仿佛是赋值给那个yield表达式一样。

a = blah()
//> Generator {  }
a.next("a")
//> Object { value: 0, done: false }
a.next("b")
//  t is  b
//> Object { value: 2, done: false }
a.next("c")
//  t is  c
//> Object { value: 4, done: false }

Python的__next__不支持这种特性

def blah():
  for i in range(10):
    t = yield i * 2
    print('t is ', t)

当你给__next__传递参数的时候

a = blah()
a.__next__(1)
#  Traceback (most recent call last):
#    File "<stdin>", line 1, in <module>
#  TypeError: expected 0 arguments, got 1

然而Python的Generator有个叫做send的方法,它可以完成这个工作。send必须传一个参数,而且初次调用必须传None。所以细节上和JS有点区别,但大体上是一致的。

a = blah()
a.send(None)
#> 0
a.send("b")
#  t is  b
#> 2
a.send("c")
#  t is  c
#> 4

至于为什么不把__next__send合并为一个就不得而知了。其实跳出来看看,对应Generator的行为,取名叫send的确更合语境。

异常与Generator

对于Generator而言,在next之外,最重要的一个方法就是throw了。在Generator函数里面,异常是可以被捕获的,即使异常的发生地点不在本函数内部。这是Generator的一大特点。

Javascript和Python在异常的处理这块出奇的一致,名字都一样使用throw

为了方便,先写一个永远返回"ok"的Generator函数

var cnt = 0

function* blah() {
  while (true) {
    try { yield ++cnt }
    catch (e) { console.log(`error: ${e.message}`) }
  }
}

现在对它做点测试

a = blah()
a.next()
//> Object {value: 1, done: false}
a.next()
//> Object {value: 2, done: false}
a.next()
//> Object {value: 3, done: false}
a.throw(new Error("haha"))
//  error: haha
//> Object {value: 4, done: false}
a.throw(new Error("haha"))
//  error: haha
//> Object {value: 5, done: false}
a.next()
//> Object {value: 6, done: false}

Python如何呢,同样的实验

cnt = 0

def blah():
  global cnt
  while True:
    try:
      cnt += 1
      yield cnt
    except Exception as e:
      print("error:", str(e))

同样的测试

a = blah()
a.__next__()
#> 1
a.__next__()
#> 2
a.__next__()
#> 3
a.throw(Exception("haha"))
#  error: haha
#> 4
a.throw(Exception("haha"))
#  error: haha
#> 5
a.__next__()
#> 6

原文:http://madmuggle.me/articles/GeneratorOfPythonAndJS.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 简介 基本概念 Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同。本章详细介绍...
    呼呼哥阅读 1,134评论 0 4
  • 在此处先列下本篇文章的主要内容 简介 next方法的参数 for...of循环 Generator.prototy...
    醉生夢死阅读 1,486评论 3 8
  • 官方中文版原文链接 感谢社区中各位的大力支持,译者再次奉上一点点福利:阿里云产品券,享受所有官网优惠,并抽取幸运大...
    HetfieldJoe阅读 6,445评论 9 19
  • 个人笔记,方便自己查阅使用 Py.LangSpec.Contents Refs Built-in Closure ...
    freenik阅读 67,931评论 0 5
  • 停不下来的少女情结, 我活了这二十几年, 我觉得听过最动人的情话, 就是, 等你跟我说, 亲爱的,嫁给我吧! Ye...
    小玩子Mary阅读 229评论 0 0

友情链接更多精彩内容