Openpyxl - 调整列宽大小

154
我有以下脚本,可以将CSV文件转换为XLSX文件,但我的列大小非常窄。每次都需要用鼠标拖动才能读取数据。有人知道如何在openpyxl中设置列宽吗?
这是我正在使用的代码。
#!/usr/bin/python2.6
import csv
from openpyxl import Workbook
from openpyxl.cell import get_column_letter

f = open('users_info_cvs.txt', "rU")

csv.register_dialect('colons', delimiter=':')

reader = csv.reader(f, dialect='colons')

wb = Workbook()
dest_filename = r"account_info.xlsx"

ws = wb.worksheets[0]
ws.title = "Users Account Information"

for row_index, row in enumerate(reader):
    for column_index, cell in enumerate(row):
        column_letter = get_column_letter((column_index + 1))
        ws.cell('%s%s'%(column_letter, (row_index + 1))).value = cell

wb.save(filename = dest_filename)

https://dev59.com/CVoV5IYBdhLWcg3wEreD - Smart Manoj
21个回答

137
你可以估算宽度(或使用等宽字体)来实现此目标。假设数据是像这样的嵌套数组:
[['a1','a2'],['b1','b2']]

我们可以获取每列中的最大字符数,然后将宽度设置为该值。宽度恰好是等宽字体的宽度(如果没有改变其他样式的话)。即使使用可变宽度字体,这也是一个不错的估计。这种方法对公式无效。
from openpyxl.utils import get_column_letter

column_widths = []
for row in data:
    for i, cell in enumerate(row):
        if len(column_widths) > i:
            if len(cell) > column_widths[i]:
                column_widths[i] = len(cell)
        else:
            column_widths += [len(cell)]
    
for i, column_width in enumerate(column_widths,1):  # ,1 to start at 1
    worksheet.column_dimensions[get_column_letter(i)].width = column_width

有点取巧,但你的报告会更易读。

你有没有想法这里出了什么问题:http://stackoverflow.com/questions/32642026/column-widths-of-some-columns-in-openpyxl-become-zero-after-60-columns - Pyderman
2
当我使用int作为单元格的值时,由于int没有len属性,这会导致错误,有没有办法避免这种情况?谢谢! - Kevin Zhao
1
@KevinZhao 有点晚了 - 但是你的问题在这里得到了解答:https://dev59.com/UHI95IYBdhLWcg3wtwRe - jonyfries
可能还有必要补充一点,即您仍然需要使用“wb.save(filename = dest_filename)”保存工作簿。 - Francesco Pegoraro
在版本3.0.9(以及可能之前的版本)中,len(cell)会失败,并显示TypeError object of type 'Cell' has no len() - Indiana Bones
显示剩余2条评论

91

这是我对Bufke答案的改进,避免了一些数组分支并忽略了空单元格/列。

现在已针对非字符串单元格值进行修正。

ws = your current worksheet
dims = {}
for row in ws.rows:
    for cell in row:
        if cell.value:
            dims[cell.column] = max((dims.get(cell.column, 0), len(str(cell.value))))    
for col, value in dims.items():
    ws.column_dimensions[col].width = value

从openpyxl版本3.0.3开始,您需要使用

 dims[cell.column_letter] = max((dims.get(cell.column_letter, 0), len(str(cell.value))))

由于openpyxl库会引发TypeError错误,如果您传递的是数字而不是列字母给column_dimensions,因此其他所有内容都可以保持不变。


4
第6行可以使用列字母进行改进:dims[cell.column_letter] = max((dims.get(cell.column_letter, 0), len(str(cell.value))))。 - Jonathan L
还值得记住的是,这将使公式单元格变长,因此在更新长度之前,您可以添加一个检查它是否以“=”开头。 - incarnadine

56

一种更加Pythonic的方法来设置所有列的宽度,该方法至少适用于openpyxl版本2.4.0:

for column_cells in worksheet.columns:
    length = max(len(as_text(cell.value)) for cell in column_cells)
    worksheet.column_dimensions[column_cells[0].column].width = length

as_text函数应该是将值转换为适当长度的字符串,例如Python 3中的操作:

as_text函数应该是将值转换为合适长度的字符串,类似于Python 3中的操作:

def as_text(value):
    if value is None:
        return ""
    return str(value)

7
def as_text(value): 如果值不为None,则返回str(value),否则返回""。 - thorhunter
5
len(cell.value or ""),无需额外函数。 - Mira Nuata
3
如果cell.value没有实现__len__,这将抛出异常(例如intNoneType)。 - thorhunter
3
@IrinaVelikopolskaya提到datetime是另一个可能导致程序异常的例子。对我而言,as_text函数似乎是最有效的解决方法。 - Software Prophets
22
请注意,使用openpyxl 2.6版本时,这段代码会崩溃并显示TypeError: expected <class 'str'>。现在必须指定列名,例如 ws.column_dimensions[openpyxl.utils.get_column_letter(column_cells[0].column)].width = length。详情请参见 https://bitbucket.org/openpyxl/openpyxl/issues/1240/typeerror-raised-when-try-to-change-the。 - phihag
显示剩余4条评论

40
使用openpyxl 3.0.3修改列的最佳方法是使用DimensionHolder对象,它是一个将每个列映射到ColumnDimension对象的字典。 ColumnDimension可以获取bestFit, auto_size(是bestFit的别名)和width等参数。 个人而言,auto_size的效果与预期不符,我必须使用width,并发现列的最佳宽度为len(cell_value) * 1.23
要获取每个单元格的值,需要逐个迭代,但我个人没有使用它,因为在我的项目中,我只需写入工作表,所以我直接从我的数据中获取了每个列中最长的字符串。
下面的示例仅显示如何修改列的维度:
import openpyxl
from openpyxl.worksheet.dimensions import ColumnDimension, DimensionHolder
from openpyxl.utils import get_column_letter

wb = openpyxl.load_workbook("Example.xslx")
ws = wb["Sheet1"]

dim_holder = DimensionHolder(worksheet=ws)

for col in range(ws.min_column, ws.max_column + 1):
    dim_holder[get_column_letter(col)] = ColumnDimension(ws, min=col, max=col, width=20)

ws.column_dimensions = dim_holder

这对我来说似乎是最好的解决方案,而其他答案并没有正确地调整所有内容。即使在合并单元格的情况下也有效。 - Zak44

10

我在使用 merged_cells 和 autosize 时遇到了问题,它们不能正常工作。如果你也遇到了同样的问题,可以通过以下代码解决:

for col in worksheet.columns:
    max_length = 0
    column = col[0].column # Get the column name
    for cell in col:
        if cell.coordinate in worksheet.merged_cells: # not check merge_cells
            continue
        try: # Necessary to avoid error on empty cells
            if len(str(cell.value)) > max_length:
                max_length = len(cell.value)
        except:
            pass
    adjusted_width = (max_length + 2) * 1.2
    worksheet.column_dimensions[column].width = adjusted_width

9
上述已接受答案仅稍有改进,我认为更符合Python风格(宁愿请求宽恕也不要请求许可)。
column_widths = []
for row in workSheet.iter_rows():
    for i, cell in enumerate(row):
        try:
            column_widths[i] = max(column_widths[i], len(str(cell.value)))
        except IndexError:
            column_widths.append(len(str(cell.value)))

for i, column_width in enumerate(column_widths):
    workSheet.column_dimensions[get_column_letter(i + 1)].width = column_width

需要考虑cell.value不是字符串的情况。例如,如果cell.value是浮点类型,则需要进行类型转换。 - wontleave
2
哇,那已经是4年前的事了。你说得对,我进行了编辑以修复它。只是添加了一个字符串转换。 - shayst
更加符合Python风格的做法是使用 defaultdict 而不是 if 或者 try/ except IndexError,使用 .items() 而不是 enumerate - bfontaine

9
我们可以将数字转换为它们的ASCII值并将其传递给column_dimension参数。
import openpyxl as xl

work_book = xl.load_workbook('file_location')
sheet = work_book['Sheet1']
column_number = 2
column = str(chr(64 + column_number))
sheet.column_dimensions[column].width = 20
work_book.save('file_location')

8

对于刚接触该主题的用户,以下是一种更为通用、简化的解决方案(不特定于问题)。

如果想要在 openpyxl(版本 3.0.9)中更改单元格的宽度或高度,只需使用 row_dimensionscolumn_dimensions 的属性对单元格进行分配即可。

import openpyxl
wb = openpyxl.Workbook()
sheet = wb["Sheet"]


sheet["A1"] = "Tall row"
sheet["B2"] = "Wide column"

# Change height of row A1
sheet.row_dimensions[1].height = 100
# Change width of column B
sheet.column_dimensions["B"].width = 50

wb.save("StackOverflow.xlsx")

4

另一种不需要存储任何状态的方法可能是这样:

from itertools import chain
# Using `ws` as the Worksheet
for cell in chain.from_iterable(ws.iter_cols()):
    if cell.value:
        ws.column_dimensions[cell.column_letter].width = max(
            ws.column_dimensions[cell.column_letter].width,
            len(f"{cell.value}"),
        )

4
这是我参考 @Virako 的代码片段后的版本。
def adjust_column_width_from_col(ws, min_row, min_col, max_col):

        column_widths = []

        for i, col in \
                enumerate(
                    ws.iter_cols(min_col=min_col, max_col=max_col, min_row=min_row)
                ):

            for cell in col:
                value = cell.value
                if value is not None:

                    if isinstance(value, str) is False:
                        value = str(value)

                    try:
                        column_widths[i] = max(column_widths[i], len(value))
                    except IndexError:
                        column_widths.append(len(value))

        for i, width in enumerate(column_widths):

            col_name = get_column_letter(min_col + i)
            value = column_widths[i] + 2
            ws.column_dimensions[col_name].width = value

以下是如何使用它的步骤:
adjust_column_width_from_col(ws, 1,1, ws.max_column)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接