我修復了Pandas包的一個bug
你好,我是zhenguo,今天說個開心事~
1. 還原這個bug
導出含有層級關系的列頭時,會多寫出一個空行,此bug穩定出現。
2. 定位問題
經過調試發現,鎖定此bug出現的位置到excel.py模塊,如下所示:
理一理excel.py模塊封裝的方法,經過調試發現,write方法中下面幾行代碼是關鍵邏輯:
寫入到excel過程,實際是逐個單元格寫入到excel過程,主要調用封裝的get_formatted_cells方法得到formatted_cells。
formatted_cells = self.get_formatted_cells()
writer.write_cells(
formatted_cells,
sheet_name,
startrow=startrow,
startcol=startcol,
freeze_panes=freeze_panes,
)
再進去看看get_formatted_cells方法,它使用chain串接了兩個生成器,然后逐一yield吐出cell:
def get_formatted_cells(self):
for cell in itertools.chain(self._format_header(), self._format_body()):
cell.val = self._format_value(cell.val)
yield cell
而串接的這兩個迭代器,一個是self._format_header(),另一個是self._format_body()。
經過調試,在這里就能找到bug出現的原因,self._format_body()是有問題的,經過格式化數據域部分。拿文章一開始的case舉例,取值為a的單元格對應的行索引被錯誤的標記為3,注意行索引是從0開始的。很明顯,實際應該是2。
3. 修復bug
找到原因后,進一步下鉆到底層方法,經過調試,進一步鎖定到self._format_body()中調用的 _format_regular_rows方法,里面與行編號相關聯的屬性是self.rowcounter,所以重點關注與它相關的寫入邏輯:
def _format_regular_rows(self):
has_aliases = isinstance(self.header, (tuple, list, np.ndarray, Index))
if has_aliases or self.header:
self.rowcounter += 1
# output index and index_label?
if self.index:
# check aliases
# if list only take first as this is not a MultiIndex
if self.index_label and isinstance(
self.index_label, (list, tuple, np.ndarray, Index)
):
index_label = self.index_label[0]
# if string good to go
elif self.index_label and isinstance(self.index_label, str):
index_label = self.index_label
else:
index_label = self.df.index.names[0]
if isinstance(self.columns, ABCMultiIndex):
self.rowcounter += 1
一共有2處可能的寫入,其中第二處寫入,也就是上面代碼塊的最后兩行,是bug出現的原因。經過仔細分析,在級聯表頭(ABCMultiIndex)寫入excel場景中,行索引已經在self._format_header()中,行索引已經被加1,所以再在此處對其加1,是重復的:
if isinstance(self.columns, ABCMultiIndex):
self.rowcounter += 1
所以修改方法就是對其標注即可。
4. 修復bug后
修復后,經過測試級聯列頭、單列頭,都正常,不再有多余的空行。
以上,此bug我已經提交到github的pandas中,希望幫助到更多的開發者。
我是zhenguo,最后希望點贊+轉發~