一個(gè)Python優(yōu)雅的數(shù)據(jù)分塊方法詳解
1.背景
看到這個(gè)標(biāo)題你可能想一個(gè)分塊能有什么難度?還值得細(xì)說(shuō)嗎,最近確實(shí)遇到一個(gè)有意思的分塊函數(shù),寫法比較巧妙優(yōu)雅,所以寫一個(gè)分享。
日前在做需求過(guò)程中有一個(gè)對(duì)大量數(shù)據(jù)分塊處理的場(chǎng)景,具體來(lái)說(shuō)就是幾十萬(wàn)量級(jí)的數(shù)據(jù),分批處理,每次處理100個(gè)。這時(shí)就需要一個(gè)分塊功能的代碼,剛好項(xiàng)目的工具庫(kù)中就有一個(gè)分塊的函數(shù)。拿過(guò)函數(shù)來(lái)用,發(fā)現(xiàn)還挺好用的,傳入列表和分塊大小,然后就能遍歷取出分好的數(shù)據(jù)。調(diào)用方式如下:
from xxx import chunk_fun chunk_list = chunk_fun(arr, 100) # 對(duì)數(shù)據(jù)進(jìn)行分塊,指定塊的大小為100 for chunk in chunk_list: print(chunk)
然后我就對(duì)這個(gè)分塊函數(shù)產(chǎn)生了興趣,想看看這個(gè)小功能是如何實(shí)現(xiàn)的。如果讓我來(lái)寫一個(gè)分塊函數(shù),我知道Python中range函數(shù)可以指定步長(zhǎng),用這個(gè)特性就完全可以優(yōu)雅的實(shí)現(xiàn)分塊功能。
arr = [1,2,3,4,5,6,7,8,9,10] step = 3 for i in range(0, len(arr), step): chunk = arr[i:i+step] print(chunk) >>> [1, 2, 3] [4, 5, 6] [7, 8, 9] [10]
沒想到看到源碼竟然才用了3行代碼就實(shí)現(xiàn)了分塊,不僅支持列表等線性結(jié)構(gòu)的分塊,而且還支持集合這種非線性結(jié)構(gòu)的分塊。這讓我感到震撼,這3行代碼不是最優(yōu)雅的分塊方法,也是接近最優(yōu)雅的分塊方法了。廢話不多說(shuō),先上代碼:
from itertools import islice def chunk_list(it, limit): it = iter(it) return iter(lambda: list(islice(it, limit)), [])
對(duì)于這3行代碼,有多少人第一眼沒看出功能的呢?反正我第一眼看的是一臉懵逼,有種不明覺厲的感覺
首先來(lái)看一下這個(gè)分塊函數(shù)的使用。
set_num = {1,2,3,4,5,6,7} for temp_list in chunk_list(set_num, 2): print(temp_list) >>> [1, 2] [3, 4] [5, 6] [7]
完全沒有使用顯示循環(huán)就把分塊這件事安排的明明白白的,而且才用了3行代碼,不包括函數(shù)的定義就只剩下2行代碼就搞定了。這是我見過(guò)最優(yōu)雅的分塊方法。然后我就花一點(diǎn)時(shí)間搞明白代碼是如何工作的。
那么這個(gè)分塊功能是如何實(shí)現(xiàn)的呢?主要有兩個(gè)知識(shí)點(diǎn):迭代器切片islice+迭代器生成函數(shù)iter。通過(guò)這兩個(gè)函數(shù)的配合,完成了分塊功能。下面我詳細(xì)介紹這兩個(gè)方法的使用。
2.islice
islice是python內(nèi)置模塊itertool中的一個(gè)函數(shù),功能是對(duì)迭代器切片,傳入一個(gè)迭代器,返回從迭代器中的start位置到stop位置的元素,可缺省起始位置。
函數(shù)定義如下:
islice(iterable, [start, ] stop [, step])
- iterable 可迭代對(duì)象
- start 切片開始位置
- stop 切片結(jié)束位置
- step 步長(zhǎng)
2.1示例
from itertools import islice from collections import Iterator iter_list = iter([1,2,3,4,5,6,7]) slice = islice(iter_list, 0, 7, 2) print(slice) >>> <itertools.islice object at 0x7fc864e5aef8> print(isinstance(slice, Iterator)) >>> True print(list(slice)) >>> [1, 3, 5, 7]
指定start為0,stop為7,step2,得到一個(gè)新的迭代器,元素是從1開始的步長(zhǎng)為2取到的數(shù)據(jù)。
2.2只指定步長(zhǎng)
islice可以只傳入步長(zhǎng)參數(shù),當(dāng)沒有start和stop時(shí),默認(rèn)從start為起點(diǎn),stop為終點(diǎn)。
from itertools import islice iter_list = iter([1,2,3,4,5,6,7]) slice = islice(iter_list, 2) print(list(slice)) slice = islice(iter_list, 2) print(list(slice)) slice = islice(iter_list, 2) print(list(slice)) slice = islice(iter_list, 2) print(list(slice)) slice = islice(iter_list, 2) print(list(slice)) slice = islice(iter_list, 2) print(list(slice)) slice = islice(iter_list, 2) print(list(slice)) >>> [1, 2] [3, 4] [5, 6] [7] [] [] []
除了獲得切片之外,以上代碼還說(shuō)明了兩個(gè)非常重要的特征,是否有留意?
第一個(gè):那就是切片能夠保留位置信息,多次調(diào)用切片功能,當(dāng)前取值是從上一次結(jié)尾的地方開始的。比如第一次取值1、2,結(jié)尾位置是3;第二次就從3開始取到了3、4;第三次從5開始取到5、6。原因islice是對(duì)迭代器切片,迭代器取值會(huì)記住位置信息。
第二個(gè):當(dāng)?shù)晁械脑刂?,返回空?shù)組。將原始列表迭代完之后不會(huì)報(bào)錯(cuò),而是一直返回空數(shù)組。
有了上面這種使用方法就為分塊提供了可能性,如果要使用islice來(lái)分塊,只需要在一個(gè)死循環(huán)里調(diào)用islice取值,當(dāng)取值為[]
時(shí)退出循環(huán)即可??赏ㄟ^(guò)如下方法實(shí)現(xiàn):
from itertools import islice def chunk(it, limit): it = iter(it) while True: temp = list(islice(it, limit)) if temp == []: break yield temp iter_list = iter([1,2,3,4,5,6,7]) for temp_list in chunk(iter_list, 2): print(temp_list) >>> [1, 2] [3, 4] [5, 6] [7]
這樣就完成了使用islice就完成了分塊的功能,但是看上可不是很優(yōu)雅,又有while循環(huán),又有yield關(guān)鍵值。
不優(yōu)雅關(guān)鍵在于需要循環(huán)調(diào)用切片函數(shù)而且還需要判斷跳出循環(huán)的條件。那么有沒有一個(gè)既可以循環(huán)調(diào)用又能判斷結(jié)束條件的函數(shù)呢?還真的有的,那就是iter
。
3.iter
iter()方法用來(lái)創(chuàng)建迭代器,iter()本質(zhì)上就是調(diào)用可迭代對(duì)象的__iter__
方法,返回一個(gè)迭代器對(duì)象。關(guān)于iter的常規(guī)使用,可參見另一篇文章一篇文章講清楚迭代器和生成器
3.1常規(guī)使用
常見的iter的使用方法是,對(duì)一個(gè)可迭代對(duì)象調(diào)用iter方法,讓其變成一個(gè)迭代器,可以通過(guò)next取值。
list = [1,2,3,4,5,6,7] iter_list = iter(list) print(next(iter_list)) print(next(iter_list)) print(next(iter_list)) >>> 1 2 3
3.2進(jìn)階使用
iter還有一種不常用的方法,來(lái)看iter函數(shù)的定義
iter(object[, sentinel])
- object -- 支持迭代的集合對(duì)象。
- sentinel -- 如果傳遞了第二個(gè)參數(shù),則參數(shù) object 必須是一個(gè)可調(diào)用的對(duì)象(如,函數(shù)),此時(shí),iter 創(chuàng)建了一個(gè)迭代器對(duì)象,每次調(diào)用這個(gè)迭代器對(duì)象的__next__()方法時(shí),都會(huì)調(diào)用 object。
也就是說(shuō)如果iter函數(shù)如果傳了第二個(gè)參數(shù),那么第一個(gè)參數(shù)就必須是一個(gè)可調(diào)用對(duì)象,每一次調(diào)用next函數(shù)時(shí),實(shí)際上就是調(diào)用第一個(gè)參數(shù),如果結(jié)果等于第二個(gè)參數(shù),那就是迭代完成了。
聽起來(lái)有點(diǎn)彎彎繞,跑一個(gè)示例就清楚了。
import random def get_random(): return random.randint(1,5) demo = iter(get_random, 4) print(next(demo)) print(next(demo)) print(next(demo)) print(next(demo)) print(next(demo)) print(next(demo)) print(next(demo)) >>> 3 2 1 2 Traceback (most recent call last): File "islice_demo.py", line 62, in <module> print(next(demo)) StopIteration
iter傳入第一個(gè)參數(shù)是一個(gè)函數(shù)get_random,函數(shù)的功能是獲取1-5之間的隨機(jī)數(shù),第二個(gè)參數(shù)是4,也就是說(shuō)如果函數(shù)返回的數(shù)值是4,那算迭代完成。每一次調(diào)用next取值就會(huì)調(diào)用get_random函數(shù),直到結(jié)果為4。當(dāng)?shù)瓿芍?,?huì)拋出一個(gè)StopIteration
的異常。
上面是通過(guò)next調(diào)用,如果是通過(guò)for循環(huán)調(diào)用,就不會(huì)拋出異常,for循環(huán)會(huì)捕獲異常。
import random def get_random(): return random.randint(1,5) demo = iter(get_random, 4) for i in demo: print(i) >>> 1 5
這個(gè)功能剛好可以實(shí)現(xiàn)調(diào)用某一個(gè)函數(shù),又能判斷退出條件,如果現(xiàn)在再把分塊的代碼擺上來(lái),能否實(shí)現(xiàn)優(yōu)雅的分塊呢?
from itertools import islice def chunk(it, limit): it = iter(it) while True: temp = list(islice(it, limit)) if temp == []: break yield temp iter_list = iter([1,2,3,4,5,6,7]) for temp_list in chunk(iter_list, 2): print(temp_list)
4.islice 和 iter 組合使用
islice 提供分塊功能,iter 提供循環(huán)調(diào)用islice的功能和判斷退出的功能,最后在兩個(gè)函數(shù)的的配合使用下,完成了優(yōu)雅的分塊。
便于理解的示例:
from itertools import islice def chunk_list(it, limit): it = iter(it) # 實(shí)現(xiàn)分塊的內(nèi)函數(shù) def iter_fun(): return list(islice(it, limit)) return iter(iter_fun, []) it = [1,2,3,4,5,6,7] chunk = chunk_list(it, 2) print(next(chunk)) print(next(chunk)) print(next(chunk)) print(next(chunk)) print(next(chunk)) >>> [1, 2] [3, 4] [5, 6] [7] Traceback (most recent call last): File "chunk_demo.py", line 44, in <module> print(next(chunk)) StopIteration
最終的示例:
from itertools import islice def chunk_list(it, limit): it = iter(it) return iter(lambda: list(islice(it, limit)), [])
iter 第一個(gè)參數(shù)傳入lambda表達(dá)式,有一個(gè)更貼合場(chǎng)景的叫法是無(wú)頭函數(shù)。 lambda: list(islice(it, limit))
。沒有傳入?yún)?shù),函數(shù)體是islice(it, limit)
;
第二個(gè)參數(shù)是空列表[],作為迭代退出的判斷。
工作原理:
當(dāng)使用for循環(huán)遍歷分塊函數(shù)時(shí),每循環(huán)一次就通過(guò)iter調(diào)用islice一次,將分塊結(jié)果list處理,然后返回。直到islice返回空列表,iter根據(jù)第二個(gè)參數(shù)判斷退出循環(huán)。
5.總結(jié)
分塊函數(shù)的優(yōu)點(diǎn):
- 實(shí)現(xiàn)很優(yōu)雅
- 支持的分塊的數(shù)據(jù)類型豐富。不單是列表,只要能夠迭代的都可以。
分塊的實(shí)現(xiàn)主要有兩個(gè)思路:
- 使用islice來(lái)完成迭代器切片,實(shí)現(xiàn)分塊的功能。但是需要多次調(diào)用islice直到迭代完成
- iter 提供調(diào)用功能,并判斷迭代退出條件
有興趣的讀者可看看iter的實(shí)現(xiàn),能夠明白為什么迭代器能記住位置,這是本文分塊的一個(gè)核心知識(shí)點(diǎn)。
這一個(gè)簡(jiǎn)單的代碼讓我感受到Python的奇妙,兩個(gè)函數(shù)默契的配合,十分優(yōu)雅的完成了分塊功能。同時(shí)我明白Python語(yǔ)言的宗旨是簡(jiǎn)易優(yōu)雅,但是簡(jiǎn)易并不簡(jiǎn)單,想要實(shí)現(xiàn)優(yōu)雅需要扎實(shí)的基礎(chǔ)和深厚的知識(shí)儲(chǔ)備。追求Pythonic,需要學(xué)習(xí)理解的還有很多。
以上就是一個(gè)Python優(yōu)雅的數(shù)據(jù)分塊方法詳解的詳細(xì)內(nèi)容,更多關(guān)于Python數(shù)據(jù)分塊的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python實(shí)現(xiàn)生成帶logo背景圖的二維碼
這篇文章主要為大家詳細(xì)介紹了如何利用Python實(shí)現(xiàn)生成帶logo背景圖的二維碼(靜態(tài)和動(dòng)態(tài)圖),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2023-05-05Python利用zhdate模塊實(shí)現(xiàn)農(nóng)歷日期處理
zhdate模塊統(tǒng)計(jì)從1900年到2100年的農(nóng)歷月份數(shù)據(jù)代碼,支持農(nóng)歷和公歷之間的轉(zhuǎn)化,并且支持日期差額運(yùn)算。本文將利用這一模塊實(shí)現(xiàn)農(nóng)歷日期的處理,需要的可以參考一下2022-03-03教你如何使用Python開發(fā)一個(gè)釘釘群應(yīng)答機(jī)器人
在聊天工具大肆侵入我們生活各個(gè)方面的今天,各種消息無(wú)時(shí)無(wú)刻不在侵?jǐn)_我們的每一寸時(shí)間,這種情況下,一個(gè)聊天的機(jī)器人就很有必要了.今天,我們來(lái)學(xué)習(xí)一下使用 Python 開發(fā)一個(gè)釘釘?shù)膽?yīng)答機(jī)器人,助你「人生苦短,少回消息」,需要的朋友可以參考下2021-06-06Python從數(shù)據(jù)庫(kù)讀取大量數(shù)據(jù)批量寫入文件的方法
今天小編就為大家分享一篇Python從數(shù)據(jù)庫(kù)讀取大量數(shù)據(jù)批量寫入文件的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-12-12python pandas利用fillna方法實(shí)現(xiàn)部分自動(dòng)填充功能
這篇文章主要介紹了python pandas通過(guò)fillna方法實(shí)現(xiàn)部分自動(dòng)填充功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-03-03python3 使用openpyxl將mysql數(shù)據(jù)寫入xlsx的操作
這篇文章主要介紹了python3 使用openpyxl將mysql數(shù)據(jù)寫入xlsx的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-05-05python安裝和pycharm環(huán)境搭建設(shè)置方法
這篇文章主要介紹了python安裝和pycharm環(huán)境搭建和設(shè)置方法,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下 ,2020-05-05