導航:首頁 > 編程語言 > python和生成器有什麼不同

python和生成器有什麼不同

發布時間:2023-08-20 07:33:39

1. 如何更好地理解python迭代器和生成器

迭代器和生成器都是Python中特有的概念,迭代器可以看作是一個特殊的對象,每次調用該對象時會返回自身的下一個元素,從實現上來看,一個可迭代的對象必須是定義了__iter__()方法的對象,而一個迭代器必須是定義了__iter__()方法和next()方法的對象。生成器的概念要比迭代器稍顯復雜,因為生成器是能夠返回一個迭代器的函數,其最大的作用是將輸入對象返回為一個迭代器。Python中使用了迭代的概念,是因為當需要循環遍歷一個較大的對象時,傳統的內存載入方式會消耗大量的內存,不如需要時讀取一個元素的方式更為經濟快捷。
迭代器
迭代器(iterator)是一種對象,它能夠用來遍歷標准模板庫容器中的部分或全部元素,每個迭代器對象代表容器中的確定的地址。迭代器修改了常規指針的介面,所謂迭代器是一種概念上的抽象:那些行為上像迭代器的東西都可以叫做迭代器。然而迭代器有很多不同的能力,它可以把抽象容器和通用演算法有機的統一起來。
迭代器提供一些基本操作符:*、++、==、!=、=。這些操作和C/C++「操作array元素」時的指針介面一致。不同之處在於,迭代器是個所謂的復雜的指針,具有遍歷復雜數據結構的能力。其下層運行機製取決於其所遍歷的數據結構。因此,每一種容器型別都必須提供自己的迭代器。事實上每一種容器都將其迭代器以嵌套的方式定義於內部。因此各種迭代器的介面相同,型號卻不同。這直接導出了泛型程序設計的概念:所有操作行為都使用相同介面,雖然它們的型別不同。
迭代器使開發人員能夠在類或結構中支持foreach迭代,而不必整個實現IEnumerable或者IEnumerator介面。只需提供一個迭代器,即可遍歷類中的數據結構。當編譯器檢測到迭代器時,將自動生成IEnumerable介面或者IEnumerator介面的Current,MoveNext和Dispose方法。
生成器
生成器是一次生成一個值的特殊類型函數。可以將其視為可恢復函數。調用該函數將返回一個可用於生成連續 x 值的生成器【Generator】
簡單的說就是在函數的執行過程中,yield語句會把你需要的值返回給調用生成器的地方,然後退出函數,下一次調用生成器函數的時候又從上次中斷的地方開始執行,而生成器內的所有變數參數都會被保存下來供下一次使用。

2. 如何更好地理解Python迭代器和生成器

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

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

1. 迭代器協議

由於生成器自動實現了迭代器協議,而迭代器協議對很多人來說,也是一個較為抽象的概念。所以,為了更好的理解生成器,我們需要簡單的回顧一下迭代器協議的概念。
迭代器協議是指:對象需要提供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', '...'

2. 生成器

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

Python有兩種不同的方式提供生成器:
生成器函數:常規函數定義,但是,使用yield語句而不是return語句返回結果。yield語句一次返回一個結果,在每個結果中間,掛起函數的狀態,以便下次重它離開的地方繼續執行
生成器表達式:類似於列表推導,但是,生成器返回按需產生結果的一個對象,而不是一次構建一個結果列表

2.1 生成器函數

我們來看一個例子,使用生成器返回自然數的平方(注意返回的是多個值):
def gensquares(N):
for i in range(N):
yield i ** 2

for item in gensquares(5):
print item,

使用普通函數:
def gensquares(N):
res = []
for i in range(N):
res.append(i*i)
return res

for item in gensquares(5):
print item,

可以看到,使用生成器函數代碼量更少。

2.2 生成器表達式

使用列表推導,將會一次產生所有結果:
>>> squares = [x**2 for x in range(5)]
>>> squares
[0, 1, 4, 9, 16]

將列表推導的中括弧,替換成圓括弧,就是一個生成器表達式:
>>> squares = (x**2 for x in range(5))
>>> squares
<generator 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)])

2.3 再看生成器

前面已經對生成器有了感性的認識,我們以生成器函數為例,再來深入探討一下Python的生成器:
語法上和函數類似:生成器函數和常規函數幾乎是一樣的。它們都是使用def語句進行定義,差別在於,生成器使用yield語句返回一個值,而常規函數使用return語句返回一個值
自動實現迭代器協議:對於生成器,Python會自動實現迭代器協議,以便應用到迭代背景中(如for循環,sum函數)。由於生成器自動實現了迭代器協議,所以,我們可以調用它的next方法,並且,在沒有值可以返回的時候,生成器自動產生StopIteration異常
狀態掛起:生成器使用yield語句返回一個值。yield語句掛起該生成器函數的狀態,保留足夠的信息,以便之後從它離開的地方繼續執行
3. 示例

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

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

大家可以在自己電腦上試試下面兩個表達式,並且觀察內存佔用情況。對於前一個表達式,我在自己的電腦上進行測試,還沒有看到最終結果電腦就已經卡死,對於後一個表達式,幾乎沒有什麼內存佔用。
sum([i for i in xrange(10000000000)])
sum(i for i in xrange(10000000000))

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

不使用生成器的情況:
def index_words(text):
result = []
if text:
result.append(0)
for index, letter in enumerate(text, 1):
if letter == ' ':
result.append(index)
return result

使用生成器的情況:
def index_words(text):
if text:
yield 0
for index, letter in enumerate(text, 1):
if letter == ' ':
yield index

這里,至少有兩個充分的理由說明 ,使用生成器比不使用生成器代碼更加清晰:
使用生成器以後,代碼行數更少。大家要記住,如果想把代碼寫的Pythonic,在保證代碼可讀性的前提下,代碼行數越少越好
不使用生成器的時候,對於每次結果,我們首先看到的是result.append(index),其次,才是index。也就是說,我們每次看到的是一個列表的append操作,只是append的是我們想要的結果。使用生成器的時候,直接yield index,少了列表append操作的干擾,我們一眼就能夠看出,代碼是要返回index。
這個例子充分說明了,合理使用生成器,能夠有效提高代碼可讀性。只要大家完全接受了生成器的概念,理解了yield語句和return語句一樣,也是返回一個值。那麼,就能夠理解為什麼使用生成器比不使用生成器要好,能夠理解使用生成器真的可以讓代碼變得清晰易懂。

4. 使用生成器的注意事項

相信通過這篇文章,大家已經能夠理解生成器的作用和好處。但是,還沒有結束,使用生成器,也有一點注意事項。

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

如下所示:
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_population
for population in gen:
print population / all_population

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

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

5. 總結

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

3. python生成器到底有什麼優點

1、節省資源消耗,和聲明序列不同的是生成器在不使用的時候幾乎不佔內存,也沒有聲明計算過程!
2、使用的時候,生成器是隨用隨生成,用完即刻釋放,非常高效!
3、可在單線程下實現並發運算處理效果,非常牛逼,這點不可小視,看看nginx epoll單線程承載的並發量比多線程還效率高很多,最底層就是這個原理!

4. python迭代器和生成器的區別

迭代器

迭代是Python最強大的功能之一,是訪問集合元素的一種方式。

迭代器是一個可以記住遍歷的位置的對象。

迭代器對象從集合的第一個元素開始訪問,直到所有的元素被訪問完結束,迭代器只能往前不會後退。

迭代器有兩個基本的方法:iter()和next()。

生成器

在Python中,使用了yield的函數被稱為生成器。

跟普通函數不同的是,生成器是一個返回迭代器的函數,只能用於迭代操作,更簡單點理解生成器就是一個迭代器。

在調用生成器運行的過程中,每次遇到yield時函數會暫停並保存當前所有的運行信息,返回yield的值,並在下一次執行next()方法時從當前位置繼續運行。

調用一個生成器函數,返回的是一個迭代器對象。

迭代器與生成器之間的區別:

迭代器是一個更抽象的概念,任何對象,如果它的類有NEXTiter方法返回自己本身,對於string、list、dict、tuple等這類容器對象,使用for循環遍歷是很方便的。在後台For語言對容器對象條用iter()函數,iter()是Python的內置函數。iter()會返回一個定義了next()方法迭代器對象,在容器中逐個訪問容器的元素,next()也是Python的內置函數,next()會拋出StopIteration異常。

生成器是創新迭代器的簡單而強大的工具,它們寫起來就好像正則函數,只是在需要返回數據的時候使用yield 語句。

迭代器協議,對象需要提供next()方法,它要麼返回迭代中的下一項,要麼就引起一個StopIteration異常,終止迭代。

可迭代對象,實現了迭代器協議對象。list、tuple、dict都是Iterable可迭代的對象,但不是Iterator迭代器對象。

5. 詳解Python中的協程,為什麼說它的底層是生成器

協程又稱為是微線程,英文名是Coroutine。它和線程一樣可以調度,但是不同的是線程的啟動和調度需要通過操作系統來處理。並且線程的啟動和銷毀需要涉及一些操作系統的變數申請和銷毀處理,需要的時間比較長。而協程呢,它的調度和銷毀都是程序自己來控制的,因此它更加輕量級也更加靈活。

協程有這么多優點,自然也會有一些缺點,其中最大的缺點就是需要編程語言自己支持,否則的話需要開發者自己通過一些方法來實現協程。對於大部分語言來說,都不支持這一機制。go語言由於天然支持協程,並且支持得非常好,使得它廣受好評,短短幾年時間就迅速流行起來。

對於Python來說,本身就有著一個GIL這個巨大的先天問題。GIL是Python的全局鎖,在它的限制下一個Python進程同一時間只能同時執行一個線程,即使是在多核心的機器當中。這就大大影響了Python的性能,尤其是在CPU密集型的工作上。所以為了提升Python的性能,很多開發者想出了使用多進程+協程的方式。一開始是開發者自行實現的,後來在Python3.4的版本當中,官方也收入了這個功能,因此目前可以光明正大地說,Python是支持協程的語言了。

生成器(generator)

生成器我們也在之前的文章當中介紹過,為什麼我們介紹協程需要用到生成器呢,是因為Python的協程底層就是通過生成器來實現的。

通過生成器來實現協程的原因也很簡單,我們都知道協程需要切換掛起,而生成器當中有一個yield關鍵字,剛好可以實現這個功能。所以當初那些自己在Python當中開發協程功能的程序員都是通過生成器來實現的,我們想要理解Python當中協程的運用,就必須從最原始的生成器開始。

生成器我們很熟悉了,本質上就是帶有yield這個關鍵詞的函數。

async,await和future

從Python3.5版本開始,引入了async,await和future。我們來簡單說說它們各自的用途,其中async其實就是@asyncio.coroutine,用途是完全一樣的。同樣await代替的是yield from,意為等待另外一個協程結束。

我們用這兩個一改,上面的代碼就成了:

async def test(k):

n = 0

while n < k:

await asyncio.sleep(0.5)

print('n = {}'.format(n))

n += 1

由於我們加上了await,所以每次在列印之前都會等待0.5秒。我們把await換成yield from也是一樣的,只不過用await更加直觀也更加貼合協程的含義。

Future其實可以看成是一個信號量,我們創建一個全局的future,當一個協程執行完成之後,將結果存入這個future當中。其他的協程可以await future來實現阻塞。我們來看一個例子就明白了:

future = asyncio.Future()

async def test(k):

n = 0

while n < k:

await asyncio.sleep(0.5)

print('n = {}'.format(n))

n += 1

future.set_result('success')

async def log():

result = await future

print(result)

loop = asyncio.get_event_loop()

loop.run_until_complete(asyncio.wait([

log(),

test(5)

]))

loop.close()

在這個例子當中我們創建了兩個協程,第一個協程是每隔0.5秒print一個數字,在print完成之後把success寫入到future當中。第二個協程就是等待future當中的數據,之後print出來。

在loop當中我們要調度執行的不再是一個協程對象了而是兩個,所以我們用asyncio當中的wait將這兩個對象包起來。只有當wait當中的兩個對象執行結束,wait才會結束。loop等待的是wait的結束,而wait等待的是傳入其中的協程的結束,這就形成了一個依賴循環,等價於這兩個協程對象結束,loop才會結束。

總結

async並不只是可以用在函數上,事實上還有很多其他的用法,比如用在with語句上,用在for循環上等等。這些用法比較小眾,細節也很多,就不一一展開了,大家感興趣的可以自行去了解一下。

不知道大家在讀這篇文章的過程當中有沒有覺得有些費勁,如果有的話,其實是很正常的。原因也很簡單,因為Python原生是不支持協程這個概念的,所以在一開始設計的時候也沒有做這方面的准備,是後來覺得有必要才加入的。那麼作為後面加入的內容,必然會對原先的很多內容產生影響,尤其是協程藉助了之前生成器的概念來實現的,那麼必然會有很多耦合不清楚的情況。這也是這一塊的語法很亂,對初學者不友好的原因。

6. python迭代器和生成器區別是什麼

python中迭代器和生成器的區別

1、共同點

生成器是一種特殊的迭代器。

相關推薦:《Python視頻教程》

2、不同點

a、語法上:

生成器是通過函數的形式中調用 yield 或()的形式創建的。

迭代器可以通過 iter() 內置函數創建。

b、用法上:

生成器在調用next()函數或for循環中,所有過程被執行,且返回值。

迭代器在調用next()函數或for循環中,所有值被返回,沒有其他過程或動作。

7. 閑話python 45: 淺談生成器yield

生成器似乎並不是一個經常被開發者討論的語法,因此也就沒有它的大兄弟迭代器那麼著名。大家不討論它並不是說大家都已經對它熟悉到人盡皆知,與之相反,即使是工作多年的開發者可能對生成器的運行過程還是知之甚少。這是什麼原因導致的呢?我猜想大概有以下幾點原因: (1)運行流程不同尋常,(2)日常開發不需要,(3)常常將生成器與迭代器混淆。 生成器的運行流程可以按照協程來理解,也就是說 返回中間結果,斷點繼續運行 。這與我們通常對於程序調用的理解稍有差異。這種運行模式是針對什麼樣的需求呢? 一般而言,生成器是應用於大量磁碟資源的處理。 比如一個很大的文件,每次讀取一行,下一次讀取需要以上一次讀取的位置為基礎。下面就通過代碼演示具體看看生成器的運行機制、使用方式以及與迭代器的比較。

什麼是生成器?直接用文字描述可能太過抽象,倒不如先運行一段代碼,分析這段代碼的運行流程,然後總結出自己對生成器的理解。

從以上演示可以看出,這段代碼定義漏明了一個函數,這個函數除了yield這個關鍵字之外與一般函數並沒有差異,也就是說生成器的魔法都是這個yield關鍵字引起的。 第一點,函數的返回值是一個生成器對象。 上述代碼中,直接調用這個看似普通的函數,然後將返回值列印出來,發現返回值是一個對象,而並不是普通函數的返回值。 第二點,慧搜拿可以使用next對這個生成器對象進行操作 。生成器對象天然的可以被next函數調用,然後返回在yield關鍵字後面的內容。 第三,再次調用next函數處理生成器對象,發現是從上次yield語句之後繼續運行,直到下一個yield語句返回。

生成器的運行流程確實詭異,下面還要演示一個生成器可以執行的更加詭異的操作:運行過程中向函數傳參。

返回生成器和next函數操作生成器已經並不奇怪了,但是在函數運行過程中向其傳參還是讓人驚呆了。 調用生成器的send函數傳入參數,在函數內使用yield語句的返回值接收,然後繼續運行直到下一個yield語句返回。 以前實現這種運行流程的方式是在函數中加上一個從控制台獲取數據的指令,或者提前將參數傳入,但是現在不用了,send方式使得傳入的參數可以隨著讀取到的參數變化而變化。

很多的開發者比較容易混淆生成器和迭代器,而迭代器的運行過程更加符合一般的程序調用運行流程,因此從親進度和使用熟悉度而言,大家對迭代器更有好感。比如下面演示一個對迭代器使用next方法進行操作。

從以上演示來看,大家或許會認為迭代器比生成器簡單易用得太多了。不過,如果你了解迭代前搭器的實現機制,可能就不會這么早下結論了。python內置了一些已經實現了的迭代器使用確實方便,但是如果需要自己去寫一個迭代器呢?下面這段代碼就帶大家見識以下迭代器的實現。

在python中,能被next函數操作的對象一定帶有__next__函數的實現,而能夠被迭代的對象有必須實現__iter__函數。看了這么一段操作,相信大家對迭代器實現的繁瑣也是深有體會了,那麼生成器的實現是不是會讓你覺得更加簡單易用呢?不過千萬別產生一個誤區,即生成器比迭代器簡單就多用生成器。 在實際開發中,如果遇到與大量磁碟文件或者資料庫操作相關的倒是可以使用生成器。但是在其他的任務中使用生成器難免有炫技,並且使邏輯不清晰而導致可讀性下降的嫌疑。 這大概也能解釋生成器受冷落的原因。不過作為一個專業的開發者,熟悉語言特性是分內之事。

到此,關於生成器的討論就結束了。本文的notebook版本文件在github上的cnbluegeek/notebook倉庫中共享,歡迎感興趣的朋友前往下載。

閱讀全文

與python和生成器有什麼不同相關的資料

熱點內容
uc伺服器怎麼打開 瀏覽:363
net怎麼編譯 瀏覽:242
我的世界187伺服器地址ip 瀏覽:953
拍賣房價的演算法 瀏覽:438
linux內核編譯視頻教程 瀏覽:881
程序員厚黑 瀏覽:207
如何在閑魚淘二手安卓機 瀏覽:175
怎麼下載晨星app 瀏覽:132
兩台伺服器如何同步內容 瀏覽:808
伺服器共用一個ip有什麼壞處 瀏覽:461
go加密exe 瀏覽:606
pdf改分欄 瀏覽:123
python執行怎麼寫 瀏覽:766
遇見她app怎麼加好友 瀏覽:548
手機怎麼設置app強制提醒 瀏覽:77
怎樣不用海綿做解壓玩具 瀏覽:81
為什麼遠程伺服器復制不了文件 瀏覽:715
打開app閃退怎麼回事 瀏覽:752
bcrpt加密原理 瀏覽:401
女程序員寫的小說 瀏覽:774