在xslxwriter中模拟自动调整列

Mic*_*ter 28 python xlsxwriter

我想在Python的xlsxwriter中模拟Excel自动调整功能.根据此网址,它不是直接支持的:http: //xlsxwriter.readthedocs.io/worksheet.html

但是,循环遍历工作表上的每个单元格并确定列的最大大小应该非常简单,只需使用worksheet.set_column(row,col,width)来设置宽度.

阻止我写这篇文章的复杂因素是:

  1. 该URL未指定set_column的第三个参数的单位.
  2. 我找不到一种方法来测量我想要插入单元格的项目的宽度.
  3. xlsxwriter似乎没有回读特定单元格的方法.这意味着我需要在写入单元格时跟踪每个单元格的宽度.如果我可以循环遍历所有单元格会更好,这样就可以编写通用例程.

Col*_*ond 30

作为一般规则,您希望列的宽度略大于列中最长字符串的大小.xlsxwriter列的1个单位大约等于一个字符的宽度.因此,您可以通过将每列设置为该列中的最大字符数来模拟自动调整.

例如,在使用pandas dataframes和xlsxwriter时,我倾向于使用下面的代码.

它首先找到索引的最大宽度,它始终是pandas excel渲染数据帧的左列.然后,它返回从左到右移动的每个剩余列的所有值的最大值和列名称.

对于您正在使用的任何数据,调整此代码应该不会太困难.

def get_col_widths(dataframe):
    # First we find the maximum length of the index column   
    idx_max = max([len(str(s)) for s in dataframe.index.values] + [len(str(dataframe.index.name))])
    # Then, we concatenate this to the max of the lengths of column name and its values for each column, left to right
    return [idx_max] + [max([len(str(s)) for s in dataframe[col].values] + [len(col)]) for col in dataframe.columns]

for i, width in enumerate(get_col_widths(dataframe)):
    worksheet.set_column(i, i, width)
Run Code Online (Sandbox Code Playgroud)

  • 使用 @DanLenski 的逻辑,这就是我所使用的:`widths = [len(col) * 1.5 if len(col) > 25 else 25 for col in cols]`。这允许您设置最小宽度。我的基于列标题长度,但可以根据需要进行调整。 (4认同)
  • _“您希望列的宽度比列中最长字符串的大小大一点。”_ 我所做的是取最大字符长度,然后乘以 **1.25**。在大多数情况下似乎工作正常。 (3认同)

jmc*_*ara 27

2023 年 1 月更新。

XlsxWriter 3.0.6+ 现在支持autofit()工作表方法:

from xlsxwriter.workbook import Workbook

workbook = Workbook('autofit.xlsx')
worksheet = workbook.add_worksheet()

# Write some worksheet data to demonstrate autofitting.
worksheet.write(0, 0, "Foo")
worksheet.write(1, 0, "Food")
worksheet.write(2, 0, "Foody")
worksheet.write(3, 0, "Froody")

worksheet.write(0, 1, 12345)
worksheet.write(1, 1, 12345678)
worksheet.write(2, 1, 12345)

worksheet.write(0, 2, "Some longer text")

worksheet.write(0, 3, "http://ww.google.com")
worksheet.write(1, 3, "https://github.com")

# Autofit the worksheet.
worksheet.autofit()

workbook.close()
Run Code Online (Sandbox Code Playgroud)

输出:

在此输入图像描述

或者使用熊猫:

import pandas as pd

# Create a Pandas dataframe from some data.
df = pd.DataFrame({
    'Country':    ['China',    'India',    'United States', 'Indonesia'],
    'Population': [1404338840, 1366938189, 330267887,       269603400],
    'Rank':       [1,          2,          3,               4]})

# Order the columns if necessary.
df = df[['Rank', 'Country', 'Population']]

# Create a Pandas Excel writer using XlsxWriter as the engine.
writer = pd.ExcelWriter('pandas_autofit.xlsx', engine='xlsxwriter')
df.to_excel(writer, sheet_name='Sheet1', index=False)

# Get the xlsxwriter workbook and worksheet objects.
workbook = writer.book
worksheet = writer.sheets['Sheet1']

worksheet.autofit()

# Close the Pandas Excel writer and output the Excel file.
writer.close()

Run Code Online (Sandbox Code Playgroud)

输出:

在此输入图像描述

  • @Nelson 我一看到你的错误报告。:-) (3认同)
  • 6 小时前发布 :D (2认同)
  • @JohnDoe `autofit()` 有限制,因为它是模拟拟合(请参阅[文档](https://xlsxwriter.readthedocs.io/worksheet.html#autofit))。但是,URL 应该按照文档中的示例所示工作:https://xlsxwriter.readthedocs.io/example_autofit.html 也许可以打开另一个问题或错误报告。 (2认同)

RC_*_*r07 7

我最近遇到了同样的问题,这就是我想到的:

r = 0
c = 0
for x in list:
    worksheet.set_column('{0}:{0}'.format(chr(c + ord('A'))), len(str(x)) + 2)
    worksheet.write(r, c, x)
    c += 1
Run Code Online (Sandbox Code Playgroud)

在我的示例中r,将是您要输出到的行号,c将是您要输出到的列号(均为 0 索引),并且将是您希望在单元格中显示的x值。list

'{0}:{0}'.format(chr(c + ord('A')))片段获取提供的列号并将其转换为 xlsxwriter 接受的列字母,因此 ifc = 0 set_column会看到'A:A',if c = 1then 会看到'B:B',依此类推。

len(str(x)) + 2部分确定您尝试输出的字符串的长度,然后向其添加 2 以确保 Excel 单元格足够宽,因为字符串的长度与单元格的宽度并不完全相关。您可能想玩,而不是添加2或可能更多,具体取决于您的数据。

xlsxwriter 接受的单位有点难以解释。当您在 Excel 中并将鼠标悬停在可以更改列宽的位置时,您将看到Width: 8.43 (64 pixels)。在这个例子中,它接受的单位是8.43,我认为是厘米?但 Excel 甚至没有提供单位,至少没有明确提供。

注意:我只在包含 1 行数据的 Excel 文件上尝试过这个答案。如果您有多行,则需要有一种方法来确定哪一行将具有“最长”信息,并且仅将其应用于该行。但是,如果无论行如何,每列的大小都大致相同,那么这应该适合您。

祝你好运,我希望这会有所帮助!


dfr*_*h22 6

我同意科尔·戴蒙德(Cole Diamond)的观点。我需要做一些非常相似的事情,对我来说效果很好。其中self.columns是我的列列表

def set_column_width(self):
    length_list = [len(x) for x in self.columns]
    for i, width in enumerate(length_list):
        self.worksheet.set_column(i, i, width)
Run Code Online (Sandbox Code Playgroud)

  • 当然,提出问题,发布链接。我会回答。 (2认同)

Fra*_*anz 6

该 URL 没有指定 set_column 的第三个参数的单位。

列宽以字体 Calibri 中“0”字符宽度的倍数给出,大小为 11(这是 Excel 标准)。

我找不到一种方法来测量我想插入到单元格中的项目的宽度。

为了掌握字符串的确切宽度,您可以使用tkinter的功能以像素为单位测量字符串长度,具体取决于字体/大小/重量/等。如果您定义字体,例如

reference_font = tkinter.font.Font(family='Calibri', size=11)
Run Code Online (Sandbox Code Playgroud)

之后您可以使用它的measure方法来确定以像素为单位的字符串宽度,例如

reference_font.measure('This is a string.')
Run Code Online (Sandbox Code Playgroud)

为了对 Excel 表格中的单元格执行此操作,您需要考虑其格式(它包含有关所用字体的所有信息)。这意味着,如果您使用 将某些内容写入表格worksheet.write(row, col, cell_string, format),您可以获得使用的字体,如下所示:

used_font = tkinter.font.Font(family     = format.font_name,
                              size       = format.font_size,
                              weight     = ('bold' if format.bold else 'normal'),
                              slant      = ('italic' if format.italic else 'roman'),
                              underline  = format.underline,
                              overstrike = format.font_strikeout)
Run Code Online (Sandbox Code Playgroud)

然后确定单元格宽度为

cell_width = used_font.measure(cell_string+' ')/reference_font.measure('0')
Run Code Online (Sandbox Code Playgroud)

将空格添加到字符串以提供一些边距。通过这种方式,结果实际上非常接近 Excel 的自动调整结果,因此我认为 Excel 就是这样做的。

为了使tkinter魔法起作用,tkinter.Tk()必须打开一个实例(一个窗口),因此返回单元格所需宽度的函数的完整代码如下所示:

import tkinter
import tkinter.font

def get_cell_width(cell_string, format = None):
  root = tkinter.Tk()
  reference_font = tkinter.font.Font(family='Calibri', size=11)
  if format:
    used_font = tkinter.font.Font(family     = format.font_name,
                                  size       = format.font_size,
                                  weight     = ('bold' if format.bold else 'normal'),
                                  slant      = ('italic' if format.italic else 'roman'),
                                  underline  = format.underline,
                                  overstrike = format.font_strikeout)
  else:
    used_font = reference_font
  cell_width = used_font.measure(cell_string+' ')/reference_font.measure('0')
  root.update_idletasks()
  root.destroy()
  return cell_width
Run Code Online (Sandbox Code Playgroud)

当然root,如果要频繁执行,您希望从函数中获得处理和参考字体创建。此外,为您的工作簿使用查找表格式->字体可能会更快,这样您就不必每次都定义使用的字体。

最后,可以处理单元串中的换行符:

pixelwidths = (used_font.measure(part) for part in cell_string.split('\n'))
cell_width = (max(pixelwidths) + used_font.measure(' '))/reference_font.measure('0')
Run Code Online (Sandbox Code Playgroud)

此外,如果您使用 Excel 过滤器功能,下拉箭头符号还需要 18 个像素(在 Excel 中放大 100%)。并且可能会有跨越多列的合并单元格......还有很大的改进空间!

xlsxwriter 似乎没有读回特定单元格的方法。这意味着我需要在编写单元格时跟踪每个单元格的宽度。如果我可以循环遍历所有单元格会更好,这样就可以编写通用例程。

如果您不喜欢跟踪自己的数据结构,至少有以下三种方法:

(A) 注册一个写处理程序来完成这项工作:
您可以为所有标准类型注册一个写处理程序。在处理函数中,您只需传递 write 命令,同时也进行簿记 wrt。列宽。这样,您最终只需要读取并设置最佳列宽(在关闭 之前workbook)。

# add worksheet attribute to store column widths
worksheet.colWidths = [0]*number_of_used_columns
# register write handler
for stdtype in [str, int, float, bool, datetime, timedelta]:
  worksheet.add_write_handler(stdtype, colWidthTracker)


def colWidthTracker(sheet, row, col, value, format):
  # update column width
  sheet.colWidths[col] = max(sheet.colWidths[col], get_cell_width(value, format))
  # forward write command
  if isinstance(value, str):
    if value == '':
      sheet.write_blank(row, col, value, format)
    else:
      sheet.write_string(row, col, value, format)
  elif isinstance(value, int) or isinstance(value, float):
    sheet.write_number(row, col, value, format)
  elif isinstance(value, bool):
    sheet.write_boolean(row, col, value, format)
  elif isinstance(value, datetime) or isinstance(value, timedelta):
    sheet.write_datetime(row, col, value, format)
  else:
    raise TypeError('colWidthTracker cannot handle this type.')


# and in the end...
for col in columns_to_be_autofitted:    
  worksheet.set_column(col, col, worksheet.colWidths[col])
Run Code Online (Sandbox Code Playgroud)

(B) 使用上面的 karolyi 的答案来查看存储在 XlsxWriter 内部变量中的数据。但是,模块的作者不鼓励这样做,因为它可能会在未来的版本中中断。

(C) 遵循jmcnamara建议:继承并覆盖默认工作表类并添加一些自动调整代码,例如:xlsxwriter.readthedocs.io/example_inheritance2.html


Sou*_*tra 5

科尔戴蒙德的回答很棒。我刚刚更新了子例程来处理多索引行和列。

def get_col_widths(dataframe):
    # First we find the maximum length of the index columns   
    idx_max = [max([len(str(s)) for s in dataframe.index.get_level_values(idx)] + [len(str(idx))]) for idx in dataframe.index.names]
    # Then, we concatenate this to the max of the lengths of column name and its values for each column, left to right
    return idx_max + [max([len(str(s)) for s in dataframe[col].values] + \
                          [len(str(x)) for x in col] if dataframe.columns.nlevels > 1 else [len(str(col))]) for col in dataframe.columns]
Run Code Online (Sandbox Code Playgroud)