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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,376评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,126评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,966评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,432评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,519评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,792评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,933评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,701评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,143评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,488评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,626评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,292评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,896评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,742评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,324评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,494评论 2 348

推荐阅读更多精彩内容

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