在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ Python/ 生成器
標(biāo)準(zhǔn)庫(kù) (4)
如何成為 Python 高手
標(biāo)準(zhǔn)庫(kù) (6)
標(biāo)準(zhǔn)庫(kù) (3)
類(lèi)(2)
Pandas 使用 (2)
xml
用 tornado 做網(wǎng)站 (5)
文件(1)
練習(xí)
列表(3)
從小工到專家
除法
錯(cuò)誤和異常 (2)
函數(shù)(1)
用 tornado 做網(wǎng)站 (7)
為做網(wǎng)站而準(zhǔn)備
函數(shù)練習(xí)
標(biāo)準(zhǔn)庫(kù) (8)
Pandas 使用 (1)
回顧 list 和 str
字典(1)
用 tornado 做網(wǎng)站 (3)
字符串(1)
函數(shù)(2)
寫(xiě)一個(gè)簡(jiǎn)單的程序
將數(shù)據(jù)存入文件
語(yǔ)句(5)
SQLite 數(shù)據(jù)庫(kù)
集成開(kāi)發(fā)環(huán)境(IDE)
集合(1)
類(lèi)(1)
用 tornado 做網(wǎng)站 (6)
用 tornado 做網(wǎng)站 (2)
自省
語(yǔ)句(4)
錯(cuò)誤和異常 (1)
用 tornado 做網(wǎng)站 (4)
集合(2)
列表(1)
標(biāo)準(zhǔn)庫(kù) (1)
生成器
mysql 數(shù)據(jù)庫(kù) (1)
第三方庫(kù)
實(shí)戰(zhàn)
運(yùn)算符
類(lèi)(3)
字典(2)
語(yǔ)句(1)
數(shù)和四則運(yùn)算
語(yǔ)句(2)
文件(2)
MySQL 數(shù)據(jù)庫(kù) (2)
電子表格
迭代器
mongodb 數(shù)據(jù)庫(kù) (1)
特殊方法 (2)
特殊方法 (1)
字符編碼
編寫(xiě)模塊
用 tornado 做網(wǎng)站 (1)
標(biāo)準(zhǔn)庫(kù) (5)
函數(shù)(4)
類(lèi)(5)
字符串(2)
關(guān)于 Python 的故事
函數(shù)(3)
字符串(4)
處理股票數(shù)據(jù)
常用數(shù)學(xué)函數(shù)和運(yùn)算優(yōu)先級(jí)
字符串(3)
為計(jì)算做準(zhǔn)備
多態(tài)和封裝
類(lèi)(4)
迭代
語(yǔ)句(3)
錯(cuò)誤和異常 (3)
分析 Hello
Python 安裝
標(biāo)準(zhǔn)庫(kù) (2)
列表(2)
元組

生成器

生成器(英文:generator)是一個(gè)非常迷人的東西,也常被認(rèn)為是 Python 的高級(jí)編程技能。不過(guò),我依然很樂(lè)意在這里跟讀者——盡管你可能是一個(gè)初學(xué)者——探討這個(gè)話題,因?yàn)槲蚁嘈抛x者看本教程的目的,絕非僅僅將自己限制于初學(xué)者水平,一定有一顆不羈的心——要成為 Python 高手。那么,開(kāi)始了解生成器吧。

還記得上節(jié)的“迭代器”嗎?生成器和迭代器有著一定的淵源關(guān)系。生成器必須是可迭代的,誠(chéng)然它又不僅僅是迭代器,但除此之外,又沒(méi)有太多的別的用途,所以,我們可以把它理解為非常方便的自定義迭代器。

最這個(gè)關(guān)系實(shí)在感覺(jué)有點(diǎn)糊涂了。稍安勿躁,繼續(xù)閱讀即明了。

簡(jiǎn)單的生成器

>>> my_generator = (x*x for x in range(4))

這是不是跟列表解析很類(lèi)似呢?仔細(xì)觀察,它不是列表,如果這樣的得到的才是列表:

>>> my_list = [x*x for x in range(4)]

以上兩的區(qū)別在于是 [] 還是 (),雖然是細(xì)小的差別,但是結(jié)果完全不一樣。

>>> dir(my_generator)
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', 
'__iter__', 
'__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 
'next', 
'send', 'throw']

為了容易觀察,我將上述結(jié)果進(jìn)行了重新排版。是不是發(fā)現(xiàn)了在迭代器中必有的方法__inter__()next(),這說(shuō)明它是迭代器。如果是迭代器,就可以用 for 循環(huán)來(lái)依次讀出其值。

>>> for i in my_generator:
...     print i
... 
0
1
4
9
>>> for i in my_generator:
...     print i
... 

當(dāng)?shù)谝槐檠h(huán)的時(shí)候,將 my_generator 里面的值依次讀出并打印,但是,當(dāng)再讀一次的時(shí)候,就發(fā)現(xiàn)沒(méi)有任何結(jié)果。這種特性也正是迭代器所具有的。

如果對(duì)那個(gè)列表,就不一樣了:

>>> for i in my_list:
...     print i
... 
0
1
4
9
>>> for i in my_list:
...     print i
... 
0
1
4
9

難道生成器就是把列表解析中的 [] 換成 () 就行了嗎?這僅僅是生成器的一種表現(xiàn)形式和使用方法罷了,仿照列表解析式的命名,可以稱之為“生成器解析式”(或者:生成器推導(dǎo)式、生成器表達(dá)式)。

生成器解析式是有很多用途的,在不少地方替代列表,是一個(gè)不錯(cuò)的選擇。特別是針對(duì)大量值的時(shí)候,如上節(jié)所說(shuō)的,列表占內(nèi)存較多,迭代器(生成器是迭代器)的優(yōu)勢(shì)就在于少占內(nèi)存,因此無(wú)需將生成器(或者說(shuō)是迭代器)實(shí)例化為一個(gè)列表,直接對(duì)其進(jìn)行操作,方顯示出其迭代的優(yōu)勢(shì)。比如:

>>> sum(i*i for i in range(10))
285

請(qǐng)讀者注意觀察上面的 sum() 運(yùn)算,不要以為里面少了一個(gè)括號(hào),就是這么寫(xiě)。是不是很迷人?如果列表,你不得不:

>>> sum([i*i for i in range(10)])
285

通過(guò)生成器解析式得到的生成器,掩蓋了生成器的一些細(xì)節(jié),并且適用領(lǐng)域也有限。下面就要剖析生成器的內(nèi)部,深入理解這個(gè)魔法工具。

定義和執(zhí)行過(guò)程

yield 這個(gè)詞在漢語(yǔ)中有“生產(chǎn)、出產(chǎn)”之意,在 Python 中,它作為一個(gè)關(guān)鍵詞(你在變量、函數(shù)、類(lèi)的名稱中就不能用這個(gè)了),是生成器的標(biāo)志。

>>> def g():
...     yield 0
...     yield 1
...     yield 2
... 
>>> g
<function g at 0xb71f3b8c>

建立了一個(gè)非常簡(jiǎn)單的函數(shù),跟以往看到的函數(shù)唯一不同的地方是用了三個(gè) yield 語(yǔ)句。然后進(jìn)行下面的操作:

>>> ge = g()
>>> ge
<generator object g at 0xb7200edc>
>>> type(ge)
<type 'generator'>

上面建立的函數(shù)返回值是一個(gè)生成器(generator)類(lèi)型的對(duì)象。

>>> dir(ge)
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__iter__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'next', 'send', 'throw']

在這里看到了__iter__()next(),說(shuō)明它是迭代器。既然如此,當(dāng)然可以:

>>> ge.next()
0
>>> ge.next()
1
>>> ge.next()
2
>>> ge.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

從這個(gè)簡(jiǎn)單例子中可以看出,那個(gè)含有 yield 關(guān)鍵詞的函數(shù)返回值是一個(gè)生成器類(lèi)型的對(duì)象,這個(gè)生成器對(duì)象就是迭代器。

我們把含有 yield 語(yǔ)句的函數(shù)稱作生成器。生成器是一種用普通函數(shù)語(yǔ)法定義的迭代器。通過(guò)上面的例子可以看出,這個(gè)生成器(也是迭代器),在定義過(guò)程中并沒(méi)有像上節(jié)迭代器那樣寫(xiě)__inter__()next(),而是只要用了 yield 語(yǔ)句,那個(gè)普通函數(shù)就神奇般地成為了生成器,也就具備了迭代器的功能特性。

yield 語(yǔ)句的作用,就是在調(diào)用的時(shí)候返回相應(yīng)的值。詳細(xì)剖析一下上面的運(yùn)行過(guò)程:

  1. ge = g():除了返回生成器之外,什么也沒(méi)有操作,任何值也沒(méi)有被返回。
  2. ge.next():直到這時(shí)候,生成器才開(kāi)始執(zhí)行,遇到了第一個(gè) yield 語(yǔ)句,將值返回,并暫停執(zhí)行(有的稱之為掛起)。
  3. ge.next():從上次暫停的位置開(kāi)始,繼續(xù)向下執(zhí)行,遇到 yield 語(yǔ)句,將值返回,又暫停。
  4. gen.next():重復(fù)上面的操作。
  5. gene.next():從上面的掛起位置開(kāi)始,但是后面沒(méi)有可執(zhí)行的了,于是 next() 發(fā)出異常。

從上面的執(zhí)行過(guò)程中,發(fā)現(xiàn) yield 除了作為生成器的標(biāo)志之外,還有一個(gè)功能就是返回值。那么它跟 return 這個(gè)返回值有什么區(qū)別呢?

yield

為了弄清楚 yield 和 return 的區(qū)別,我們寫(xiě)兩個(gè)沒(méi)有什么用途的函數(shù):

>>> def r_return(n):
...     print "You taked me."
...     while n > 0:
...         print "before return"
...         return n
...         n -= 1
...         print "after return"
... 
>>> rr = r_return(3)
You taked me.
before return
>>> rr
3

從函數(shù)被調(diào)用的過(guò)程可以清晰看出,rr = r_return(3),函數(shù)體內(nèi)的語(yǔ)句就開(kāi)始執(zhí)行了,遇到 return,將值返回,然后就結(jié)束函數(shù)體內(nèi)的執(zhí)行。所以 return 后面的語(yǔ)句根本沒(méi)有執(zhí)行。這是 return 的特點(diǎn),關(guān)于此特點(diǎn)的詳細(xì)說(shuō)明請(qǐng)閱讀《函數(shù)(2)》中的返回值相關(guān)內(nèi)容。

下面將 return 改為 yield:

>>> def y_yield(n):
...     print "You taked me."
...     while n > 0:
...         print "before yield"
...         yield n
...         n -= 1
...         print "after yield"
... 
>>> yy = y_yield(3)    #沒(méi)有執(zhí)行函數(shù)體內(nèi)語(yǔ)句
>>> yy.next()          #開(kāi)始執(zhí)行
You taked me.
before yield
3                      #遇到 yield,返回值,并暫停
>>> yy.next()          #從上次暫停位置開(kāi)始繼續(xù)執(zhí)行
after yield
before yield
2                      #又遇到 yield,返回值,并暫停
>>> yy.next()          #重復(fù)上述過(guò)程
after yield
before yield
1
>>> yy.next()
after yield            #沒(méi)有滿足條件的值,拋出異常
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

結(jié)合注釋和前面對(duì)執(zhí)行過(guò)程的分析,讀者一定能理解 yield 的特點(diǎn)了,也深知與 return 的區(qū)別了。

一般的函數(shù),都是止于 return。作為生成器的函數(shù),由于有了 yield,則會(huì)遇到它掛起,如果還有 return,遇到它就直接拋出 SoptIteration 異常而中止迭代。

斐波那契數(shù)列已經(jīng)是老相識(shí)了。不論是循環(huán)、迭代都用它舉例過(guò),現(xiàn)在讓我們還用它吧,只不過(guò)是要用上 yield:

#!/usr/bin/env Python
# coding=utf-8

def fibs(max):
    """
    斐波那契數(shù)列的生成器
    """
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1

if __name__ == "__main__":
    f = fibs(10)
    for i in f:
        print i ,

運(yùn)行結(jié)果如下:

$ python 21501.py
1 1 2 3 5 8 13 21 34 55

用生成器方式實(shí)現(xiàn)的斐波那契數(shù)列是不是跟以前的有所不同了呢?讀者可以將本教程中已經(jīng)演示過(guò)的斐波那契數(shù)列實(shí)現(xiàn)方式做一下對(duì)比,體會(huì)各種方法的差異。

經(jīng)過(guò)上面的各種例子,已經(jīng)明確,一個(gè)函數(shù)中,只要包含了 yield 語(yǔ)句,它就是生成器,也是迭代器。這種方式顯然比前面寫(xiě)迭代器的類(lèi)要簡(jiǎn)便多了。但,并不意味著上節(jié)的就被拋棄。是生成器還是迭代器,都是根據(jù)具體的使用情景而定。

生成器方法

在 python2.5 以后,生成器有了一個(gè)新特征,就是在開(kāi)始運(yùn)行后能夠?yàn)樯善魈峁┬碌闹?。這就好似生成器和“外界”之間進(jìn)行數(shù)據(jù)交流。

>>> def repeater(n):
...     while True:
...         n = (yield n)
... 
>>> r = repeater(4)
>>> r.next()
4
>>> r.send("hello")
'hello'

當(dāng)執(zhí)行到 r.next() 的時(shí)候,生成器開(kāi)始執(zhí)行,在內(nèi)部遇到了 yield n 掛起。注意在生成器函數(shù)中,n = (yield n) 中的 yield n 是一個(gè)表達(dá)式,并將結(jié)果賦值給 n,雖然不嚴(yán)格要求它必須用圓括號(hào)包裹,但是一般情況都這么做,請(qǐng)讀者也追隨這個(gè)習(xí)慣。

當(dāng)執(zhí)行 r.send("hello") 的時(shí)候,原來(lái)已經(jīng)被掛起的生成器(函數(shù))又被喚醒,開(kāi)始執(zhí)行 n = (yield n),也就是講 send() 方法發(fā)送的值返回。這就是在運(yùn)行后能夠?yàn)樯善魈峁┲档暮x。

如果接下來(lái)再執(zhí)行 r.next() 會(huì)怎樣?

>>> r.next()

什么也沒(méi)有,其實(shí)就是返回了 None。按照前面的敘述,讀者可以看到,這次執(zhí)行 r.next(),由于沒(méi)有傳入任何值,yield 返回的就只能是 None.

還要注意,send() 方法必須在生成器運(yùn)行后并掛起才能使用,也就是 yield 至少被執(zhí)行一次。如果不是這樣:

>>> s = repeater(5)
>>> s.send("how")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't send non-None value to a just-started generator

就報(bào)錯(cuò)了。但是,可將參數(shù)設(shè)為 None:

>>> s.send(None)
5

這是返回的是調(diào)用函數(shù)的時(shí)傳入的值。

此外,還有兩個(gè)方法:close() 和 throw()

  • throw(type, value=None, traceback=None):用于在生成器內(nèi)部(生成器的當(dāng)前掛起處,或未啟動(dòng)時(shí)在定義處)拋出一個(gè)異常(在 yield 表達(dá)式中)。
  • close():調(diào)用時(shí)不用參數(shù),用于關(guān)閉生成器。

最后一句,你在編程中,不用生成器也可以。


總目錄   |   上節(jié):迭代器   |   下節(jié):錯(cuò)誤和異常(1)

如果你認(rèn)為有必要打賞我,請(qǐng)通過(guò)支付寶:qiwsir@126.com,不勝感激。

上一篇:數(shù)和四則運(yùn)算下一篇:除法