Generator,Python和JS

第一次接觸到yield這個(gè)關(guān)鍵字是在Python里面。伴隨著Generator和Comprehension了解到的。當(dāng)時(shí)一直沒(méi)覺(jué)得它多重要,還以為它只是個(gè)另外一個(gè)用起來(lái)放便點(diǎn)的語(yǔ)法糖。后來(lái)接觸了ES6,接觸了co,我才意識(shí)到,這是個(gè)了不得的東西。

Python的Generator

Python對(duì)「Comprehension」的支持非常友好,相同的語(yǔ)法,可以用在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>

上例中的最后一個(gè),就是所謂的Generator。它還有另外一種產(chǎn)生方法。先定義一個(gè)特殊的函數(shù)

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

然后就可以用如下方法產(chǎn)生一個(gè)Generator

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

通過(guò)__next__方法,我們可以看到,Generator以一種極為怪異的方式運(yùn)作。最簡(jiǎn)單的理解方式,是把yield看作一個(gè)特殊的return,它們?cè)谀撤N程度上的確相似。

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

看上去很怪異,因?yàn)椤窯enerator function」和「function」根本就是兩種不同的東西。而在Python里,卻使用同樣的語(yǔ)法def name():來(lái)創(chuàng)建它們。

調(diào)用「Generator function」返回的是一個(gè)Generator對(duì)象,而不是那個(gè)“函數(shù)”的執(zhí)行結(jié)果。

Javascript的Generator

在這一點(diǎn)上,Javascript比Python做得好。引入Generator的時(shí)候,Javascript定義了一個(gè)新的關(guān)鍵字function*,兩者就被顯式地區(qū)分開(kāi)了。你一眼就能注意到,這不是一個(gè)普通的函數(shù)。

我覺(jué)得這是少數(shù)Javascript做得比Python好的地方。

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

和Python一樣,通過(guò)一個(gè)特定的方法(next)來(lái)使用

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 }

對(duì)參數(shù)的支持情況

Generator的next方法支持參數(shù)這一點(diǎn)非常重要,它是讓Generator能夠包裝異步調(diào)用的基礎(chǔ)特性。JS里面,繼續(xù)使用next就行了。

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

傳遞參數(shù)給next,就仿佛是賦值給那個(gè)yield表達(dá)式一樣。

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)

當(dāng)你給__next__傳遞參數(shù)的時(shí)候

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

然而Python的Generator有個(gè)叫做send的方法,它可以完成這個(gè)工作。send必須傳一個(gè)參數(shù),而且初次調(diào)用必須傳None。所以細(xì)節(jié)上和JS有點(diǎn)區(qū)別,但大體上是一致的。

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

至于為什么不把__next__send合并為一個(gè)就不得而知了。其實(shí)跳出來(lái)看看,對(duì)應(yīng)Generator的行為,取名叫send的確更合語(yǔ)境。

異常與Generator

對(duì)于Generator而言,在next之外,最重要的一個(gè)方法就是throw了。在Generator函數(shù)里面,異常是可以被捕獲的,即使異常的發(fā)生地點(diǎn)不在本函數(shù)內(nèi)部。這是Generator的一大特點(diǎn)。

Javascript和Python在異常的處理這塊出奇的一致,名字都一樣使用throw。

為了方便,先寫(xiě)一個(gè)永遠(yuǎn)返回"ok"的Generator函數(shù)

var cnt = 0

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

現(xiàn)在對(duì)它做點(diǎn)測(cè)試

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如何呢,同樣的實(shí)驗(yàn)

cnt = 0

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

同樣的測(cè)試

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 簡(jiǎn)介 基本概念 Generator函數(shù)是ES6提供的一種異步編程解決方案,語(yǔ)法行為與傳統(tǒng)函數(shù)完全不同。本章詳細(xì)介紹...
    呼呼哥閱讀 1,135評(píng)論 0 4
  • 在此處先列下本篇文章的主要內(nèi)容 簡(jiǎn)介 next方法的參數(shù) for...of循環(huán) Generator.prototy...
    醉生夢(mèng)死閱讀 1,486評(píng)論 3 8
  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持,譯者再次奉上一點(diǎn)點(diǎn)福利:阿里云產(chǎn)品券,享受所有官網(wǎng)優(yōu)惠,并抽取幸運(yùn)大...
    HetfieldJoe閱讀 6,446評(píng)論 9 19
  • 個(gè)人筆記,方便自己查閱使用 Py.LangSpec.Contents Refs Built-in Closure ...
    freenik閱讀 67,937評(píng)論 0 5
  • 停不下來(lái)的少女情結(jié), 我活了這二十幾年, 我覺(jué)得聽(tīng)過(guò)最動(dòng)人的情話, 就是, 等你跟我說(shuō), 親愛(ài)的,嫁給我吧! Ye...
    小玩子Mary閱讀 229評(píng)論 0 0

友情鏈接更多精彩內(nèi)容