最近在用Python写一个生成Excel日历的脚本, 功能上实现没多大问题, 倒是在xlsxwriter的格式写入上遇到了一个大坑.

举个例子:

importxlsxwriter
wb= xlsxwriter.Workbook("test.xlsx")
ws=wb.add_worksheet()#加粗和字体样式
bold = wb.add_format({"bold":True})
fontsize= wb.add_format({"font_size":15})
ws.write("A1", "test", bold)
ws.write("A2", "test", fontsize)

可以预见A1格是粗体, A2格字体则大了几号. 但我们无法同时给一个单元格套用两种样式, 或者说, 单元格只接受最后套用的样式:

ws.write("A1", "test", bold)
ws.write("A1", "test", fontsize)

这一格的文字只会变大而不会变粗 .

另一个问题是, 无法分别给一个单元格写入值和样式. 对于单个单元格, 必须在写入值的同时为单元格写入样式. 关于这个, 可以用 set_column 和 set_row 解决, 它们的实质是设置该列/行的默认样式:

ws.set_column("A:A", None, bold)
ws.set_row(2, None, fontsize) #第三行
ws.write("A1", "test1")
ws.write("A2", "test2", fontsize)
ws.write("A3", "test3")

运行代码可以发现A1继承了A列的默认样式(加粗), 而A2只有字体变大了, 样式覆盖问题依然存在; 另外, A3只继承了行默认样式: 不管 set_column 和 set_row 的先后顺序如何, 行默认样式会覆盖列默认样式.

对于规模极小的表格, 可以分别为单元格设置单独而完全的格式, 但我们依旧希望能够分别/分次为单元格追加新样式, 例如先对一些单元格设置字体, 然后再对一些单元格设置背景色, 最后再给整体添加框线等.

Xlsxwriter被设计为只能写入xlsx文件而不能读取或修改, 但问题在于, 我们xlsxwriter写入的数据会暂存在内存中, 直到最后close()时才会写入到文件中, 那么理应能够从缓存中修改, 最起码读出某单元格的值和样式. 而官方目前没有这样的设计, 所以我们可以先将设置的样式缓存下来, 如果某单元格已经有了样式那就合并, 最后再调用xlsxwriter的方法写入到表格中.

由于样式和值需要同时写入到单元格中, 所以值的写入也需要缓存. 我为我的Excel日历生成脚本写的缓存机制代码如下:

cells_format ={}
cells_value={}defwrite_format(row, col, append_format: dict):
cell=xlsxwriter.worksheet.xl_rowcol_to_cell_fast(row, col) # 将行列号转换成A1这样的格式
fmt= cells_format[cell].copy() if cell in cells_format else{}
fmt.update(append_format)
cells_format[cell]=fmt
# 批量套用样式defwrite_formats(s_row, s_col, e_row, e_col, append_format: dict):for row in range(s_row, e_row + 1):for col in range(s_col, e_col + 1):
write_format(row, col, append_format)defwrite_value(row, col, value):
cell=xlsxwriter.worksheet.xl_rowcol_to_cell_fast(row, col)
cells_value[cell]=valuedefwrite_finish(wb: xlsxwriter.Workbook,
ws: xlsxwriter.Workbook.worksheet_class):
values, formats=set(cells_value.keys()), set(cells_format.keys())for c invalues.difference(formats):
ws.write(c, cells_value[c])for c invalues.intersection(formats):
ws.write(c, cells_value[c], wb.add_format(cells_format[c]))for c informats.difference(values):
ws.write_blank(c, None, wb.add_format(cells_format[c]))
首先通过write_value和write_format(s)将值和样式写入缓存字典中, 如果已存在样式, 就拷贝并追加新样式, 在最后调用write_finish完成写入. 有兴趣的读者可以将它模块化, 再整合进一些其他功能, 方便xlsxwriter的写入.
然后就可以欢快地写样式了, 最后是一个简单的小日历:
# 每周前5天颜色较深
write_formats(0, 0, 4, 4, {"bg_color":"#C8C8C8"})
write_formats(0,5, 4, 6, {"bg_color":"#D9D9D9"})
# 外框线
write_formats(0, 0,4, 0, {"left":1})
write_formats(0,6, 4, 6, {"right":1})
write_formats(0, 0, 0,6, {"top":1})
write_formats(4, 0, 4, 6, {"bottom":1})for i in range(1, 32):
write_value(i//7, i%7, i)
wb= xlsxwriter.Workbook("test.xlsx")
ws=wb.add_worksheet()
write_finish(wb, ws)
wb.close()