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

    原文作者:madmuggle
    原文地址: https://www.jianshu.com/p/28c0cc03f92f
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞