Python系列之 迭代器和生成器

来源:https://www.cnblogs.com/reposkeeper-wx/archive/2018/04/30/Python-xi-lie-zhi-die-dai-qi-he-sheng-cheng-qi.html
-Advertisement-
Play Games

很多Python的程式員都會混淆 迭代器 和 生成器 的概念和作用,分不清到底兩個有什麼區別。今天我們來好好說一說這兩個概念。迭代器(Iterator)Iterator PatternIterator 是一種設計模式,它的作用是,提供一種順序訪問一個聚合對象中的各個元素,但又不需要暴露出其內部實現的... ...


很多Python的程式員都會混淆 迭代器生成器 的概念和作用,分不清到底兩個有什麼區別。今天我們來好好說一說這兩個概念。

迭代器(Iterator)

Iterator Pattern

Iterator 是一種設計模式,它的作用是,提供一種順序訪問一個聚合對象中的各個元素,但又不需要暴露出其內部實現的方法。它是一種惰性的獲取數據的方法,我們不需要一次把所有的數據載入記憶體,這樣可以避免數據集太大,記憶體無法全部裝載的麻煩。
這種應用場景,比如:讀取一個大文件,分析每一行的關鍵字

一個最簡單的迭代器模式,表現為一個介面,介面中包含兩個方法:

  1. Next() 返回下一個元素
  2. hasNext() 返回是否還有下一個元素

實現了這種兩個方法的對象就是一個迭代器。

Python中的Iterator

在很多時候,Python程式員會忽略 迭代器(Iterator) 和 可迭代對象(Iterable Object) 的區別。

其實,我們要好好的區分一下他們兩個。

可迭代對象(Iterable Object)

可迭代對象是表示一個對象,擁有一次返回一個他自己的數據元素的能力。

例如:

In [1]: a = [1, 2, 3, 4, 5]

In [2]: for i in a:
   ...:     print(i)
   ...:
1
2
3
4
5

In [3]: b = {"first":1, "second":2, "third":3}

In [4]: for i in b:
   ...:     print i
   ...:
second
third
first

上面的代碼通過迭代的方式輸出了list中所有的元素,和dict中所有的key。所以,我們把list和dict叫做可迭代對象(不是迭代器)。

在Python中,所有的集合都可以迭代。在語言內部,迭代器支持下麵列出的操作:

  • for迴圈
  • 遍歷文件、目錄
  • 列表推導、字典推導和集合推導
  • 元組拆包
  • 調用函數時,使用 * 拆包實參
  • 構建和擴展集合類型

所以可以看到,迭代操作在python中很多地方都很重要。

序列可以迭代的原因

這依賴一個buildin-function iter()。假如解釋器要迭代對象x,則會調用 iter() 產生一個迭代器,進行迭代。

內置的 iter 函數有以下作用:

  1. 檢查對象是否實現了 __iter__ 方法,如果實現了就調用它,獲取一個迭代器。
  2. 如果沒有實現 __iter__ 方法, 但是實現了 __getitem__ 方法, Python 會創建一個迭代 器,嘗試按順序(從索引 0 開始)獲取元素。
  3. 如果嘗試失敗, Python 拋出 TypeError 異常, 通常會提示“X object is not iterable”。
In [8]: x = 2
In [9]: iter(x)
-----------------------------------------------------
TypeError           Traceback (most recent call last)
<ipython-input-9-128770259dbe> in <module>()
----> 1 iter(x)

TypeError: 'int' object is not iterable

標準序列都實現了 __getitem__ 方法。 其實,它們也都實現了 __iter__ 方法,因此你也應該這麼做。之所以都實現 __getitem__ 是因為要向後相容,但後續可能廢棄。

如何實現可迭代對象

自己創建的 Object 如何變成一個可迭代的對象呢?如何自己創建一個迭代器呢?其實非常簡單。

對於可迭代對象,需要滿足下麵兩個要求的任意一個(原因參見上面):

  1. 擁有 __getitem__ 方法;接受一個參數 index
  2. 擁有 __iter__ 方法;返回一個 Iterator

例:

#!/usr/bin/env python


class MyIterableObject():

    def __init__(self, s):
        self.seq = s.split(' ')

    def __getitem__(self, index):
        return self.seq[index]
        
    def __iter__(self):  
        return MyIterator(self.seq) # MyIterator的具體實現參見後面


if __name__ == '__main__':

    mio = MyIterableObject("a b c d e f g")

    for i in mio:
        print(i)

迭代器(Iterator)

當用 iter 函數獲取到一個迭代器之後,就可以操作迭代器來獲取對象的數據了。

使用 next() 方法來一個個的獲取元素。當所有元素獲取完畢,繼續調用 next() 方法的話,就會拋出一個 StopIteration 的異常。

如下:

In [13]: a = [1, 2, 3, 4, 5]
In [14]: i = iter(a)
In [15]: while True:
    ...:     print(next(i))
    ...:
1
2
3
4
5
-----------------------------------------------------
StopIteration       Traceback (most recent call last)
<ipython-input-15-ac43f8f9aeeb> in <module>()
      1 while True:
----> 2     print(next(i))
      3

StopIteration:

python的迭代器較為簡單,它並不支持重新定位到開始這樣的操作。如果一個迭代器一旦開始使用,如果想要從最開始讀取的話,只能創建一個新的迭代器了。

如何實現迭代器

標準的python迭代器需要實現兩個方法:

  1. __iter__ 返回迭代器本身
  2. next() 返回數據集中的下一個元素。如果沒有下一個了,則拋出一個 StopIteration

TIPS:
在 python3中 next() 方法的名稱,改為了 __next__ ,但是使用 python2 的方式依然可行。

例:


class MyIterator():

    def __init__(self, s):
        
        self.seq = s
        self.len = len(self.seq)
        self.index = 0

    def __iter__(self):
        return self

    def next(self):
        try:
            n = self.seq[self.index]
        except IndexError:
            raise StopIteration

        self.index += 1

        return n

這裡有一點需要註意:迭代器模式描述中,需要有一個方法來判斷是否是最後一個元素,在python中使用異常代替了這個函數。在我們使用迭代器的過程中,捕獲這個異常即可。如果使用 buildin 的 for .. in 方式的話,它會自動幫我們捕獲。

生成器(Generator)

首先,我們平常說起來 Generator 這個東西的時候,其實,它一般指代兩個東西:

  1. Generator Function: 一個函數,在定義時使用了 yield 關鍵字,則成為這個函數為 生成器函數
  2. Generator Object: 由 Generator Function 生成的,是一個特殊的 Iterator。它包裝了 生成器函數 的定義體,並實現了 __iter__next 兩個方法,符合 Iterator 的協議。

生成器和迭代器最大的不同在哪裡呢?

主要是對於值產生的方法不一樣。當使用迭代器時,所有要迭代的元素必須是已經存在的。而對於生成器來說,每個值不必已經存在,可以在執行的過程中計算(生成)出來。

比如:用生成器 生成一個等比數列

def arithmetic_progression(base, dif, count):
    for n in range(count):
        yield base + dif * n


if __name__ == '__main__':

    for i in arithmetic_progression(1, 3, 10):
        print(i)

可以看到這個等比數列是不存在的,是在迭代的過程中每次執行到 yield 的時候,計算出來的。

可以達到這樣的特性歸功於 yield 關鍵字。它可以將執行的函數暫停,並返回值,下一次從中斷的地方繼續。它的執行流程如下:

  1. 使用 next 調用 生成器函數
  2. 函數 執行到 yield,會返回一個值,並暫停函數
  3. 重覆 1-2 步,直到所有的值都返回完畢
  4. 如果使用 next,則會拋出 StopIteration

代碼驗證如下:

In [21]: def test():
    ...:     yield 1
    ...:     yield 2
    ...:     yield 3
    ...:

In [22]: gen = test()

In [23]: next(gen)
Out[23]: 1

In [24]: next(gen)
Out[24]: 2

In [25]: next(gen)
Out[25]: 3

In [26]: next(gen)
-----------------------------------------------------
StopIteration       Traceback (most recent call last)
<ipython-input-26-8a6233884a6c> in <module>()
----> 1 next(gen)

StopIteration:

用生成器代替迭代器

現在我們用生成器來替代上面的 迭代器方案 MyIterableObject

class MyGenerator():

    def __init__(self, s):
        self.seq = s.split(' ')

    def __iter__(self):
        for s in self.seq:
            yield s

代碼簡化了很多,我們不需要再自己創建 Iterator 對象,yield會幫我們做這些。

迭代器工具集(itertools)

雖然,生成器的使用已經夠簡單了,但是像python這種節省你生命時間的語言,怎麼會沒有更進一步的包裝出來?

python內置了非常多的生成器函數,比如遍歷文件夾的 os.walk,工具類的有 mapenumerate 等等。

python還有一個官方庫,叫做 itertools,它包含了 19個 生成器函數,可以組合完成各樣的功能。

結尾

以上,就是 迭代器和生成器的區別。其實,這兩個東西並不難理解。但是,這裡面有幾個比較容易混淆的概念。只要搞清楚了這些概念,就能區分得很清楚啦!


作者和出處(reposkeeper) 授權分享 By CC BY-SA 4.0 Creative Commons License

關註微信公眾號,獲取新文章的推送!
qrcode


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

-Advertisement-
Play Games
更多相關文章
  • 一,HTTP的歷史 1,HTTP的概念 HTTP(HyperText Transfer Protocol,超文本傳輸協議)是一種通信協議,它允許將超文本標記語言(HTML)文檔從Web伺服器傳送到客戶端(如瀏覽器) 它是一個應用層的協議,承載於TCP之上 由請求和響應構成,是一個標準的客戶端伺服器模 ...
  • 介紹 Vue.js是一套構建用戶界面的漸進式框架。Vue 只關註視圖層,採用自底向上增量開發的設計。Vue 的目標是通過儘可能簡單的 API 實現響應的數據綁定和組合的視圖組件。 安裝node.js 從node官網下載並安裝node,安裝步驟很簡單,只要一路“next”就可以了。安裝完成後,打開命令 ...
  • 新增的標簽和屬性 1、結構標簽 article section aside nav header footer hgroup figure address 2、媒體標簽 video audio embed 3、表單屬性 email url number range ...
  • 相關內容: 首發時間:2018-03-02 修改: 什麼是css選擇器: 介紹: css可以設置標簽的樣式,為了更好的設置樣式以及為了方便給某些標簽指定樣式(批量的給某些標簽增加樣式),所以有了css選擇器,css選擇器可以篩選出指定的標簽。篩選出來之後就可以給對應的標簽設置樣式。 css選擇器的語 ...
  • <!DOCTYPE html><html><head lang="en"> <meta charset="UTF-8"> <title></title> <link rel="stylesheet" href="../css/reset.css"/> <link rel="stylesheet" h ...
  • 1.模型管理 :web線上流程設計器、預覽流程xml、導出xml、部署流程 2.流程管理 :導入導出流程資源文件、查看流程圖、根據流程實例反射出流程模型、激活掛起 3.運行中流程:查看流程信息、當前任務節點、當前流程圖、作廢暫停流程、指派待辦人 4.歷史的流程:查看流程信息、流程用時、流程狀態、查看 ...
  • Timer是一種定時器工具,用來在一個後臺線程計劃執行指定任務。它可以計劃執行一個任務一次或反覆多次。 TimerTask一個抽象類,它的子類代表一個可以被Timer計劃的任務。 ...
  • 安裝docker 好慢....一個小時吧... 啟動docker 先執行命令docker version來來一下: 發現沒有啟動docker server, 執行命令: 然後再查看一下 docker version docker pull命令 pull命令用於拉取鏡像 可以使用docker pull ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...