Saltar al contenido

Curso sobre como trabajar con hojas de cálculo (Excel, Calc) usando openpyxl en Python (I)

Índice:

Hay varias librerías para leer y/o escribir archivos en formato xlsx desde Python. El formato xlsx es el formato que usa Microsoft para la hoja de cálculos Microsoft Excel y se basa en el estándar Office Open XML del que ya hemos hablado bastante.

En este tutorial vamos a ver cómo podemos trabajar para leer información de ficheros en formato xlsx ya creados o para crear nuestros propios ficheros en formato xlsx.

Primero unos conceptos básicos:

  • libro: En nuestra jerga esto será la representación de nuestro fichero xlsx cuando lo abrimos en un software de hoja de cálculos.
  • hoja o pestaña: Un libro está compuesto de varias hojas o pestañas. Una hoja o pestaña es lo que usamos para escribir nuestra información. Cuando está vacia la podemos ver como una tabla vacia o rejilla (si es una hoja de trabajo). A esta tabla le podemos añadir valores numéricos, fechas, texto, fórmulas,…, en celdas. Además, sobre la rejilla podemos colocar gráficos basados en los datos de las celdas. Las hojas también pueden ser de gráficos. Veremos esto más en detalle en próximos capítulos.
  • celda: Es dónde colocamos nuestra información con la que haremos cosas.

Hay muchas cosas pero estas son las principales. Vamos a ver cómo se representan estas cosas en openpyxl:

Como siempre, vamos a empezar importando bibliotecas:

from inspect import (
    getmembers, ismethod, isdatadescriptor, ismethoddescriptor
)

import openpyxl

Veamos lo que nos ofrece:

print(dir(openpyxl))

La salida de lo anterior nos dará algo como lo siguiente:

['DEFUSEDXML', 'LXML', 'NUMPY', 'PANDAS', 'Workbook', '__author__', '__author_email__', '__builtins__', '__cached__', '__doc__', '__file__', '__license__', '__loader__', '__maintainer_email__', '__name__', '__package__', '__path__', '__spec__', '__url__', '__version__', '_constants', 'cell', 'chart', 'chartsheet', 'comments', 'compat', 'constants', 'descriptors', 'drawing', 'formatting', 'formula', 'load_workbook', 'open', 'packaging', 'pivot', 'reader', 'styles', 'utils', 'workbook', 'worksheet', 'writer', 'xml']

Puedes ver que hay nombres como Workbook y worksheet que es lo que, más arriba, hemos denominado libro y hoja, respectivamente.

Para crear un documento vacio podemos hacer lo siguiente:

wb = openpyxl.Workbook()

Si inspeccionamos un poco esta instancia de Workbook:

print(dir(wb))

Lo anterior nos mostrará algo como lo siguiente:

['_Workbook__write_only', '__class__', '__contains__', '__delattr__', '__delitem__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_active_sheet_index', '_add_sheet', '_alignments', '_borders', '_cell_styles', '_colors', '_data_only', '_date_formats', '_differential_styles', '_duplicate_name', '_external_links', '_fills', '_fonts', '_named_styles', '_number_formats', '_pivots', '_protections', '_read_only', '_setup_styles', '_sheets', '_table_styles', 'active', 'add_named_range', 'add_named_style', 'calculation', 'chartsheets', 'close', 'code_name', 'copy_worksheet', 'create_chartsheet', 'create_named_range', 'create_sheet', 'data_only', 'defined_names', 'encoding', 'epoch', 'excel_base_date', 'get_index', 'get_named_range', 'get_named_ranges', 'get_sheet_by_name', 'get_sheet_names', 'index', 'is_template', 'iso_dates', 'loaded_theme', 'mime_type', 'move_sheet', 'named_styles', 'path', 'properties', 'read_only', 'rels', 'remove', 'remove_named_range', 'remove_sheet', 'save', 'security', 'shared_strings', 'sheetnames', 'style_names', 'template', 'vba_archive', 'views', 'worksheets', 'write_only']

Tiene un montón de cosas disponibles. Vamos a ver qué podría significar cada método público de los que tenemos ahí. Para ello vamos a usar algunas utilidades del módulo inspect:

for member in getmembers(wb, predicate=ismethod):
    if not member[0].startswith("_"):
        print(member[0])
        try:
            if member[1].__doc__.split("\n")[0]:
                print(member[1].__doc__.split("\n")[0])
            else:
                print(member[1].__doc__.split("\n")[1])
        except:
            continue
        print()

Lo anterior nos da información sobre los métodos:

add_named_range
Add an existing named_range to the list of named_ranges.

add_named_style
        Add a named style

close
        Close workbook file if open. Only affects read-only and write-only modes.

copy_worksheet
Copy an existing worksheet in the current workbook

create_chartsheet
create_named_range
Create a new named_range on a worksheet

create_sheet
Create a worksheet (at an optional index).

get_index
Return the index of the worksheet.

get_named_range
Return the range specified by name.

get_named_ranges
Return all named ranges

get_sheet_by_name
Returns a worksheet by its name.

get_sheet_names


index
Return the index of a worksheet.

move_sheet
        Move a sheet or sheetname

remove
Remove `worksheet` from this workbook.

remove_named_range
Remove a named_range from this workbook.

remove_sheet
Remove `worksheet` from this workbook.

save
Save the current workbook under the given `filename`.

Veamos si tiene property‘s que puedan ser útiles:

for member in getmembers(openpyxl.workbook.Workbook, 
                         predicate=isdatadescriptor):
    if not member[0].startswith("_"):
        print(member[0])
        try:
            if member[1].__doc__.split("\n")[0]:
                print(member[1].__doc__.split("\n")[0])
            else:
                print(member[1].__doc__.split("\n")[1])
        except:
            continue
        print()

Lo anterior mostrará en pantalla:

active
Get the currently active sheet or None

chartsheets
A list of Chartsheets in this workbook

data_only
excel_base_date
mime_type
        The mime type is determined by whether a workbook is a template or

named_styles
        List available named styles

read_only
sheetnames
Returns the list of the names of worksheets in this workbook.

style_names
        List of named styles

worksheets
A list of sheets in this workbook

write_only

Vemos algunos métodos y property‘s interesantes para obtener la hoja que está activa, para obtener los nombres de las hojas, borrar alguna hoja, guardar el libro,…

Vamos a acceder a una hoja puesto que ahí es donde podemos leer y/o escribir cosas. Lo podemos hacer usando la property active:

ws = wb.active
print(type(ws))

Lo anterior debería mostrar en pantalla:

<class 'openpyxl.worksheet.worksheet.Worksheet'>

Vemos que es un objeto Worksheet. Vamos a ver lo que podemos hacer con el mismo:

print(dir(ws))

Lo anterior debería mostrar en pantalla:

['BREAK_COLUMN', 'BREAK_NONE', 'BREAK_ROW', 'HeaderFooter', 'ORIENTATION_LANDSCAPE', 'ORIENTATION_PORTRAIT', 'PAPERSIZE_A3', 'PAPERSIZE_A4', 'PAPERSIZE_A4_SMALL', 'PAPERSIZE_A5', 'PAPERSIZE_EXECUTIVE', 'PAPERSIZE_LEDGER', 'PAPERSIZE_LEGAL', 'PAPERSIZE_LETTER', 'PAPERSIZE_LETTER_SMALL', 'PAPERSIZE_STATEMENT', 'PAPERSIZE_TABLOID', 'SHEETSTATE_HIDDEN', 'SHEETSTATE_VERYHIDDEN', 'SHEETSTATE_VISIBLE', '_WorkbookChild__title', '__class__', '__delattr__', '__delitem__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_add_cell', '_add_column', '_add_row', '_cells', '_cells_by_col', '_cells_by_row', '_charts', '_clean_merge_range', '_comments', '_current_row', '_default_title', '_drawing', '_get_cell', '_hyperlinks', '_id', '_images', '_invalid_row', '_move_cell', '_move_cells', '_parent', '_path', '_pivots', '_print_area', '_print_cols', '_print_rows', '_rel_type', '_rels', '_setup', '_tables', 'active_cell', 'add_chart', 'add_data_validation', 'add_image', 'add_pivot', 'add_table', 'append', 'auto_filter', 'calculate_dimension', 'cell', 'col_breaks', 'column_dimensions', 'columns', 'conditional_formatting', 'data_validations', 'delete_cols', 'delete_rows', 'dimensions', 'encoding', 'evenFooter', 'evenHeader', 'firstFooter', 'firstHeader', 'formula_attributes', 'freeze_panes', 'insert_cols', 'insert_rows', 'iter_cols', 'iter_rows', 'legacy_drawing', 'max_column', 'max_row', 'merge_cells', 'merged_cell_ranges', 'merged_cells', 'mime_type', 'min_column', 'min_row', 'move_range', 'oddFooter', 'oddHeader', 'orientation', 'page_breaks', 'page_margins', 'page_setup', 'paper_size', 'parent', 'path', 'print_area', 'print_options', 'print_title_cols', 'print_title_rows', 'print_titles', 'protection', 'row_breaks', 'row_dimensions', 'rows', 'scenarios', 'selected_cell', 'set_printer_settings', 'sheet_format', 'sheet_properties', 'sheet_state', 'sheet_view', 'show_gridlines', 'show_summary_below', 'show_summary_right', 'tables', 'title', 'unmerge_cells', 'values', 'views']

La hoja ofrece muchas más cosas que el libro. Es lógico puesto que aquí es donde vamos a hacer la mayoría de cosas.

Como antes, vamos a inspeccionar un poco los métodos y property‘s para ver brevemente lo que hacen:

for member in getmembers(ws, predicate=ismethod):
    if not member[0].startswith("_"):
        print(member[0])
        try:
            if member[1].__doc__.split("\n")[0]:
                print(member[1].__doc__.split("\n")[0])
            else:
                print(member[1].__doc__.split("\n")[1])
        except:
            continue
        print()

Y lo anterior debería mostrar en pantalla algo como lo siguiente:

add_chart
        Add a chart to the sheet

add_data_validation
 Add a data-validation object to the sheet.  The data-validation

add_image
        Add an image to the sheet.

add_pivot
add_table
        Check for duplicate name in definedNames and other worksheet tables

append
Appends a group of values at the bottom of the current sheet.

calculate_dimension
Return the minimum bounding range for all cells containing data (ex. 'A1:M24')

cell
        Returns a cell object based on the given coordinates.

delete_cols
        Delete column or columns from col==idx

delete_rows
        Delete row or rows from row==idx

insert_cols
        Insert column or columns before col==idx

insert_rows
        Insert row or rows before row==idx

iter_cols
        Produces cells from the worksheet, by column. Specify the iteration range

iter_rows
        Produces cells from the worksheet, by row. Specify the iteration range

merge_cells
 Set merge on a cell range.  Range is a cell range (e.g. A1:E1) 

move_range
        Move a cell range by the number of rows and/or columns:

set_printer_settings
Set printer settings 

unmerge_cells
 Remove merge on a cell range.  Range is a cell range (e.g. A1:E1) 
for member in getmembers(openpyxl.worksheet.worksheet.Worksheet, 
                         predicate=isdatadescriptor):
    if not member[0].startswith("_"):
        print(member[0])
        try:
            if member[1].__doc__.split("\n")[0]:
                print(member[1].__doc__.split("\n")[0])
            else:
                print(member[1].__doc__.split("\n")[1])
        except:
            continue
        print()

Y, como antes, nos mostrará nuevas cosas por pantalla:

active_cell
columns
Produces all cells in the worksheet, by column  (see :func:`iter_cols`)

dimensions
Returns the result of :func:`calculate_dimension`

encoding
evenFooter
evenHeader
firstFooter
firstHeader
freeze_panes
max_column
The maximum column index containing data (1-based)

max_row
The maximum row index containing data (1-based)

merged_cell_ranges
Return a copy of cell ranges

min_column
The minimum column index containing data (1-based)

min_row
The minimium row index containing data (1-based)

oddFooter
oddHeader
page_breaks
parent
path
print_area
        The print area for the worksheet, or None if not set. To set, supply a range

print_title_cols
Columns to be printed at the left side of every page (ex: 'A:C')

print_title_rows
Rows to be printed at the top of every page (ex: '1:3')

print_titles
rows
Produces all cells in the worksheet, by row (see :func:`iter_rows`)

selected_cell
sheet_view
show_gridlines
show_summary_below
show_summary_right
tables
title
values
Produces all cell values in the worksheet, by row

Nos vamos a fijar ahora en el método cell, el cual nos permite acceder a la celda colocada en la posición que especifiquemos. Accedemos a la celda que está en la segunda fila y en la segunda columna:

[INCISO: Las filas y columnas empiezan en 1 y no en 0 como todo lo demás en Python].

c = ws.cell(2, 2)
print(type(c))

Lo anterior nos muestra en pantalla el tipo del nuevo objeto:

<class 'openpyxl.cell.cell.Cell'>

Tenemos ahora un objeto Cell. Como anteriormente, vamos a ver lo que tiene disponible:

print(dir(c))

Que nos muestra en pantalla:

['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', '_bind_value', '_comment', '_hyperlink', '_style', '_value', 'alignment', 'base_date', 'border', 'check_error', 'check_string', 'col_idx', 'column', 'column_letter', 'comment', 'coordinate', 'data_type', 'encoding', 'fill', 'font', 'has_style', 'hyperlink', 'internal_value', 'is_date', 'number_format', 'offset', 'parent', 'pivotButton', 'protection', 'quotePrefix', 'row', 'style', 'style_id', 'value']

Siguiendo la misma tónica que antes vamos a ver los métodos y propertys y una breve descripción:

for member in getmembers(c, predicate=ismethoddescriptor):
    if not member[0].startswith("_"):
        print(member[0])
        try:
            if member[1].__doc__.split("\n")[0]:
                print(member[1].__doc__.split("\n")[0])
            else:
                print(member[1].__doc__.split("\n")[1])
        except:
            continue
        print()
for member in getmembers(openpyxl.cell.Cell, predicate=isdatadescriptor):
    if not member[0].startswith("_"):
        print(member[0])
        try:
            if member[1].__doc__.split("\n")[0]:
                print(member[1].__doc__.split("\n")[0])
            else:
                print(member[1].__doc__.split("\n")[1])
        except:
            continue
        print()

Y nos mostrará en pantalla:

alignment
base_date
border
col_idx
The numerical index of the column

column
column_letter
comment
 Returns the comment associated with this cell

coordinate
This cell's coordinate (ex. 'A5')

data_type
encoding
fill
font
has_style
hyperlink
Return the hyperlink target or an empty string

internal_value
Always returns the value for excel.

is_date
True if the value is formatted as a date

number_format
parent
pivotButton
protection
quotePrefix
row
style
style_id
value
Get or set the value held in the cell.

Con la property value pòdemos obtener el valor de la celda o darle un valor. Vamos a darle un valor:

c.value = 9999

Y vamos a guardar el libro:

wb.save("new.xlsx")

El anterior código debería haber creado un fichero llamado ‘new.xlsx’ en la misma carpeta desde donde estés ejecutando el código. Si abrimos el nuevo fichero que acabamos de crear con un programa de hoja de cálculos deberíamos ver algo como lo siguiente:

Hoja de cálculos resultado del código anterior.

Resumen

Este primer artículo ha sido un poco un repaso al concepto de libro, hoja y celda, que son algunos de los conceptos más importantes en una hoja de cálculos y hemos visto un poco por encima cómo se han implementado estos conceptos en openpyxl y qué funcionalidad asociada tienen.

Hemos visto que la información se distribuye principalmente en un libro (objeto openpyxl.workbook.Workbook) que contiene una o más hojas o pestañas (objeto openpyxl.worksheet.worksheet.Worksheet) que, a su vez, contienen celdas (objeto openpyxl.cell.Cell) y pueden contener otras cosas.

Dentro de poco más. Estad atentas.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

67 + = sixty nine

Pybonacci