【Python Cookbook】迭代器與生成器(四)

目錄
案例
目錄
案例
迭代器與生成器(一)1.手動遍歷迭代器
2.代理迭代
3.使用生成器創建新的迭代模式
4.實現迭代器協議
迭代器與生成器(三)9.排列組合的迭代
10.序列上索引值迭代
11.同時迭代多個序列
12.不同集合上元素的迭代
迭代器與生成器(二)5.反向迭代
6.帶有外部狀態的生成器函數
7.迭代器切片
8.跳過可迭代對象的開始部分
迭代器與生成器(四)13.創建數據處理管道
14.展開嵌套的序列
15.順序迭代合并后的排序迭代對象
16.迭代器代替 while 無限循環

迭代器與生成器(四)

  • 13.創建數據處理管道
  • 14.展開嵌套的序列
  • 15.順序迭代合并后的排序迭代對象
  • 16.迭代器代替 while 無限循環

13.創建數據處理管道

你想以數據管道(類似 Unix 管道)的方式迭代處理數據。比如,你有個大量的數據需要處理,但是不能將它們一次性放入內存中。

生成器函數是一個實現管道機制的好辦法。為了演示,假定你要處理一個非常大的日志文件目錄:

foo/access-log-012007.gzaccess-log-022007.gzaccess-log-032007.gz...access-log-012008
bar/access-log-092007.bz2...access-log-022008

假設每個日志文件包含這樣的數據:

124.115.6.12 - - [10/Jul/2012:00:18:50 -0500] "GET /robots.txt ..." 200 71
210.212.209.67 - - [10/Jul/2012:00:18:51 -0500] "GET /ply/ ..." 200 11875
210.212.209.67 - - [10/Jul/2012:00:18:51 -0500] "GET /favicon.ico ..." 404 369
61.135.216.105 - - [10/Jul/2012:00:20:04 -0500] "GET /blog/atom.xml ..." 304 -
...

為了處理這些文件,你可以定義一個由多個執行特定任務獨立任務的簡單生成器函數組成的容器。就像這樣:

import os
import fnmatch
import gzip
import bz2
import redef gen_find(filepat, top):'''在目錄樹中查找所有匹配指定通配符模式的文件名。'''for path, dirlist, filelist in os.walk(top):           # 使用 os.walk(top) 遞歸遍歷 top 目錄及其子目錄。for name in fnmatch.filter(filelist, filepat):     # 對于每個目錄,篩選出匹配 filepat 的文件名。yield os.path.join(path, name)                  # 使用 yield 生成每個匹配文件的完整路徑(拼接目錄路徑和文件名)。def gen_opener(filenames):'''按順序打開一系列文件,每次生成一個文件對象。文件會在下一次迭代前關閉。'''for filename in filenames:if filename.endswith('.gz'):f = gzip.open(filename, 'rt')elif filename.endswith('.bz2'):f = bz2.open(filename, 'rt')else:f = open(filename, 'rt')yield ff.close()       # 由于生成器會在 yield 后暫停,文件對象的實際關閉是在調用代碼繼續下一次迭代時發生的。def gen_concatenate(iterators):'''將多個迭代器連接成一個單一的序列。'''for it in iterators:yield from itdef gen_grep(pattern, lines):'''在行序列中搜索匹配正則表達式的行。'''pat = re.compile(pattern)for line in lines:if pat.search(line):yield line

現在你可以很容易的將這些函數連起來創建一個處理管道。比如,為了查找包含單詞 python 的所有日志行,你可以這樣做:

lognames = gen_find('access-log*', 'www')
files = gen_opener(lognames)
lines = gen_concatenate(files)
pylines = gen_grep('(?i)python', lines)      # (?i)是正則表達式的忽略大小寫標志,因此會匹配 python、Python、PYTHON 等。
for line in pylines:print(line)

如果將來的時候你想擴展管道,你甚至可以在生成器表達式中包裝數據。比如,下面這個版本計算出傳輸的字節數并計算其總和。

lognames = gen_find('access-log*', 'www')
files = gen_opener(lognames)
lines = gen_concatenate(files)
pylines = gen_grep('(?i)python', lines)bytecolumn = (line.rsplit(None,1)[1] for line in pylines)
bytes = (int(x) for x in bytecolumn if x != '-')
print('Total', sum(bytes))
  • line.rsplit(None, 1)
    • None 表示按任意空白字符(空格、制表符等)分割。
    • 1 表示最多分割一次,從右側開始分割。
    • 例如:"1234 python 512".rsplit(None, 1)['1234 python', '512']
  • [1] 取分割后的最后一列(如'512')。
  • bytecolumn 是一個生成器表達式,生成所有行的最后一列值。

示例輸出

# 輸入pylines:
# ["1234 python 512", "42 PYTHON 1024", "python - 2048"]
bytecolumn = (line.rsplit(None,1)[1] for line in pylines)
# 生成的bytecolumn內容:
# ['512', '1024', '2048']
# 輸入bytecolumn:
# ['512', '1024', '2048']
bytes = (int(x) for x in bytecolumn if x != '-')
# 生成的bytes內容:
# 512, 1024, 2048

以管道方式處理數據可以用來解決各類其他問題,包括解析,讀取實時數據,定時輪詢等。

為了理解上述代碼,重點是要明白 yield 語句作為數據的 生產者,而 for 循環語句作為數據的 消費者。當這些生成器被連在一起后,每個 yield 會將一個單獨的數據元素傳遞給迭代處理管道的下一階段。在例子最后部分,sum() 函數是最終的程序驅動者,每次從生成器管道中提取出一個元素。

這種方式一個非常好的特點是每個生成器函數很小并且都是獨立的。這樣的話就很容易編寫和維護它們了。很多時候,這些函數如果比較通用的話可以在其他場景重復使用。并且最終將這些組件組合起來的代碼看上去非常簡單,也很容易理解。

使用這種方式的內存效率也不得不提。上述代碼即便是在一個超大型文件目錄中也能工作的很好。事實上,由于使用了迭代方式處理,代碼運行過程中只需要很小很小的內存。

在調用 gen_concatenate() 函數的時候你可能會有些不太明白。這個函數的目的是將輸入序列拼接成一個很長的行序列。itertools.chain() 函數同樣有類似的功能,但是它需要將所有可迭代對象作為參數傳入。

在上面這個例子中,你可能會寫類似這樣的語句 lines = itertools.chain(*files),這將導致 gen_opener() 生成器被提前全部消費掉。但由于 gen_opener() 生成器每次生成一個打開過的文件,等到下一個迭代步驟時文件就關閉了,因此 chain() 在這里不能這樣使用。上面的方案可以避免這種情況。

files = gen_opener(['a.txt', 'b.gz'])  # 生成器,每次 yield 一個文件對象
lines = itertools.chain(*files)        # 錯誤!files 會被全部消費,文件可能已關閉
for line in lines:                     # 實際迭代時文件已關閉,可能報錯print(line)
files = gen_opener(['a.txt', 'b.gz'])  # 生成器,每次 yield 一個文件對象
lines = gen_concatenate(files)         # 惰性拼接,按需打開文件
for line in lines:                     # 安全迭代print(line)

gen_concatenate() 函數中出現過 yield from 語句,它將 yield 操作代理到父生成器上去。語句 yield from it 簡單的返回生成器 it 所產生的所有值。

🚀 對 yield from 仍有疑問的,可以參考博主的這篇博客《yield from 功能解析》。

最后還有一點需要注意的是,管道方式并不是萬能的。有時候你想立即處理所有數據。然而,即便是這種情況,使用生成器管道也可以將這類問題從邏輯上變為工作流的處理方式。

14.展開嵌套的序列

你想將一個多層嵌套的序列展開成一個單層列表。

可以寫一個包含 yield from 語句的遞歸生成器來輕松解決這個問題。比如:

from collections import Iterabledef flatten(items, ignore_types=(str, bytes)):for x in items:if isinstance(x, Iterable) and not isinstance(x, ignore_types):yield from flatten(x)else:yield xitems = [1, 2, [3, 4, [5, 6], 7], 8]
# Produces 1 2 3 4 5 6 7 8
for x in flatten(items):print(x)

在上面代碼中, isinstance(x, Iterable) 檢查某個元素是否是可迭代的。如果是的話, yield from 就會返回所有子例程的值。最終返回結果就是一個沒有嵌套的簡單序列了。

額外的參數 ignore_types 和檢測語句 isinstance(x, ignore_types) 用來將字符串和字節排除在可迭代對象外,防止將它們再展開成單個的字符。這樣的話字符串數組就能最終返回我們所期望的結果了。比如:

>>> items = ['Dave', 'Paula', ['Thomas', 'Lewis']]
>>> for x in flatten(items):
...     print(x)
...
Dave
Paula
Thomas
Lewis
>>>

語句 yield from 在你想在生成器中調用其他生成器作為子例程的時候非常有用。如果你不使用它的話,那么就必須寫額外的 for 循環了。比如:

def flatten(items, ignore_types=(str, bytes)):for x in items:if isinstance(x, Iterable) and not isinstance(x, ignore_types):for i in flatten(x):yield ielse:yield x

盡管只改了一點點,但是 yield from 語句看上去感覺更好,并且也使得代碼更簡潔清爽。

之前提到的對于字符串和字節的額外檢查是為了防止將它們再展開成單個字符。如果還有其他你不想展開的類型,修改參數 ignore_types 即可。

最后要注意的一點是, yield from 在涉及到基于協程和生成器的并發編程中扮演著更加重要的角色。

15.順序迭代合并后的排序迭代對象

你有一系列排序序列,想將它們合并后得到一個排序序列并在上面迭代遍歷。

heapq.merge() 函數可以幫你解決這個問題。比如:

>>> import heapq
>>> a = [1, 4, 7, 10]
>>> b = [2, 5, 6, 11]
>>> for c in heapq.merge(a, b):
...     print(c)
...
1
2
4
5
6
7
10
11

heapq.merge 可迭代特性意味著它不會立馬讀取所有序列。這就意味著你可以在非常長的序列中使用它,而不會有太大的開銷。比如,下面是一個例子來演示如何合并兩個排序文件:

with open('sorted_file_1', 'rt') as file1, \open('sorted_file_2', 'rt') as file2, \open('merged_file', 'wt') as outf:for line in heapq.merge(file1, file2):outf.write(line)

有一點要強調的是 heapq.merge() 需要 所有輸入序列必須是排過序的。特別的,它并不會預先讀取所有數據到堆棧中或者預先排序,也不會對輸入做任何的排序檢測。它僅僅是檢查所有序列的開始部分并返回最小的那個,這個過程一直會持續直到所有輸入序列中的元素都被遍歷完。

16.迭代器代替 while 無限循環

你在代碼中使用 while 循環來迭代處理數據,因為它需要調用某個函數或者和一般迭代模式不同的測試條件。能不能用迭代器來重寫這個循環呢?

一個常見的 IO 操作程序可能會像下面這樣:

CHUNKSIZE = 8192def reader(s):while True:data = s.recv(CHUNKSIZE)if data == b'':breakprocess_data(data)

這種代碼通常可以使用 iter() 來代替,如下所示:

def reader2(s):for chunk in iter(lambda: s.recv(CHUNKSIZE), b''):pass# process_data(data)

如果你懷疑它到底能不能正常工作,可以試驗下一個簡單的例子。比如:

>>> import sys
>>> f = open('/etc/passwd')
>>> for chunk in iter(lambda: f.read(10), ''):
...     n = sys.stdout.write(chunk)
...
nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false
root:*:0:0:System Administrator:/var/root:/bin/sh
daemon:*:1:1:System Services:/var/root:/usr/bin/false
_uucp:*:4:4:Unix to Unix Copy Protocol:/var/spool/uucp:/usr/sbin/uucico
...
>>>

iter 函數一個鮮為人知的特性是它接受一個可選的 callable 對象和一個標記(結尾)值作為輸入參數。當以這種方式使用的時候,它會創建一個迭代器,這個迭代器會不斷調用 callable 對象直到返回值和標記值相等為止。

iter(callable, sentinel) 的用法

  • 功能
    創建一個迭代器,重復調用 callable(可調用對象)直到它返回 sentinel(哨兵值)。
  • 在此代碼中
    • callablelambda: f.read(10):每次調用時從文件 f 讀取 10 字節。
    • sentinel'':當 f.read(10) 返回空字符串時(表示文件結束),迭代停止。
  • 效果
    將文件分塊讀取,每次 10 字節,避免一次性加載大文件到內存。

這種特殊的方法對于一些特定的會被重復調用的函數很有效果,比如涉及到 I/O 調用的函數。舉例來講,如果你想從套接字或文件中以數據塊的方式讀取數據,通常你得要不斷重復的執行 read()recv() ,并在后面緊跟一個文件結尾測試來決定是否終止。這節中的方案使用一個簡單的 iter() 調用就可以將兩者結合起來了。其中 lambda 函數參數是為了創建一個無參的 callable 對象,并為 recvread() 方法提供了 size 參數。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/907451.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/907451.shtml
英文地址,請注明出處:http://en.pswp.cn/news/907451.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

React 播客專欄 Vol.16|useRef 和 useMemo 是干嘛的?

👋 歡迎回到《前端達人 React 播客書單》第 16 期(正文內容為學習筆記摘要,音頻內容是詳細的解讀,方便你理解),請點擊下方收聽 視頻版 🎙 歡迎來到《前端達人 播客書單》第 16 期。 今天我們來…

漫談英偉達GPU架構進化史:從Celsius到Blackwell

在英偉達官網,我們可以清晰地看到其從1999年Celsius到2024年Blackwell的20+代架構演進。這一歷程猶如一部波瀾壯闊的科技史詩,見證了英偉達在GPU領域的卓越創新與持續引領。 NVIDIA GPU架構變遷路線: 年份 NV GPU架構變遷 2025 Blackwell 2.0 2024 Blackwell 2023-2024 Hopp…

車載通信網絡 --- CAN FD與CAN XL

我是穿拖鞋的漢子,魔都中堅持長期主義的汽車電子工程師。 老規矩,分享一段喜歡的文字,避免自己成為高知識低文化的工程師: 做到欲望極簡,了解自己的真實欲望,不受外在潮流的影響,不盲從,不跟風。把自己的精力全部用在自己。一是去掉多余,凡事找規律,基礎是誠信;二是…

DM達夢數據庫開啟SQL日志記錄功能

DM達夢數據庫開啟SQL日志記錄功能 配置SQL日志(非必須的配置步驟,與主備集群配置無關,如果沒有需求可以跳過配置SQL日志) sqllog.ini 配置文件用于SQL日志的配置,當且僅當 INI(dm.ini) 參數 SV…

【HW系列】—C2遠控服務器(webshell鏈接工具, metasploit、cobaltstrike)的漏洞特征流量特征

文章目錄 蟻劍、冰蝎、哥斯拉一、蟻劍(AntSword)流量特征二、冰蝎(Behinder)流量特征三、哥斯拉(Godzilla)流量特征 metasploit、cobaltstrike一、Metasploit流量特征二、CobaltStrike流量特征三、檢測與防…

手機平板等設備租賃行業MDM方案解析

目錄 引言:MDM 在租賃行業的重要性日益凸顯 用戶場景:租賃公司面臨的主要挑戰 1. 設備丟失、逾期未還 2. 手動配置和恢復效率低 3. 非授權使用頻繁 4. 時區設置混亂影響運維 5. 缺乏實時監管能力 EasyControl MDM:租賃設備的遠程管控…

前端面試核心考點全解析

前端面試常見問題及解析大綱 核心技術篇 HTML相關問題 1. HTML5新特性解析 語義化標簽&#xff08;<header>、<section>等&#xff09;的作用與示例本地存儲&#xff08;localStorage與sessionStorage&#xff09;的差異 localStorage.setItem(key, value); c…

Selenium 測試框架 - Kotlin

??Selenium Kotlin 實踐指南:以百度搜索為例的完整測試示例 隨著測試自動化的普及,Selenium 已成為 Web 自動化測試的事實標準,而 Kotlin 憑借其簡潔語法和高安全性,越來越受到開發者歡迎。本指南將通過一個完整的實戰案例——在百度中執行搜索操作,來展示如何使用 Sele…

vscode調試stm32,Cortex Debug的配置文件lanuch.json如何寫,日志

https://blog.csdn.net/jiladahe1997/article/details/122046665 https://discuss.em-ide.com/blog/67-cortex-debug 第一版 {// 使用 IntelliSense 了解相關屬性。 // 懸停以查看現有屬性的描述。// 欲了解更多信息&#xff0c;請訪問: https://go.microsoft.com/fwlink/?li…

反范式設計應用場景解析

反范式設計應用場景解析 1. 反范式設計核心概念 反范式設計是指為了特定性能優化目標,在數據庫設計中故意違反關系數據庫的范式規則(通常是第三范式或BC范式),通過引入冗余數據或合并表結構來提升查詢效率的設計方法。 關鍵結論:反范式不是對范式理論的否定,而是在特定…

算法-js-子集

題&#xff1a;給你一個整數數組 nums &#xff0c;數組中的元素 互不相同 。返回該數組所有可能的子集&#xff08;冪集&#xff09;。解集 不能 包含重復的子集。你可以按 任意順序 返回解集。 方法一&#xff1a;迭代法 核心邏輯&#xff1a;動態擴展子集&#xff0c; 小規…

python里的NumPy算法

NumPy&#xff08;Numerical Python&#xff09;是 Python 中用于科學計算的基礎庫&#xff0c;提供了高性能的多維數組對象、矩陣運算以及大量數學函數庫。其核心優勢在于通過向量化操作替代傳統循環&#xff0c;大幅提升計算效率&#xff0c;尤其適合處理大規模數據的算法實現…

HarmonyOS優化應用文件上傳下載慢問題性能優化

一、概述 在開發應用時&#xff0c;客戶端與服務器之間數據交換的效率取決于文件傳輸的性能。一個數據交換性能較低的應用會導致其在加載過程中耗費較長時間&#xff0c;在很多的場景造成頁面卡頓&#xff0c;極大的影響了用戶體驗。相反&#xff0c;一個數據交換高效的應用&a…

64、【OS】【Nuttx】任務休眠與喚醒:clock_nanosleep

背景 之前的 blog 63、【OS】【Nuttx】任務休眠與喚醒&#xff1a;sleep 分析了任務休眠中的 sleep 函數&#xff0c;下面繼續來分析下 sleep 函數中的核心功能 clock_nanosleep clock_nanosleep usleep 上篇 blog 分析了 sleep 函數&#xff0c;其核心功能封裝到了 clock_…

【生產實踐】華為存儲XSG1在RHEL 7.x/8.x上的多路徑配置操作手冊(生產環境)

一、概述 本手冊針對Red Hat Enterprise Linux 7.x/8.x系統與華為XSG1存儲設備的多路徑I/O&#xff08;MPIO&#xff09;配置&#xff0c;通過優化路徑策略實現高可用、負載均衡及故障容錯&#xff0c;適配華為存儲硬件特性&#xff0c;滿足生產環境需求。 二、參數解析與配置…

Unity開發之Webgl自動更新程序包

之前讓客戶端更新webgl程序是在程序里寫版本號然后和服務器對比&#xff0c;不同就調用 window.location.reload(true);之前做的客戶端都是給企業用&#xff0c;用戶數少看不出來啥問題。后來自己開發一個小網站&#xff0c;用戶數量還是挺多&#xff0c;然后就會遇到各種各樣的…

一個開源腳本,可自動安裝在 AMD Radeon 7900XTX 上運行選定 AI 接口所需的所有內容

?一、軟件介紹 文末提供程序和源碼下載 一個開源腳本&#xff0c;可自動安裝在 AMD Radeon 7900XTX 上運行選定 AI 接口所需的所有內容。 二、ROCm-AI-Installer ROCm-AI-安裝程序 一個開源腳本&#xff0c;可自動安裝在 AMD Radeon 7900XTX 上運行選定 AI 接口所需的所有內…

【Axure結合Echarts繪制圖表】

1.繪制一個矩形&#xff0c;用于之后存放圖表&#xff0c;將其命名為test&#xff1a; 2.新建交互 -> 載入時 -> 打開鏈接&#xff1a; 3.鏈接到URL或文件路徑&#xff1a; 4.點擊fx&#xff1a; 5.輸入&#xff1a; javascript: var script document.createEleme…

Relooking:損失權重λ 、梯度權重α、學習率η

一般多任務&#xff0c;大家都喜歡疊加很多損失&#xff0c;由此產生很多損失權重系數。此外&#xff0c;有的學者直接對梯度進行操作。咋一看&#xff0c;上面三個系數貌似重復多余&#xff0c;直接用其中一個系數代替不行嗎&#xff1f;為此&#xff0c;回顧了下神經網絡的前…

數學復習筆記 20

復習方程組&#xff0c;還有隨便復習一下高數和矩陣&#xff0c;向量。現在是復習高數的導數這一章。兩個曲線相切&#xff0c;列出方程&#xff0c;然后解出參數&#xff0c;沒有任何難度呢。算切線方程&#xff0c;就是&#xff0c;算導數&#xff0c;導數就用導數定義&#…