pandas讀取excel時(shí)獲取讀取進(jìn)度的實(shí)現(xiàn)
寫(xiě)在前面
QQ群里偶然看到群友問(wèn)這個(gè)問(wèn)題, pandas讀取大文件時(shí)怎么才能獲取進(jìn)度? 我第一反應(yīng)是: 除非pandas的read_excel等函數(shù)提供了回調(diào)函數(shù)的接口, 否則應(yīng)該沒(méi)辦法做到. 搜索了一下官方文檔和網(wǎng)上的帖子, 果然是沒(méi)有現(xiàn)成的方案, 只能自己動(dòng)手.
準(zhǔn)備工作
確定方案
一開(kāi)始我就確認(rèn)了實(shí)現(xiàn)方案, 那就是增加回調(diào)函數(shù). 這里現(xiàn)學(xué)現(xiàn)賣(mài)科普一下什么是回調(diào)函數(shù). 簡(jiǎn)單的說(shuō)就是:
所使用的模塊里面, 會(huì)調(diào)用一個(gè)你給定的外部方法/函數(shù), 就是回調(diào)函數(shù). 拿本次的嘗試作為例子, 我會(huì)編寫(xiě)一個(gè)"顯示進(jìn)度函數(shù)", 通過(guò)傳參的方式傳入pd.read_excel, 這樣pd在讀取excel時(shí), 會(huì)邊讀取邊調(diào)用"顯示進(jìn)度函數(shù)". 為什么不直接在pd里面增加? 因?yàn)閜d讀取excel文件時(shí)是阻塞的, 內(nèi)部方法在被調(diào)用時(shí)無(wú)法拋出進(jìn)度信息. (如有謬誤請(qǐng)指正)
理解讀取方式
先得了解一下pandas是怎么讀取excel的. 在pycharm里面按住control點(diǎn)擊read_excel, 再瀏覽一下代碼根據(jù)關(guān)鍵的函數(shù)繼續(xù)跳轉(zhuǎn), 還是挺容易得到調(diào)用的路徑的.
最后OpenpyxlReader讀取excel的方法代碼如下. 很明顯重點(diǎn)就在其中的for循環(huán)里. 調(diào)用get_sheet_data時(shí), 已經(jīng)通過(guò)一系列方法獲得了目標(biāo)sheet(這里細(xì)節(jié)不贅述), 然后在for循環(huán)里逐行讀取數(shù)據(jù)并返回data最后生成dataframe.
def get_sheet_data(self, sheet, convert_float: bool) -> List[List[Scalar]]: # GH 39001 # Reading of excel file depends on dimension data being correct but # writers sometimes omit or get it wrong import openpyxl version = LooseVersion(get_version(openpyxl)) # There is no good way of determining if a sheet is read-only # https://foss.heptapod.net/openpyxl/openpyxl/-/issues/1605 is_readonly = hasattr(sheet, "reset_dimensions") if version >= "3.0.0" and is_readonly: sheet.reset_dimensions() data: List[List[Scalar]] = [] last_row_with_data = -1 for row_number, row in enumerate(sheet.rows): converted_row = [self._convert_cell(cell, convert_float) for cell in row] if not all(cell == "" for cell in converted_row): last_row_with_data = row_number data.append(converted_row) # Trim trailing empty rows data = data[: last_row_with_data + 1] if version >= "3.0.0" and is_readonly and len(data) > 0: # With dimension reset, openpyxl no longer pads rows max_width = max(len(data_row) for data_row in data) if min(len(data_row) for data_row in data) < max_width: empty_cell: List[Scalar] = [""] data = [ data_row + (max_width - len(data_row)) * empty_cell for data_row in data ] return data
開(kāi)始改動(dòng)
這里直接暴力更改pandas庫(kù)源文件!(僅用于調(diào)試, 注意備份和保護(hù)自己的工作環(huán)境)
主程序代碼
編寫(xiě)main.py, 代碼比較簡(jiǎn)單, 相關(guān)功能我都用注釋作為解釋. 其中show_pd_read_excel_progress就是我編寫(xiě)的回調(diào)函數(shù), 通過(guò)命令行的方式輸出實(shí)時(shí)的讀取進(jìn)度. 當(dāng)然你如果編寫(xiě)的是GUI程序比如PYQT5, 也可以在這個(gè)回調(diào)函數(shù)中發(fā)送signal給main UI, 做成progress bar或者其他的GUI樣式.
import pandas as pd from datetime import datetime ''' 定義回調(diào)函數(shù) cur: 讀取時(shí)的當(dāng)前行數(shù) tt: 讀取文件的總行數(shù) ''' def show_pd_read_excel_progress(cur, tt): # 進(jìn)度數(shù)值 progress = " {:.2f}%".format(cur/tt*100) # 進(jìn)度條 bar = " ".join("█" for _ in range(int(cur/tt*100/10))) # 顯示進(jìn)度 print("\r進(jìn)度:" + bar + progress, end="", flush=True) # 記錄開(kāi)始時(shí)間 t = datetime.now() # 開(kāi)始讀取excel print("pd.read_excel: test_4.xlsx...") xl_data = pd.read_excel("test_4.xlsx", callback=show_pd_read_excel_progress) # 打印excel頭幾行 print(xl_data.head()) print("\n") # 顯示花費(fèi)的時(shí)間 print("Time spent:", datetime.now()-t)
修改pandas源碼
再自己觀察一下, 我在pd.read_excel方法的參數(shù)里增加了callback參數(shù), 這個(gè)參數(shù)是原版read_excel方法里沒(méi)有的. 所以我們需要處理pandas源碼, 這個(gè)源碼在…/pandas/io/excel/_base.py中, pycharm中按住control點(diǎn)擊read_excel可以快速跳轉(zhuǎn). 這個(gè)地方我增加了一個(gè)參數(shù)callback, 默認(rèn)值為None. 下方io.parse同樣把callback參數(shù)傳遞給ExcelFile類(lèi).
def read_excel( io, sheet_name=0, header=0, names=None, index_col=None, usecols=None, squeeze=False, dtype=None, engine=None, converters=None, true_values=None, false_values=None, skiprows=None, nrows=None, na_values=None, keep_default_na=True, na_filter=True, verbose=False, parse_dates=False, date_parser=None, thousands=None, comment=None, skipfooter=0, convert_float=True, mangle_dupe_cols=True, storage_options: StorageOptions = None, callback = None, # 增加callback參數(shù) ): should_close = False if not isinstance(io, ExcelFile): should_close = True io = ExcelFile(io, storage_options=storage_options, engine=engine) elif engine and engine != io.engine: raise ValueError( "Engine should not be specified when passing " "an ExcelFile - ExcelFile already has the engine set" ) try: data = io.parse( sheet_name=sheet_name, header=header, names=names, index_col=index_col, usecols=usecols, squeeze=squeeze, dtype=dtype, converters=converters, true_values=true_values, false_values=false_values, skiprows=skiprows, nrows=nrows, na_values=na_values, keep_default_na=keep_default_na, na_filter=na_filter, verbose=verbose, parse_dates=parse_dates, date_parser=date_parser, thousands=thousands, comment=comment, skipfooter=skipfooter, convert_float=convert_float, mangle_dupe_cols=mangle_dupe_cols, callback = callback, # 增加callback參數(shù) ) finally: # make sure to close opened file handles if should_close: io.close() return data ... # 省略代碼
瀏覽一下ExcelFile類(lèi)(還在_base.py中)的代碼, 這個(gè)類(lèi)會(huì)根據(jù)文件類(lèi)型選擇引擎, 我讀取的是xlsx文件, 所以會(huì)跳轉(zhuǎn)到openpyxl并把所有的參數(shù)傳遞過(guò)去, 這個(gè)類(lèi)不用處理. 下面跳轉(zhuǎn)到_openpyxl.py中看一下OpenpyxlReader類(lèi), 這個(gè)類(lèi)是繼承BaseExcelReader類(lèi)(在_base.py中)的, 所以還是得回去看一下BaseExcelReader, 并修改一下參數(shù), 增加callback(如下2處).
def parse( self, sheet_name=0, header=0, names=None, index_col=None, usecols=None, squeeze=False, dtype=None, true_values=None, false_values=None, skiprows=None, nrows=None, na_values=None, verbose=False, parse_dates=False, date_parser=None, thousands=None, comment=None, skipfooter=0, convert_float=True, mangle_dupe_cols=True, callback = None, # 增加callback參數(shù) **kwds, ): ... # 省略代碼
for asheetname in sheets: if verbose: print(f"Reading sheet {asheetname}") if isinstance(asheetname, str): sheet = self.get_sheet_by_name(asheetname) else: # assume an integer if not a string sheet = self.get_sheet_by_index(asheetname) data = self.get_sheet_data(sheet, convert_float, callback) # 傳遞callback參數(shù)給get_sheet_data方法 usecols = maybe_convert_usecols(usecols) ... # 省略代碼
好了, 終于到重點(diǎn)了, 我們跳轉(zhuǎn)到get_sheet_data方法, 并做對(duì)應(yīng)修改(方法參數(shù), 獲取總行數(shù), 調(diào)用回調(diào)函數(shù)). 思路非常清晰, 通過(guò)一頓操作, 終于千里迢迢把callback給一層層傳遞過(guò)來(lái)了, 所以在一行行讀取excel時(shí), 可以調(diào)用并顯示進(jìn)度了.
def get_sheet_data(self, sheet, convert_float: bool, callback) -> List[List[Scalar]]: # 傳遞參數(shù)增加callback # GH 39001 # Reading of excel file depends on dimension data being correct but # writers sometimes omit or get it wrong import openpyxl # 獲取sheet的總行數(shù) max_row = sheet.max_row print("sheet_max_row:", sheet.max_row) version = LooseVersion(get_version(openpyxl)) # There is no good way of determining if a sheet is read-only # https://foss.heptapod.net/openpyxl/openpyxl/-/issues/1605 is_readonly = hasattr(sheet, "reset_dimensions") if version >= "3.0.0" and is_readonly: sheet.reset_dimensions() data: List[List[Scalar]] = [] last_row_with_data = -1 for row_number, row in enumerate(sheet.rows): # 調(diào)用回調(diào)函數(shù) if callback is not None: callback(row_number+1, max_row) converted_row = [self._convert_cell(cell, convert_float) for cell in row] if not all(cell == "" for cell in converted_row): last_row_with_data = row_number data.append(converted_row) # Trim trailing empty rows data = data[: last_row_with_data + 1] if version >= "3.0.0" and is_readonly and len(data) > 0: # With dimension reset, openpyxl no longer pads rows max_width = max(len(data_row) for data_row in data) if min(len(data_row) for data_row in data) < max_width: empty_cell: List[Scalar] = [""] data = [ data_row + (max_width - len(data_row)) * empty_cell for data_row in data ] return data
運(yùn)行測(cè)試
運(yùn)行一下main.py, 效果如下, 實(shí)時(shí)顯示進(jìn)度功能已經(jīng)實(shí)現(xiàn), 且會(huì)計(jì)算出讀取所花費(fèi)的時(shí)間. 如果你是要讀取csv或者sql之類(lèi)的, 也可以照貓畫(huà)虎.
優(yōu)化和應(yīng)用
- 前面也說(shuō)過(guò)直接修改pandas源碼是非常不科學(xué)的操作, 這會(huì)破壞已有的編程環(huán)境, 且源碼換到別的機(jī)器上還得重新在修改一遍
- 也嘗試過(guò)用繼承+重寫(xiě)pandas, 不過(guò)水平有限沒(méi)有成功, 希望大家指點(diǎn)
- 實(shí)測(cè)print進(jìn)度條會(huì)非常費(fèi)時(shí)間, 當(dāng)然也不需要每讀一行excel都更新一次進(jìn)度條, 定時(shí)(比如每秒刷一次)或者定量(每n行, 或者每1%進(jìn)度刷新一次)比較合理
- 讀取大規(guī)模數(shù)據(jù)時(shí), 頻繁調(diào)用回調(diào)函數(shù)肯定會(huì)耽誤效率, 不過(guò)如果是GUI程序或者給其他人使用的, 有實(shí)時(shí)進(jìn)度肯定會(huì)改善用戶體驗(yàn), 其中優(yōu)劣需要coder自己權(quán)衡
到此這篇關(guān)于pandas讀取excel時(shí)獲取讀取進(jìn)度的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)pandas讀取excel讀取內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
教你如何利用python3爬蟲(chóng)爬取漫畫(huà)島-非人哉漫畫(huà)
本文給大家分享利用python3爬蟲(chóng)爬取漫畫(huà)島-非人哉漫畫(huà),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友跟隨小編一起學(xué)習(xí)下吧2021-07-07Python3 sort和sorted用法+cmp_to_key()函數(shù)詳解
這篇文章主要介紹了Python3 sort和sorted用法+cmp_to_key()函數(shù)詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07Python變量名詳細(xì)規(guī)則詳細(xì)變量值介紹
這篇文章主要介紹了Python變量名詳細(xì)規(guī)則詳細(xì)變量值,Python需要使用標(biāo)識(shí)符給變量命名,其實(shí)標(biāo)識(shí)符就是用于給程序中變量、類(lèi)、方法命名的符號(hào)(簡(jiǎn)單來(lái)說(shuō),標(biāo)識(shí)符就是合法的名稱,下面葛小編一起進(jìn)入文章里哦阿姐更多詳細(xì)內(nèi)容吧2022-01-01pandas刪除重復(fù)數(shù)據(jù)簡(jiǎn)單方法
這篇文章主要給大家介紹了關(guān)于pandas刪除重復(fù)數(shù)據(jù)的簡(jiǎn)單方法,在數(shù)據(jù)處理過(guò)程中常常會(huì)遇到重復(fù)的問(wèn)題,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-07-07Numpy ndarray 多維數(shù)組對(duì)象的使用
這篇文章主要介紹了Numpy ndarray 多維數(shù)組對(duì)象的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02Jupyter Notebook/VSCode導(dǎo)出PDF中文不顯示的解決
這篇文章主要介紹了Jupyter Notebook/VSCode導(dǎo)出PDF中文不顯示的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06舉例講解Linux系統(tǒng)下Python調(diào)用系統(tǒng)Shell的方法
這篇文章主要介紹了舉例講解Linux系統(tǒng)下Python調(diào)用系統(tǒng)Shell的方法,包括用Python和shell讀取文件某一行的實(shí)例,需要的朋友可以參考下2015-11-11YOLOv5車(chē)牌識(shí)別實(shí)戰(zhàn)教程(六)性能優(yōu)化與部署
這篇文章主要介紹了YOLOv5車(chē)牌識(shí)別實(shí)戰(zhàn)教程(六)性能優(yōu)化與部署,在這個(gè)教程中,我們將一步步教你如何使用YOLOv5進(jìn)行車(chē)牌識(shí)別,幫助你快速掌握YOLOv5車(chē)牌識(shí)別技能,需要的朋友可以參考下2023-04-04