解决xlsxwriter的format覆盖问题

2018-08-10 11:26:50来源:博客园 阅读 ()

新老客户大回馈,云服务器低至5折

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

 举个例子:

import xlsxwriter

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 = {}


def write_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

# 批量套用样式
def write_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)


def write_value(row, col, value):
    cell = xlsxwriter.worksheet.xl_rowcol_to_cell_fast(row, col)
    cells_value[cell] = value


def write_finish(wb: xlsxwriter.Workbook,
                 ws: xlsxwriter.Workbook.worksheet_class):
    values, formats = set(cells_value.keys()), set(cells_format.keys())
    for c in values.difference(formats):
        ws.write(c, cells_value[c])
    for c in values.intersection(formats):
        ws.write(c, cells_value[c], wb.add_format(cells_format[c]))
    for c in formats.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()

效果如下:

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:Mysql数据库是必须学好的知识点!难道爬的数据都放内存盘吗?

下一篇:掌握这8个爬取网站常用技巧,再也没有能阻挡你爬取数据的网站了