Índice:
- Introducción (este capítulo).
- El libro.
- La hoja de cálculos.
- La celda.
- Los estilos.
- Los gráficos.
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:
1 2 3 4 5 |
from inspect import ( getmembers, ismethod, isdatadescriptor, ismethoddescriptor ) import openpyxl |
Veamos lo que nos ofrece:
1 |
print(dir(openpyxl)) |
La salida de lo anterior nos dará algo como lo siguiente:
1 |
['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:
1 |
wb = openpyxl.Workbook() |
Si inspeccionamos un poco esta instancia de Workbook
:
1 |
print(dir(wb)) |
Lo anterior nos mostrará algo como lo siguiente:
1 |
['_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
:
1 2 3 4 5 6 7 8 9 10 11 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
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
:
1 |
ws = wb.active |
1 |
print(type(ws)) |
Lo anterior debería mostrar en pantalla:
1 |
<class 'openpyxl.worksheet.worksheet.Worksheet'> |
Vemos que es un objeto Worksheet
. Vamos a ver lo que podemos hacer con el mismo:
1 |
print(dir(ws)) |
Lo anterior debería mostrar en pantalla:
1 |
['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:
1 2 3 4 5 6 7 8 9 10 11 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
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) |
1 2 3 4 5 6 7 8 9 10 11 12 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
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].
1 |
c = ws.cell(2, 2) |
1 |
print(type(c)) |
Lo anterior nos muestra en pantalla el tipo del nuevo objeto:
1 |
<class 'openpyxl.cell.cell.Cell'> |
Tenemos ahora un objeto Cell
. Como anteriormente, vamos a ver lo que tiene disponible:
1 |
print(dir(c)) |
Que nos muestra en pantalla:
1 |
['__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:
1 2 3 4 5 6 7 8 9 10 11 |
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() |
1 2 3 4 5 6 7 8 9 10 11 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
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:
1 |
c.value = 9999 |
Y vamos a guardar el libro:
1 |
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:

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.