寫在前面
QQ群里偶然看到群友問這個問題, pandas讀取大文件時怎么才能獲取進度? 我第一反應是: 除非pandas的read_excel等函數(shù)提供了回調函數(shù)的接口, 否則應該沒辦法做到. 搜索了一下官方文檔和網(wǎng)上的帖子, 果然是沒有現(xiàn)成的方案, 只能自己動手.
準備工作
確定方案
一開始我就確認了實現(xiàn)方案, 那就是增加回調函數(shù). 這里現(xiàn)學現(xiàn)賣科普一下什么是回調函數(shù). 簡單的說就是:
所使用的模塊里面, 會調用一個你給定的外部方法/函數(shù), 就是回調函數(shù). 拿本次的嘗試作為例子, 我會編寫一個"顯示進度函數(shù)", 通過傳參的方式傳入pd.read_excel, 這樣pd在讀取excel時, 會邊讀取邊調用"顯示進度函數(shù)". 為什么不直接在pd里面增加? 因為pd讀取excel文件時是阻塞的, 內(nèi)部方法在被調用時無法拋出進度信息. (如有謬誤請指正)
理解讀取方式
先得了解一下pandas是怎么讀取excel的. 在pycharm里面按住control點擊read_excel, 再瀏覽一下代碼根據(jù)關鍵的函數(shù)繼續(xù)跳轉, 還是挺容易得到調用的路徑的.
最后OpenpyxlReader讀取excel的方法代碼如下. 很明顯重點就在其中的for循環(huán)里. 調用get_sheet_data時, 已經(jīng)通過一系列方法獲得了目標sheet(這里細節(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
開始改動
這里直接暴力更改pandas庫源文件!(僅用于調試, 注意備份和保護自己的工作環(huán)境)
主程序代碼
編寫main.py, 代碼比較簡單, 相關功能我都用注釋作為解釋. 其中show_pd_read_excel_progress就是我編寫的回調函數(shù), 通過命令行的方式輸出實時的讀取進度. 當然你如果編寫的是GUI程序比如PYQT5, 也可以在這個回調函數(shù)中發(fā)送signal給main UI, 做成progress bar或者其他的GUI樣式.
import pandas as pd
from datetime import datetime
'''
定義回調函數(shù)
cur: 讀取時的當前行數(shù)
tt: 讀取文件的總行數(shù)
'''
def show_pd_read_excel_progress(cur, tt):
# 進度數(shù)值
progress = " {:.2f}%".format(cur/tt*100)
# 進度條
bar = " ".join("█" for _ in range(int(cur/tt*100/10)))
# 顯示進度
print("\r進度:" + bar + progress, end="", flush=True)
# 記錄開始時間
t = datetime.now()
# 開始讀取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")
# 顯示花費的時間
print("Time spent:", datetime.now()-t)
修改pandas源碼
再自己觀察一下, 我在pd.read_excel方法的參數(shù)里增加了callback參數(shù), 這個參數(shù)是原版read_excel方法里沒有的. 所以我們需要處理pandas源碼, 這個源碼在…/pandas/io/excel/_base.py中, pycharm中按住control點擊read_excel可以快速跳轉. 這個地方我增加了一個參數(shù)callback, 默認值為None. 下方io.parse同樣把callback參數(shù)傳遞給ExcelFile類.
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類(還在_base.py中)的代碼, 這個類會根據(jù)文件類型選擇引擎, 我讀取的是xlsx文件, 所以會跳轉到openpyxl并把所有的參數(shù)傳遞過去, 這個類不用處理. 下面跳轉到_openpyxl.py中看一下OpenpyxlReader類, 這個類是繼承BaseExcelReader類(在_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)
... # 省略代碼
好了, 終于到重點了, 我們跳轉到get_sheet_data方法, 并做對應修改(方法參數(shù), 獲取總行數(shù), 調用回調函數(shù)). 思路非常清晰, 通過一頓操作, 終于千里迢迢把callback給一層層傳遞過來了, 所以在一行行讀取excel時, 可以調用并顯示進度了.
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):
# 調用回調函數(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
運行測試
運行一下main.py, 效果如下, 實時顯示進度功能已經(jīng)實現(xiàn), 且會計算出讀取所花費的時間. 如果你是要讀取csv或者sql之類的, 也可以照貓畫虎.
優(yōu)化和應用
- 前面也說過直接修改pandas源碼是非常不科學的操作, 這會破壞已有的編程環(huán)境, 且源碼換到別的機器上還得重新在修改一遍
- 也嘗試過用繼承+重寫pandas, 不過水平有限沒有成功, 希望大家指點
- 實測print進度條會非常費時間, 當然也不需要每讀一行excel都更新一次進度條, 定時(比如每秒刷一次)或者定量(每n行, 或者每1%進度刷新一次)比較合理
- 讀取大規(guī)模數(shù)據(jù)時, 頻繁調用回調函數(shù)肯定會耽誤效率, 不過如果是GUI程序或者給其他人使用的, 有實時進度肯定會改善用戶體驗, 其中優(yōu)劣需要coder自己權衡
到此這篇關于pandas讀取excel時獲取讀取進度的實現(xiàn)的文章就介紹到這了,更多相關pandas讀取excel讀取內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
您可能感興趣的文章:- 教你使用Pandas直接核算Excel中的快遞費用
- Python入門之使用pandas分析excel數(shù)據(jù)
- pandas快速處理Excel,替換Nan,轉字典的操作
- pandas讀取excel,txt,csv,pkl文件等命令的操作
- python pandas模糊匹配 讀取Excel后 獲取指定指標的操作
- pandas針對excel處理的實現(xiàn)
- 關于Python 解決Python3.9 pandas.read_excel(‘xxx.xlsx‘)報錯的問題
- 解決使用Pandas 讀取超過65536行的Excel文件問題
- 利用Python pandas對Excel進行合并的方法示例
- Python pandas對excel的操作實現(xiàn)示例
- pandas to_excel 添加顏色操作
- 解決python pandas讀取excel中多個不同sheet表格存在的問題
- Python pandas如何向excel添加數(shù)據(jù)
- pandas中的ExcelWriter和ExcelFile的實現(xiàn)方法
- pandas實現(xiàn)excel中的數(shù)據(jù)透視表和Vlookup函數(shù)功能代碼
- Python使用Pandas讀寫Excel實例解析
- pandas將多個dataframe以多個sheet的形式保存到一個excel文件中
- 利用python Pandas實現(xiàn)批量拆分Excel與合并Excel