解決 openpyxl 垂直分頁符和水平分頁符同時新增的問題
前言
十天前知乎上有人提問 python:openpyxl模組怎麼給表格新增分頁符?實現分頁列印功能?,看到問題之後,我很快的給他了一個如何新增垂直分頁符或水平分頁符的示例,你以為問題就結束了?我是這麼以為的,但是事實證明,我太天真了,就在我給出示例的幾分鐘後,他在我的回答下評論了,說是同時新增垂直分頁符和水平分頁符失敗了.
我當時的第一反應:
心裡想著,肯定是他的寫法有問題,毫不猶豫的回覆到," 沒有試過同時新增兩種分頁符的操作,預設是水平分頁符,如果你先添加了垂直分頁符的話,應該後面需要重新宣告:openpyxl.worksheet.pagebreak.PageBreak.tagname = "rowBreaks",聽著自己飛快擊打鍵盤的聲音,自己不經有點飄飄然.就在沉浸在自己的YY當中,又過去了幾分鐘,他用正確的程式碼錯誤的結果狠狠的摔在了我的臉上:
col_break = openpyxl.worksheet.pagebreak.Break(5) #建立分頁符,引數5:在第5/6中間分頁 sheet1.page_breaks.tagname = 'rowBreaks' #分頁符屬性設定為行分頁符 sheet1.page_breaks.append(col_break) #把分頁符物件新增到sheet物件裡 row_break = openpyxl.worksheet.pagebreak.Break(3) #建立分頁符 sheet1.page_breaks.tagname = 'colBreaks' #分頁符屬性設定為列分頁符 sheet1.page_breaks.append(row_break) #把分頁符物件新增到sheet物件裡
"結果是在第3和5列添加了兩個垂直分頁符,是哪裡有問題?大師",他問道. 我看了一眼程式碼好像沒有錯,我的心有點慌了,雙手開始微微顫抖起來,一遍又一遍地仔細地巡視著程式碼,檢視找出錯誤反駁他,但是並沒有,抱著最後的希望,我把他的程式碼複製到自己的檔案中,然後敲下回車符, excel 檔案靜靜的生成在目錄下,這可能是我最後的希望了.拿滑鼠的手不自覺的顫抖起來,那麼小的螢幕,那麼大的檔案,滑鼠怎麼半天都沒辦法移動上去,我深吸一口氣,控制住自己手,終於把滑鼠一上去了,雙擊excel,閉上眼睛,再睜開,我知道,我錯了.但是就這麼放棄了嗎?絕不!我要把這個問題打到!
亮劍
"是時候展示真正的技術了"
俗話說"解鈴還須繫鈴人",我們還得自己看一下問題程式碼:
# example.py
from openpyxl import Workbook
from openpyxl.compat import range
from openpyxl.utils import get_column_letter
from openpyxl.worksheet.pagebreak import Break, PageBreak
wb = Workbook()
ws = wb.active
for row in range(1, 20):
for col in range(1,30):
_ = ws.cell(column=col, row=row, value="{0}".format(get_column_letter(col)))
col_break = Break(5) #建立分頁符,引數5:在第5/6中間分頁
ws.page_breaks.tagname = 'rowBreaks' #分頁符屬性設定為行分頁符
ws.page_breaks.append(col_break) #把分頁符物件新增到sheet物件裡
row_break = Break(3) #建立分頁符
ws.page_breaks.tagname = 'colBreaks' #分頁符屬性設定為列分頁符
ws.page_breaks.append(row_break) #把分頁符物件新增到sheet物件裡
wb.save(filename = dest_filename)
從程式碼上應該是後面的 page_breaks 把前面的覆蓋了, 那讓我們看看 page_breaks 究竟是什麼東西.
class Worksheet(_WorkbookChild):
# 省略部分程式碼
def _setup(self):
self.page_breaks = PageBreak() # 再看 PageBreak
class PageBreak(Serialisable):
tagname = "rowBreaks"
# 省略部分程式碼
def append(self, brk=None):
"""
Add a page break
"""
vals = list(self.brk)
if not isinstance(brk, Break):
brk = Break(id=self.count+1)
vals.append(brk)
self.brk = vals
從 example 中我們不難發現,我們是通過修改 page_breaks 的 tag_name 去決定插入的分頁符是垂直分頁符還是水平分頁符的.但是 page_breaks 現在只有一個 PageBreak 這就難怪後宣告的會把前面的覆蓋了,那麼如果我們把 page_breaks 變成 PageBreak 的列表呢?
First Blood -- page_breaks
說改咱就改啊,首先嚐試修改 WorkSheet 類
class Worksheet(_WorkbookChild):
# 省略部分程式碼
def _setup(self):
self.page_breaks = [PageBreak()]
然後再修改一下 example.py
from openpyxl import Workbook
from openpyxl.compat import range
from openpyxl.utils import get_column_letter
from openpyxl.worksheet.pagebreak import Break, PageBreak
wb = Workbook()
dest_filename = 'empty_book.xlsx'
ws = wb.active
for row in range(1, 20):
for col in range(1,30):
_ = ws.cell(column=col, row=row, value="{0}".format(get_column_letter(col)))
rowPageBreak = PageBreak()
rowPageBreak.tagname = 'rowBreaks'
colPageBreak = PageBreak()
colPageBreak.tagname = 'colBreaks'
ws.page_breaks = [rowPageBreak, colPageBreak]
ws.page_breaks[0].append(Break(id=5))
ws.page_breaks[1].append(Break(id=3))
wb.save(filename = dest_filename)
敲下回車,心裡那個美滋滋,還沒高興幾秒鐘,就出問題了,果然做人還是得低調一點
Traceback (most recent call last):
File "test.py", line 24, in <module>
wb.save(filename = dest_filename)
File "F:\workspace\python\test_openpyxl\test_openpyxl\lib\site-packages\openpyxl\workbook\workbook.py", line 391, in save
save_workbook(self, filename)
File "F:\workspace\python\test_openpyxl\test_openpyxl\lib\site-packages\openpyxl\writer\excel.py", line 284, in save_workbook
writer.save(filename)
File "F:\workspace\python\test_openpyxl\test_openpyxl\lib\site-packages\openpyxl\writer\excel.py", line 266, in save
self.write_data()
File "F:\workspace\python\test_openpyxl\test_openpyxl\lib\site-packages\openpyxl\writer\excel.py", line 83, in write_data
self._write_worksheets()
File "F:\workspace\python\test_openpyxl\test_openpyxl\lib\site-packages\openpyxl\writer\excel.py", line 203, in _write_worksheets
xml = ws._write()
File "F:\workspace\python\test_openpyxl\test_openpyxl\lib\site-packages\openpyxl\worksheet\worksheet.py", line 893, in _write
return write_worksheet(self)
File "F:\workspace\python\test_openpyxl\test_openpyxl\lib\site-packages\openpyxl\writer\worksheet.py", line 151, in write_worksheet
xf.write(ws.page_breaks.to_tree())
AttributeError: 'list' object has no attribute 'to_tree'
看了一眼錯誤資訊,發現了從中作祟的傢伙再 worksheet.py 的 151 行, 讓我們悄悄地看一眼,打槍的不要.
# worksheet.py
# 省略部分程式碼
if ws.page_breaks:
xf.write(ws.page_breaks.to_tree())
原來是我們修改了 page_breaks 之後, page_breaks 有時候不再是孤家寡人了,我們需要考慮它有另外的 PageBreak 的情況了.
Double Kill -- Worksheet
# worksheet.py
# 省略部分程式碼
if ws.page_breaks:
if isinstance(ws.page_breaks,list):
for page_break_item in ws.page_breaks:
xf.write(page_break_item.to_tree())
else:
xf.write(ws.page_breaks.to_tree())
回車,毫無問題,人生啊就是這麼寂寞如雪~~~
後記
已經在 openpyxl 提了相應的issue,目前再寫測試用例,過段時間就提交 PR 了