生成器有什麼好處?

2022-07-02 09:27:12 字數 4010 閱讀 9494

在python這門語言中,生成器毫無疑問是最有用的特性之一。與此同時,也是使用的最不廣泛的python特性之一。究其原因,主要是因為,在其他主流語言裡面沒有生成器的概念。正是由於生成器是乙個「新」的東西,所以,它一方面沒有引起廣大工程師的重視,另一方面,也增加了工程師的學習成本,最終導致大家錯過了python中如此有用的乙個特性。

我的這篇文章,希望通過簡單易懂的方式,深入淺出地介紹python的生成器,以改變「如此有用的特性卻使用極不廣泛」的現象。本文的組織如下:在第1章,我們簡單地介紹了python中的迭代器協議;在本文第2章,將會詳細介紹生成器的概念和語法;在第3章,將會給出乙個有用的例子,說明使用生成器的好處;在本文最後,簡單的討論了使用生成器的注意事項。

由於生成器自動實現了迭代器協議,而迭代器協議對很多人來說,也是乙個較為抽象的概念。所以,為了更好的理解生成器,我們需要簡單的回顧一下迭代器協議的概念。

迭代器協議是指:物件需要提供next方法,它要麼返回迭代中的下一項,要麼就引起乙個stopiteration異常,以終止迭代

可迭代物件就是:實現了迭代器協議的物件

協議是一種約定,可迭代物件實現迭代器協議,python的內建工具(如for迴圈,sum,min,max函式等)使用迭代器協議訪問物件。

舉個例子:在所有語言中,我們都可以使用for迴圈來遍歷陣列,python的list底層實現是乙個陣列,所以,我們可以使用for迴圈來遍歷list。如下所示:

for n in [1, 2, 3, 4]:... print n

但是,對python稍微熟悉一點的朋友應該知道,python的for迴圈不但可以用來遍歷list,還可以用來遍歷檔案物件,如下所示:

with open(『/etc/passwd』) as f: # 檔案物件提供迭代器協議... for line in f: # for迴圈使用迭代器協議訪問檔案... print line...

為什麼在python中,檔案還可以使用for迴圈進行遍歷呢?這是因為,在python中,檔案物件實現了迭代器協議,for迴圈並不知道它遍歷的是乙個檔案物件,它只管使用迭代器協議訪問物件即可。正是由於python的檔案物件實現了迭代器協議,我們才得以使用如此方便的方式訪問檔案,如下所示:

f = open('/etc/passwd')>>> dir(f)['class', 'enter', 'exit', 'iter', 'new', 'writelines', '...'

python使用生成器對延遲操作提供了支援。所謂延遲操作,是指在需要的時候才產生結果,而不是立即產生結果。這也是生成器的主要好處。

python有兩種不同的方式提供生成器:

生成器函式:常規函式定義,但是,使用yield語句而不是return語句返回結果。yield語句一次返回乙個結果,在每個結果中間,掛起函式的狀態,以便下次重它離開的地方繼續執行

生成器表示式:類似於列表推導,但是,生成器返回按需產生結果的乙個物件,而不是一次構建乙個結果列表

我們來看乙個例子,使用生成器返回自然數的平方(注意返回的是多個值):

def gensquares(n): for i in range(n): yield i ** 2for item in gensquares(5): print item,

使用普通函式:

使用列表推導,將會一次產生所有結果:

squares = [x**2 for x in range(5)]>>> squares[0, 1, 4, 9, 16]

將列表推導的中括號,替換成圓括號,就是乙個生成器表示式:

squares = (x**2 for x in range(5))>>> squaresgenerator object at 0x00b2ec88>>>> next(squares)0>>> next(squares)1>>> next(squares)4>>> list(squares)[9, 16]

python不但使用迭代器協議,讓for迴圈變得更加通用。大部分內建函式,也是使用迭代器協議訪問物件的。例如, sum函式是python的內建函式,該函式使用迭代器協議訪問物件,而生成器實現了迭代器協議,所以,我們可以直接這樣計算一系列值的和:

sum(x ** 2 for x in xrange(4))

而不用多此一舉的先構造乙個列表:

sum([x ** 2 for x in xrange(4)])

前面已經對生成器有了感性的認識,我們以生成器函式為例,再來深入**一下python的生成器:

語法上和函式類似:生成器函式和常規函式幾乎是一樣的。它們都是使用def語句進行定義,差別在於,生成器使用yield語句返回乙個值,而常規函式使用return語句返回乙個值

自動實現迭代器協議:對於生成器,python會自動實現迭代器協議,以便應用到迭代背景中(如for迴圈,sum函式)。由於生成器自動實現了迭代器協議,所以,我們可以呼叫它的next方法,並且,在沒有值可以返回的時候,生成器自動產生stopiteration異常

狀態掛起:生成器使用yield語句返回乙個值。yield語句掛起該生成器函式的狀態,保留足夠的資訊,以便之後從它離開的地方繼續執行

我們再來看兩個生成器的例子,以便大家更好的理解生成器的作用。

首先,生成器的好處是延遲計算,一次返回乙個結果。也就是說,它不會一次生成所有的結果,這對於大資料量處理,將會非常有用。

大家可以在自己電腦上試試下面兩個表示式,並且觀察記憶體占用情況。對於前乙個表示式,我在自己的電腦上進行測試,還沒有看到最終結果電腦就已經卡死,對於後乙個表示式,幾乎沒有什麼記憶體占用。

sum([i for i in xrange(10000000000)])sum(i for i in xrange(10000000000))

除了延遲計算,生成器還能有效提高**可讀性。例如,現在有乙個需求,求一段文字中,每個單詞出現的位置。

不使用生成器的情況:

def index_words(text): if text: yield 0 for index, letter in enumerate(text, 1): if letter == ' ': yield index

這裡,至少有兩個充分的理由說明 ,使用生成器比不使用生成器**更加清晰:

我們直接來看例子,假設檔案中儲存了每個省份的人口總數,現在,需要求每個省份的人口佔全國總人口的比例。顯然,我們需要先求出全國的總人口,然後在遍歷每個省份的人口,用每個省的人口數除以總人口數,就得到了每個省份的人口佔全國人口的比例。

如下所示:

def get_province_population(filename): with open(filename) as f: for line in f: yield int(line)gen = get_province_population('data.txt')all_population = sum(gen)#print all_populationfor population in gen: print population / all_population

執行上面這段**,將不會有任何輸出,這是因為,生成器只能遍歷一次。在我們執行sum語句的時候,就遍歷了我們的生成器,當我們再次遍歷我們的生成器的時候,將不會有任何記錄。所以,上面的**不會有任何輸出。

因此,生成器的唯一注意事項就是:生成器只能遍歷一次。

本文深入淺出地介紹了python中,乙個容易被大家忽略的重要特性,即python的生成器。為了講解生成器,本文先介紹了迭代器協議,然後介紹了生成器函式和生成器表示式,並通過示例演示了生成器的優點和注意事項。在實際工作中,充分利用python生成器,不但能夠減少記憶體使用,還能夠提高**可讀性。掌握生成器也是python高手的標配。希望本文能夠幫助大家理解python的生成器。

python生成器好處 Python生成器筆記

python中三大器有迭代器,生成器,裝飾器,本文主要講述生成器。主要從生成器的概念,本質,以及yield關鍵字的使用執行過程。本質 生成器是一類特殊的迭代器,使用了yield關鍵字的函式不再是函式,而是生成器。使用了yield的函式就是生成器 1.yield關鍵字有兩點作用 1.1 yield語句...

Python生成器到底有什麼優點?

迭代是python最強大的功能之一,是訪問集合元素的一種方式。迭代器是乙個可以記住遍歷的位置的物件。迭代器物件從集合的第乙個元素開始訪問,直到所有的元素被訪問完結束。迭代器只能往前不會後退。迭代器有兩個基本的方法 iter 和next 建立迭代器 把乙個類作為乙個迭代器使用需要在類中實現兩個方法 i...

python 生成器作用 Python生成器

生成器介紹 在函式內部包含yield關鍵字,那麼該函式執行的結果是生成器,生成器就是迭代器。生成器的功能 把函式結果做成迭代器 以一種優雅的方式封裝好iter,next 提供了一種自己定義迭代器的方式。使用生成器建立乙個迭代器 def a print a yield 11 使用yield,執行後返回...