openpyxl - ajustar el tamaño del ancho de la columna

82

Tengo el siguiente script que convierte un archivo CSV en un archivo XLSX, pero el tamaño de mi columna es muy estrecho. Cada vez que tengo que arrastrarlos con el mouse para leer los datos. ¿Alguien sabe cómo establecer el ancho de la columna openpyxl?

Aquí está el código que estoy usando.

#!/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)
Satish
fuente

Respuestas:

84

Puede estimar (o usar una fuente de ancho mono) para lograr esto. Supongamos que los datos son una matriz anidada como [['a1', 'a2'], ['b1', 'b2']]

Podemos obtener el máximo de caracteres en cada columna. Luego establezca el ancho a eso. El ancho es exactamente el ancho de una fuente monoespaciada (si no cambia otros estilos al menos). Incluso si usa una fuente de ancho variable, es una estimación decente. Esto no funcionará con fórmulas.

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):
    worksheet.column_dimensions[get_column_letter(i+1)].width = column_width

Un poco complicado, pero sus informes serán más legibles.

Bufke
fuente
¿
Puede
1
cuando tengo int como valor de celda, esto generará un error ya que int no tiene propiedad len, ¿hay alguna manera de evitar esto? ¡Gracias!
Kevin Zhao
1
@KevinZhao Un poco tarde, pero su pregunta se aborda aquí: stackoverflow.com/questions/2189800/…
jonyfries
53

Mi variación de la respuesta de Bufke. Evita un poco de ramificación con la matriz e ignora las celdas / columnas vacías.

Ahora arreglado para valores de celda que no son cadenas.

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

A partir de la versión 3.0.3 de openpyxl, debe usar

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

como la biblioteca openpyxl generará un TypeError si pasa column_dimensionsun número en lugar de una letra de columna, todo lo demás puede permanecer igual.

velis
fuente
2
La línea 6 se puede mejorar para usar la letra de columna: dims [cell.column_letter] = max ((dims.get (cell.column_letter, 0), len (str (cell.value))))
Jonathan L
36

Una forma aún más pitónica de establecer el ancho de todas las columnas que funciona al menos en la versión 2.4.0 de openpyxl:

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

La función as_text debería ser algo que convierta el valor en una cadena de longitud adecuada, como para Python 3:

def as_text(value):
    if value is None:
        return ""
    return str(value)
Usuario3759685
fuente
6
def as_text(value): return str(value) if value is not None else ""
thorhunter
4
@thorhunter len(cell.value or "") , no se necesitan funciones adicionales
Irina Velikopolskaya
2
@IrinaVelikopolskaya si cell.value no se implementa __len__, esto arrojará una excepción ( into NoneTypepor ejemplo)
thorhunter
2
@IrinaVelikopolskaya datetime es otro ejemplo de dónde se obtiene una excepción. La función as_text parece funcionar mejor para mí.
Software Prophets
6
Tenga en cuenta que con openpyxl 2.6, este código se bloqueará con TypeError: expected <class 'str'>. Uno tiene que especificar un nombre de columna ahora, es decir ws.column_dimensions[openpyxl.utils.get_column_letter(column_cells[0].column)].width = length.Ver bitbucket.org/openpyxl/openpyxl/issues/1240/...
phihag
10

Tengo un problema con merged_cells y el tamaño automático no funciona correctamente, si tienes el mismo problema, puedes resolverlo con el siguiente código:

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
Virako
fuente
7

Una ligera mejora de la respuesta aceptada anterior, que creo que es más pitónica (pedir perdón es mejor que pedir permiso)

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
shayst
fuente
Es necesario considerar si cell.value no es una cadena. Por ejemplo, si cell.value es de tipo flotante, se necesitará conversión de tipo
wontleave
2
wow eso fue hace 4 años. Tienes razón, aunque he editado para corregirlo. Acabo de agregar un yeso a la cuerda.
shayst
4

Todas las respuestas anteriores están generando un problema que es que la columna [0] .columna devuelve un número mientras que la hoja de cálculo.columna_dimensiones [columna] acepta solo caracteres como 'A', 'B', 'C' en lugar de columna. He modificado el código de @ Virako y ahora funciona bien.

import re
import openpyxl
..
for col in _ws.columns:
    max_lenght = 0
    print(col[0])
    col_name = re.findall('\w\d', str(col[0]))
    col_name = col_name[0]
    col_name = re.findall('\w', str(col_name))[0]
    print(col_name)
    for cell in col:
        try:
            if len(str(cell.value)) > max_lenght:
                max_lenght = len(cell.value)
        except:
            pass
    adjusted_width = (max_lenght+2)
    _ws.column_dimensions[col_name].width = adjusted_width
Ssubrat Rrudra
fuente
4

Con openpyxl 3.0.3, la mejor manera de modificar las columnas es con el objeto DimensionHolder , que es un diccionario que asigna cada columna a un objeto ColumnDimension . ColumnDimension puede obtener parámetros como bestFit , auto_size (que es un alias de bestFit) y ancho . Personalmente, auto_size no funciona como se esperaba y tuve que usar el ancho y me di cuenta de que el mejor ancho para la columna es len(cell_value) * 1.23.

Para obtener el valor de cada celda es necesario iterar sobre cada una, pero yo personalmente no lo usé porque en mi proyecto solo tenía que escribir hojas de trabajo, así que obtuve la cadena más larga en cada columna directamente en mis datos.

El siguiente ejemplo solo muestra cómo modificar las dimensiones de la columna:

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
GI Jack
fuente
3

Esta es mi versión que hace referencia al fragmento de código de @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

Y cómo usarlo es el siguiente,

adjust_column_width_from_col(ws, 1,1, ws.max_column)
solos
fuente
3

Podemos convertir números a sus valores ASCII y darle al parámetro 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')
Prashant Tiwari
fuente
3

Tuve que cambiar @ User3759685 la respuesta anterior a esto cuando se actualizó openpxyl. Recibí un error. Bueno, @phihag también informó esto en los comentarios.

for column_cells in ws.columns:
    new_column_length = max(len(as_text(cell.value)) for cell in column_cells)
    new_column_letter = (openpyxl.utils.get_column_letter(column_cells[0].column))
    if new_column_length > 0:
        ws.column_dimensions[new_column_letter].width = new_column_length + 1
Monte Jones
fuente
2

Después de la actualización de openpyxl2.5.2a a la última 2.6.4 (versión final para el soporte de python 2.x), tuve el mismo problema al configurar el ancho de una columna.

Básicamente, siempre calculo el ancho de una columna (dims es un dictado que mantiene el ancho de cada columna):

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

Luego estoy modificando la escala a algo un poco más grande que el tamaño original, pero ahora tienes que dar el valor de "Letra" de una columna y ya no un valor int (la columna de abajo es el valor y se traduce a la letra correcta):

worksheet.column_dimensions[get_column_letter(col)].width = value +1 

Esto solucionará el error visible y asignará el ancho correcto a su columna;) Espero que esto ayude.

Marco smdm
fuente
2

Aquí hay una respuesta para Python 3.8 y OpenPyXL 3.0.0.

Intenté evitar el uso de la get_column_letterfunción pero fallé.

Esta solución utiliza las expresiones de asignación recientemente introducidas, también conocidas como "operador de morsa":

import openpyxl
from openpyxl.utils import get_column_letter

workbook = openpyxl.load_workbook("myxlfile.xlsx")

worksheet = workbook["Sheet1"]

MIN_WIDTH = 10
for i, column_cells in enumerate(worksheet.columns, start=1):
    width = (
        length
        if (length := max(len(str(cell_value) if (cell_value := cell.value) is not None else "")
                          for cell in column_cells)) >= MIN_WIDTH
        else MIN_WIDTH
    )
    worksheet.column_dimensions[get_column_letter(i)].width = width
dmmfll
fuente
1
max(len(str(cell.value)) for cell in filter(None, column_cells))me parece más claro.
Nuno André
0

Esta es una solución sucia. Pero openpyxl realmente admite auto_fit. Pero no hay ningún método para acceder a la propiedad.

import openpyxl
from openpyxl.utils import get_column_letter

wb = openpyxl.load_workbook("Example.xslx")
ws = wb["Sheet1"]
for i in range(1, ws.max_column+1):
    ws.column_dimensions[get_column_letter(i)].bestFit = True
    ws.column_dimensions[get_column_letter(i)].auto_size = True
Han Luo
fuente
No funcionó. Al menos no para Microsoft Excel.
Sirmabus