生成器和迭代器

来源:https://www.cnblogs.com/xiaoyafei/archive/2018/05/22/9074340.html
-Advertisement-
Play Games

生成器和迭代器 列表生成式 現在有這麼一個需求,要將 這個列表中的每個元素都加1,那麼需要怎麼實現呢?你可能會想到這幾種方式: 其實還有一種寫法,如下: 通過列表生成式,我們可以直接創建一個列表。但是受到記憶體限制,列表容量肯定是有限制的,就像是遞歸,最大遞歸深度python就對其作了限制。而且,創建 ...


生成器和迭代器

列表生成式

現在有這麼一個需求,要將[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]這個列表中的每個元素都加1,那麼需要怎麼實現呢?你可能會想到這幾種方式:


a = list(range(0,10))
# print(a)


# 1.二逼青年版
b = []
for i in a:
    b.append(i+1)

print(b)


# 2.普通青年版

c = a
for index,i in enumerate(c):
    a[index] += 1

print(c)


# 3.文藝青年版
d = a
d = map(lambda x:x,d)
for i in d:
    print(i)

其實還有一種寫法,如下:

# 4.裝逼青年版
e = a
e = [i+1 for i in range(10)]
print(e)

通過列表生成式,我們可以直接創建一個列表。但是受到記憶體限制,列表容量肯定是有限制的,就像是遞歸,最大遞歸深度python就對其作了限制。而且,創建一個包含100萬個元素的列表,不僅占用很大的存儲空間,如果我們僅僅想訪問的是前幾個元素的話,那麼後面絕大多數元素占用的空間就是浪費了。

就比如說:我們家是賣手機的,我們找到了專門製作手機的廠商,告訴他,我這個手機要1000萬台,你們給我做吧,然而廠商考慮,和你說:這款手機我不知道能不能賣這麼多,你看我們就1000台分一批給你生產出來,如果你還要,那麼再給你生產1000台,你考慮到這也是一個問題,所以便同意了。

所以,如果列表元素可以按照某種演算法推算出來,那我們是否可以在迴圈中不斷推算出後面的元素呢?這樣就不需要去創建一個完整的list了,從而節省了大量的空間,在python中,這樣一邊迴圈一邊計算的機制,稱為:生成器:generator

創建一個generator有很多種辦法,第一種方法很簡單,只需要把一個列表生成式的[]改成(),就創建了一個generator:

l = [x*x for x in range(10)]
print(l)  # l = [x*x for x in range(10)]

g = (x*x for x in range(10))
print(g)

運行結果為:
<generator object <genexpr> at 0x000001E28508FFC0>

創建l和g的卻別僅在於最外層的[]和(),l是一個list,而g是一個generator

我們可以直接列印出來l列表中的每一個元素,但是怎麼列印出generator的每一個元素呢?

如果需要列印的話,我們就需要使用next()函數獲得generator的下一個返回值:

g = (x*x for x in range(10))
print(g)
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))

運行結果為:
<generator object <genexpr> at 0x000002586EF82518>
0
1
4
9
16

我們講過,generator保存的是演算法,每次調用next(g)後就能計算出g的下一個元素的值,直到計算出最後一個元素,沒有更多的元素時,會拋出StopIteration異常

當然,每需要獲取下一個元素的值,就需要使用一次next的話,就有點太變態了,所以,正確的方法時for迴圈,因為generator也是可迭代的

g = (x*x for x in range(10))
for i in g:
    print(i)
    
運行結果為:
0
1
4
9
16
25
36
49
64
81

genera足夠強大,如果推算的演算法比較複雜,用類似列表生成式的for迴圈無法實現的時候,還可以用函數來實現,著名的斐波那契數列用列表生成式寫不出來,但是用函數把它列印出來卻很容易:

def fib(max):
    n,a,b = 0,1,1  # 分別賦值
    while n < max:  # 條件判斷
        print(b)  
        a,b = b,a+b  # 賦值,把b賦值給a,把a+b賦值給b,在這裡需要註意的是,是相加之前的結果,不是把b賦值給a後,然後再相加
        n += 1
    return 'Done'

fib(10)

運行結果如下:
1
2
3
5
8
13
21
34
55
89

仔細觀察:可以看出,fib函數是定義了斐波那契數列的推算規則,可以從第一個元素開始,推算出後續任意的元素,這種邏輯其實非常類似generator。

也就是說,上面的函數和generator只有一步之遙。要把fib函數編程生成器generator,只需要把print(b)改成yield b就可以了:

def fib(max):
    n,a,b = 0,1,1
    while n < max:
        yield b
        # 出現了yield就相當於把這段程式變成了生成器
        # yield把值返回到外部,而且不用終止
        # yield把函數的執行過程凍結到這一步,並且把b的值返回給外面的next()
        a,b = b,a+b
        n += 1
        
    return 'Done'

f = fib(10)
print(next(f))
print(next(f))
print(next(f))
print(next(f))
print(next(f))
print(next(f))

運行結果如下
1
2
3
5
8
13

想要獲取下一個元素的值,用next就可以了

這裡最難理解的是generator和函數的執行順序流程不一樣。函數是順序執行,遇到return語句或者最後一行函數語句就返回(結束),而編程generator的函數,在每次調用next()的時候執行,遇到yield語句返回,再次被next()調用時從上次返回的yield語句出繼續執行。

def fib(max):
    n,a,b = 0,1,1
    while n < max:
        yield b
        a,b = b,a+b
        n += 1

    return 'Done'
data = fib(10)
print(data)

print(data.__next__())
print('乾點別的事')
print(data.__next__())

運行結果如下:
<generator object fib at 0x0000020D5B02FFC0>
1
乾點別的事
2

在上面的fib例子中,我們在迴圈過程中不斷調用yield,就會不斷中斷,當然要給迴圈設置一個條件來退出迴圈,不然就會產生一個無限數列出來。同樣的,把函數改成generator後,我們都是使用for迴圈來獲取返回值

def fib(max):
    n,a,b = 0,1,1
    while n < max:
        yield b
        a,b = b,a+b
        n += 1

    return 'Done'
data = fib(10)
for i in data:
    print(i)
    
運行結果如下:
1
2
3
5
8
13
21
34
55
89

但是我們發現了,如果使用for迴圈的話,根本拿不到generator的return語句返回值。所以如果想拿到返回的值的話,必須要捕捉異常

def fib(max):
    n,a,b = 0,1,1
    while n < max:
        yield b
        a,b = b,a+b
        n += 1

    return 'Done'
data = fib(10)
while True:
    try:
        x = next(data)
        print('data:',x)

    except StopIteration as e:
        print('Generator return value:',e.value)
        break
        
運行結果:
data: 1
data: 2
data: 3
data: 5
data: 8
data: 13
data: 21
data: 34
data: 55
data: 89

迭代器

我們已經直到,可以直接作用域for迴圈的數據類型有以下幾種:

一類是集合數據類型,如list、tuple、dict、set、str等
另一類是generator,包括生成器和帶yield 的 generator fgunction等

這些可以直接作用於for迴圈的對象統稱為可迭代對象:Iterable

可以使用isinstance()判斷一個對象是否是Iterstance對象:

In [1]: from  collections import Iterable



In [2]: isinstance([],Iterable)
In [2]: isinstance([],Iterable)
Out[2]: True

In [3]: isinstance((),Iterable)
Out[3]: True

In [4]: isinstance({},Iterable)
Out[4]: True

In [6]: isinstance('abc',Iterable)
Out[6]: True

In [7]: isinstance((x for x in range(10)),Iterable)
Out[7]: True

而生成器不但可以作用域for迴圈,還可以被next()函數不斷調用並返回下一個值,直到拋出錯誤

可以被next()函數調用並不斷返回下一個值的對象稱為迭代器:Iterator
可以使用isinstance()判斷一個對象是否是Iterator對象

In [1]: from collections import Iterator

In [2]: isinstance((),Iterator)
Out[2]: False

In [3]: isinstance([],Iterator)
Out[3]: False

In [4]: isinstance({},Iterator)
Out[4]: False

In [5]: isinstance((x for x in range(10)),Iterator)
Out[5]: True

生成器都是迭代器,但列表、元組、字典雖然是可迭代對象,但不是迭代器

把列表、元組、字典等可迭代對象轉換成迭代器可以使用iter()函數

In [6]: isinstance(iter([]),Iterator)
Out[6]: True

In [7]: isinstance(iter({}),Iterator)
Out[7]: True

你可能會問,為什麼List,tuole,dict等數據類型不是迭代器呢?

這是因為python的迭代器對象表示的是一個數據流,迭代器對象可以被next()函數調用並不斷返回下一個數據,直到沒有數據的時候拋出異常。可以把這個數據流看作是一個有序序列,但我們卻不能提高知道序列的長度,只能不斷通過next()函數實現按需計算下一個數據,所以,Iterator的計算是惰性的,只需在返回下一個數據時它才會計算。

Iterator甚至可以表示一個無限大的數據流,而list時永遠不可能存儲全體自然數的。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本博客起源於博主的大三NoSQL課程設計,採用python+MongoDB結合方式,將數據從txt文件導入MongoDB之中,再將其取出以作圖。主要技術是採用python與MongoDB結合存儲讀取方案,所以本博客截取了課設的部分內容,主要講解python操作MongoDB方案實現,以給想要學習py ...
  • list是一種有序的集合,可以隨時添加和刪除其中的元素。 用len()函數可以獲得list元素的個數。 用索引來訪問list中每一個位置的元素,索引是從0開始的。如果要取最後一個元素,除了計算索引位置外,還可以用-1作索引,直接獲取最後一個元素。以此類推,可以獲取倒數第2個、倒數第3個。 list是 ...
  • 6.16.3 使用嵌套迴圈,按下麵格式列印字母: F FE FED FEDC FEDCB FEDCBA 1 #include <stdio.h> 2 3 int main() 4 { 5 const int ROWS = 6; 6 7 for (int row(0); row != ROWS; ++ ...
  • Description The course of Software Design and Development Practice is objectionable. ZLC is facing a serious problem .There are many points in K-dimen ...
  • 方法一: public void reverse(String arr[]){ for(int i=arr.length-1;i>=0;i--){ System.out.println(arr[i]); } } 方法二: public static void reverse2(String arr[ ...
  • org.apache.tiles.template.NoSuchAttributeException: Attribute 'header' not found. ...
  • 有關圖形庫的學習筆記 1.安裝 ww.easys.cn 2.創建win32控制台應用程式 .cpp文件(圖形庫必須創建cpp文件) *重點 3.安裝好後 重啟一下vs 圖形庫 是一些函數的集合 作用是做一些界面和滑鼠操作 函數-->幫助文檔 1.基本概念 顏色 RGB值 -->RGB值表示一種顏色 ...
  • 在最早的時候只有127個字元被編碼到電腦里,也就是大小寫英文字母、數字和一些符號,這個編碼被成為ASCII編碼。 但是要處理中文顯然一個位元組是不夠的,至少需要兩個位元組,而且還不能和ASCII編碼衝突,所以中國制定了GB2312編碼,用來把中文編進去。 世界上有很多語言,各國有各國的標準,就會不可避 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...