欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

五分鐘帶你搞懂python 迭代器與生成器

 更新時(shí)間:2020年08月30日 14:34:35   作者:劉早起  
這篇文章主要介紹了python 迭代器與生成器的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)python,感興趣的朋友可以了解下

前言

大家周末好,今天給大家?guī)?lái)的是Python當(dāng)中生成器和迭代器的使用。

我當(dāng)初第一次學(xué)到迭代器和生成器的時(shí)候,并沒(méi)有太在意,只是覺(jué)得這是一種新的獲取數(shù)據(jù)的方法。對(duì)于獲取數(shù)據(jù)的方法而言,我們會(huì)一種就足夠了。但是在我后來(lái)Python的使用以及TensorFlow等學(xué)習(xí)使用當(dāng)中,我發(fā)現(xiàn)很多地方都用到了迭代器和生成器,或者是直接使用,或者是借鑒了思路。今天就讓我們仔細(xì)來(lái)看看,它們到底是怎么回事。

迭代器

我們先從迭代器開(kāi)始入手,迭代器并不是Python獨(dú)有的概念,在C++和Java當(dāng)中都有iterator的概念,兩者的使用也都差不多。迭代器主要解決了一個(gè)問(wèn)題,在一個(gè)復(fù)雜場(chǎng)景下,獲取數(shù)據(jù)怎么盡可能簡(jiǎn)便。

我們來(lái)假設(shè)一個(gè)場(chǎng)景,假設(shè)我們從某個(gè)數(shù)據(jù)源獲取了一批數(shù)據(jù)。然后我們需要調(diào)用前一萬(wàn)條生成一個(gè)結(jié)果,得到結(jié)果之后,我們要將剩下的數(shù)據(jù)交給另一個(gè)調(diào)用方去處理。這個(gè)過(guò)程看起來(lái)非常平常,但是隱藏了兩個(gè)問(wèn)題,第一個(gè)問(wèn)題是如果我們能保證第一次處理的時(shí)候,每次都是使用一萬(wàn)條還好說(shuō),如果我們使用的條數(shù)是一個(gè)動(dòng)態(tài)的值呢?顯然,我們需要一個(gè)變量來(lái)記錄我們究竟用了多少條數(shù)據(jù),和這批數(shù)據(jù)的狀態(tài)。其次,如果這個(gè)數(shù)據(jù)量很大會(huì)存在一個(gè)數(shù)據(jù)傳輸?shù)膯?wèn)題。我們每次都要將一大批數(shù)據(jù)傳來(lái)傳去,顯然會(huì)消耗很多資源。

還有一個(gè)場(chǎng)景是如果我們開(kāi)發(fā)的是一個(gè)比較復(fù)雜的數(shù)據(jù)結(jié)構(gòu),比如一棵多叉樹,下游想要遍歷它的時(shí)候,必須要了解它的實(shí)現(xiàn)原理才行。這顯然也不太友好。

迭代器的出現(xiàn)正是針對(duì)以上這些問(wèn)題,它的含義也很簡(jiǎn)單,有點(diǎn)像是我們遍歷鏈表的時(shí)候用到的cur的指針。永遠(yuǎn)指向當(dāng)前的位置,永遠(yuǎn)知道下一個(gè)位置在哪里。

容器迭代器

我們先從簡(jiǎn)單的元素迭代器開(kāi)始了解它的用途,我們都知道Python當(dāng)中經(jīng)典的幾個(gè)容器:list, tupledict。它們都是一個(gè)可迭代對(duì)象,我們可以直接使用關(guān)鍵字iter獲取一個(gè)對(duì)應(yīng)的迭代器。

我們來(lái)看一個(gè)例子:

arr = [1, 3, 4, 5, 9]

it = iter(arr)

print(next(it))
print(next(it))

這是一個(gè)非常經(jīng)典的例子,我們首先定義了一個(gè)數(shù)組,然后通過(guò)iter關(guān)鍵字獲取了一個(gè)讀取它的迭代器。有了迭代器之后我們可以通過(guò)next關(guān)鍵字獲取迭代器當(dāng)中的下一個(gè)元素,我們一共調(diào)用了兩次next,第一次輸出的結(jié)果是1,第二次的結(jié)果是3。和我們剛才說(shuō)的一樣,我們每一次調(diào)用,它會(huì)自動(dòng)往后移動(dòng)一格,獲取后面一位的數(shù)據(jù)。

這里有一點(diǎn)需要注意,因?yàn)槲覀儎?chuàng)建的數(shù)組當(dāng)中一共只有5個(gè)元素,如果我們調(diào)用it的次數(shù)超過(guò)5次,那么會(huì)引發(fā)超界,Python的解釋器會(huì)拋出StopIterat****ion的error。

除了使用next,我們也可以使用for循環(huán)來(lái)迭代它:

for i in it:
  print(i)

這種用法就和我們用for循環(huán)遍歷元素是一樣的。

自定義迭代器

官方的迭代器的用法就這么多,這也不是它的主要用法,它最主要的用法是我們自己創(chuàng)建迭代器。和之前介紹Python自定義排序的時(shí)候的思路一樣,我們?yōu)轭愄砑由?code>__iter__方法和__next__方法即可。

其中__iter__方法用來(lái)初始化并返回迭代器,關(guān)于它的解釋比較復(fù)雜。在Python當(dāng)中迭代有兩個(gè)概念一個(gè)是iterable,一個(gè)是iterator。協(xié)議規(guī)定iteratble的__iter__方法會(huì)返回一個(gè)iterator。而iterator本身也是一個(gè)iterable對(duì)象,自然也需要實(shí)現(xiàn)__iter__方法。

我知道這么說(shuō)可能聽(tīng)不太明白,我舉個(gè)例子,比如說(shuō)員工和老板,員工沒(méi)有審批權(quán)限,只能轉(zhuǎn)達(dá)給老板。我們把員工比喻成iterable對(duì)象,老板比喻成iterator。

員工面臨一個(gè)問(wèn)題的時(shí)候沒(méi)有權(quán)限處理,只能找來(lái)老板決定。也就是最終決定的是老板,但如果是老板自己發(fā)現(xiàn)的問(wèn)題,他完全可以自己就解決了,不需要再去找其他人。所以說(shuō)我們用iter調(diào)用iterable對(duì)象的__iter__的時(shí)候,會(huì)得到一個(gè)iterator,也就是調(diào)用員工返回老板,然后通過(guò)調(diào)用iterator的__next__來(lái)進(jìn)行迭代。

到這里也就清楚了,只有iterator有__next__方法,而iterable沒(méi)有,并且__iter__返回的是一個(gè)iterator。然而我們定義的已經(jīng)是iterator了,它同時(shí)也是一個(gè)iterable對(duì)象,所以調(diào)用__iter__時(shí)只需要返回self就好了。__next__方法很簡(jiǎn)單,對(duì)應(yīng)迭代器的next方法,用來(lái)返回下一個(gè)迭代的元素。

我們來(lái)看一個(gè)例子:

class PowTwo:
  """Class to implement an iterator
  of powers of two"""

  def __init__(self, max = 0):
    self.max = max

  def __iter__(self):
    self.n = 0
    return self

  def __next__(self):
    if self.n <= self.max:
      result = 2 ** self.n
      self.n += 1
      return result
    else:
      raise StopIteration

這是一個(gè)簡(jiǎn)單的生成2的冪的迭代器,我們?cè)?code>__iter__里為self.n初始化為0,然后返回自身。在__next__里判斷有沒(méi)有迭代結(jié)束,如果結(jié)束的話拋出一個(gè)異常。

我們來(lái)看使用它的例子:

>>> a = PowTwo(4)
>>> i = iter(a)
>>> next(i)
1
>>> next(i)
2
>>> next(i)
4
>>> next(i)
8
>>> next(i)
16
>>> next(i)
Traceback (most recent call last):
...
StopIteration

我們也可以用for循環(huán)來(lái)迭代它:

>>> for i in PowTwo(5):
...   print(i)
... 
1
2
4
8
16
32

迭代器除了可以迭代一個(gè)容器或者是像上面這樣自定義迭代方法之外,還可以用來(lái)迭代生成器。下面就讓我們一起來(lái)看下生成器的概念。

生成器

生成器的概念和迭代器相輔相成,迭代器是生成一個(gè)遍歷數(shù)據(jù)的迭代工具,而生成器則是數(shù)據(jù)生成工具。

舉個(gè)很簡(jiǎn)單的例子,比如說(shuō)斐波那契數(shù)列我們都知道,從第三個(gè)數(shù)開(kāi)始等于前面兩個(gè)數(shù)的和。比如我們想獲取100萬(wàn)個(gè)斐波那契數(shù)列,按照傳統(tǒng)的方法我們需要開(kāi)辟一個(gè)長(zhǎng)度是一百萬(wàn)的數(shù)組,然后按照斐波那契數(shù)列的定義一個(gè)一個(gè)地計(jì)算。顯然這樣會(huì)消耗大量的空間,有沒(méi)有辦法我們和迭代器那樣構(gòu)建一個(gè)生成數(shù)據(jù)的方法,我們每次調(diào)用獲取下一個(gè)結(jié)果呢?這樣我們要多少數(shù)據(jù)就調(diào)用多少次就可以了,從根本上解決了存儲(chǔ)的問(wèn)題。

下面我們來(lái)看怎么定義一個(gè)生成器。

括號(hào)創(chuàng)建法

最簡(jiǎn)單的方法真的很簡(jiǎn)單,和我們創(chuàng)建list基本上一模一樣。

在Python當(dāng)中,我們經(jīng)常這樣初始化一個(gè)數(shù)組:

arr = [i * 3for i in range(10)]

也就是說(shuō)我們把循環(huán)放在list的定義當(dāng)中,這樣Python會(huì)自動(dòng)執(zhí)行里面的循環(huán),然后將所有循環(huán)的結(jié)果進(jìn)行二次計(jì)算后寫入到list當(dāng)中去。我們稍微變形一下,就得到了一個(gè)最簡(jiǎn)單的生成器。

g = (i * 3for i in range(10))

print(next(g))

看清楚了嗎,其實(shí)和list沒(méi)什么差別,只是我們將最外層的括號(hào)從[]換成了()。

這種方法大家應(yīng)該都能看懂,但是可能會(huì)有一個(gè)疑惑。我們這樣做的意義是什么呢?這樣和上面用[]定義有什么區(qū)別呢?

其實(shí)是有區(qū)別的,如果沒(méi)有區(qū)別,那么我們用生成器也就沒(méi)有意義了。它的區(qū)別也就是生成器的意義,簡(jiǎn)單來(lái)說(shuō),我們前文中已經(jīng)說(shuō)過(guò)了當(dāng)定義一個(gè)list的時(shí)候,Python會(huì)自動(dòng)將for循環(huán)執(zhí)行一遍,然后將結(jié)果寫入進(jìn)list當(dāng)中。但是生成器不會(huì),雖然我們也用到了for循環(huán),但是它只是起到了限制個(gè)數(shù)的作用,在執(zhí)行完這一步之后,Python并不會(huì)將for循環(huán)執(zhí)行結(jié)束。只有我們每次調(diào)用next,才會(huì)觸發(fā)它進(jìn)行一次循環(huán)。

不相信的同學(xué)可以試試,看看運(yùn)行一下下面兩個(gè)語(yǔ)句的區(qū)別:

g = (i for i in range(1000000000))
g = [i for i in range(1000000000)]

如果奇怪的事情發(fā)生了,不妨再回到文章來(lái)思考一下。

函數(shù)創(chuàng)建法

上面介紹的方法雖然簡(jiǎn)單,但是不太實(shí)用,因?yàn)楹芏鄷r(shí)候我們想要的數(shù)據(jù)構(gòu)造方法會(huì)比較復(fù)雜,很難用這種形式展現(xiàn)出來(lái)。

所以Python當(dāng)中還為我們提供了一種構(gòu)造生成器的方法,相比起來(lái)要稍微復(fù)雜一點(diǎn)點(diǎn),但是也很好用。我們來(lái)看一個(gè)例子:

def gtr(n):
  for i in range(n):
    yield i

從代碼上來(lái)看,我們好像定義了一個(gè)函數(shù),某種程度上可以這么理解,但是它返回的結(jié)果并不是一個(gè)值,而是一個(gè)生成器[2]。

如果你真的去試了,你會(huì)得到一個(gè)generator類型的實(shí)例,這也是Python自帶的生成器的實(shí)例。

再仔細(xì)觀察一下,你會(huì)發(fā)現(xiàn)這個(gè)函數(shù)當(dāng)中的關(guān)鍵字和一般的不太一樣,它沒(méi)有使用return,而是使用了yield。yield和return在很大程度上很接近,但是又有些不同。

相同點(diǎn)是當(dāng)我們執(zhí)行到y(tǒng)ield時(shí),和return一樣會(huì)將yield之后的內(nèi)容返回給調(diào)用方。比如上面代碼當(dāng)中寫到y(tǒng)ield i,那么我們運(yùn)行next的時(shí)候就會(huì)獲取到這個(gè)i。

不同的地方是,當(dāng)我們下一次再次執(zhí)行的時(shí)候,會(huì)繼續(xù)從上次yield處開(kāi)始往下執(zhí)行。有些類似于遞歸的時(shí)候,底層的遞歸執(zhí)行結(jié)束回到上層的情況。因此如果我們要獲取多個(gè)值,需要在生成器當(dāng)中使用循環(huán)。舉個(gè)例子:

def test():
  n = 0
  whileTrue:
    if n < 3:
      yield n
      n += 1
    else:
      yield10
      
      
if __name__ == '__main__':
  t = test()
  for i in range(10):
    print(next(t))

我們?nèi)绻麍?zhí)行上面這段代碼,前三個(gè)數(shù)是0,1和2,從第四個(gè)數(shù)開(kāi)始一直是10。如果你能看懂這個(gè)例子,一定能明白yield的含義。

yield from

接下來(lái)要介紹的yield from和yield用法差不多,也是從生成器返回一個(gè)結(jié)果,并且下次執(zhí)行的時(shí)候從返回的位置開(kāi)始繼續(xù)執(zhí)行。

但是它有一點(diǎn)和yield不同,我們來(lái)看一個(gè)經(jīng)典的例子。

def g1():
   yield range(5)
def g2():
   yieldfrom range(5)

it1 = g1()
it2 = g2()
for x in it1:
  print(x)

for x in it2:
  print(x)

這兩者打印出來(lái)的結(jié)果是一樣的,但是邏輯完全不同。在第一個(gè)生成器g1當(dāng)中,直接通過(guò)yield返回了一個(gè)迭代器。也就是說(shuō)我們for循環(huán)執(zhí)行的其實(shí)是range(5),而第二個(gè)生成器g2則通過(guò)yield from獲取了range(5)這個(gè)迭代器當(dāng)中的值進(jìn)行的返回。

也就是說(shuō)yield from可以返回一個(gè)迭代器或者是生成器執(zhí)行next之后的結(jié)果。

最后,我們來(lái)看一個(gè)yield from使用的一個(gè)經(jīng)典場(chǎng)景:二叉樹的遍歷:

class Node:

  def __init__(self, key):
    self.key = key
    self.lchild = None
    self.rchild = None
    self.iterated = False
    self.father = None

  def iterate(self):
    if self.lchild isnotNone:
      yieldfrom self.lchild.iterate()
    yield self.key
    if self.rchild isnotNone:
      yieldfrom self.rchild.iterate()

在這個(gè)代碼當(dāng)中我們定義了二叉樹當(dāng)中的一個(gè)節(jié)點(diǎn),以及它對(duì)應(yīng)的迭代方法。由于我們用到了yield來(lái)返回結(jié)果,所以iterate方法本質(zhì)是一個(gè)生成器。再來(lái)看iterate方法內(nèi)部,我們通過(guò)yield from調(diào)用了iterate,所以我們?cè)趫?zhí)行的時(shí)候,它會(huì)自動(dòng)繼續(xù)解析node.lchild的iterate,也就是說(shuō)我們通過(guò)yield from實(shí)現(xiàn)了遞歸。

當(dāng)我們建好樹之后,可以直接使用root.iterate來(lái)遍歷整棵樹。

class Tree:

  def __init__(self):
    #建樹過(guò)程
    self.root = Node(4)
    self.root.lchild = Node(3)
    self.root.lchild.father = self.root
    self.root.rchild = Node(5)
    self.root.rchild.father = self.root
    self.root.lchild.lchild = Node(1)
    self.root.lchild.lchild.father = self.root.lchild
    self.root.rchild.rchild = Node(7)
    self.root.rchild.rchild.father = self.root.rchild

  def iterate(self):
    yieldfrom self.root.iterate()

通過(guò)yield from,我們可以很輕松地利用遞歸的思路來(lái)實(shí)現(xiàn)樹上的生成器。從而可以很方便地以生成器的思路來(lái)遍歷樹上所有的元素。

到這里,關(guān)于Python當(dāng)中迭代器和生成器的知識(shí)就算是講完了,這兩者的概念有些接近,但是又不完全一樣,很多初學(xué)者容易搞混淆。

其實(shí)可以這么理解,迭代器和生成器遍歷元素的方式是一樣的,都是通過(guò)調(diào)用next來(lái)獲取下一個(gè)元素。我們通過(guò)yield創(chuàng)建函數(shù),返回的結(jié)果其實(shí)就是生成器生成的數(shù)據(jù)的迭代器。也就是說(shuō)迭代器只是迭代和獲取數(shù)據(jù)的,但是并不能無(wú)中生有地創(chuàng)造數(shù)據(jù)。而生成器的主要作用是創(chuàng)造數(shù)據(jù),它生成出來(lái)的數(shù)據(jù)是以迭代器的形式返回的。

舉個(gè)例子,你開(kāi)了一個(gè)奶茶店,通過(guò)奶茶店每個(gè)月可以在銀行賬戶里獲得一筆收入。迭代器就是這個(gè)賬戶,通過(guò)它你可以獲得一筆一筆的收入。而奶茶店則是一個(gè)生成器,它產(chǎn)出數(shù)據(jù),但是是以迭代器的形式返回給你的,也就是以銀行賬戶的方式給你收入。我們拿到銀行卡并不知道它里面的錢是怎么賺來(lái)的,只能看到錢,也就是說(shuō)我們并不知道迭代器背后數(shù)據(jù)的邏輯。但是生成器我們是清楚的,因?yàn)殄X(生產(chǎn)邏輯)是我們親自賺來(lái)的。

以上就是五分鐘帶你搞懂python 迭代器與生成器的詳細(xì)內(nèi)容,更多關(guān)于python 迭代器與生成器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論