Comparison receipts
openpyxl alternatives, measured side by side
Every number on this page comes from one committed benchmark run of the pinned public packages on 2026-08-18, median of 5 rounds, with the raw results file and the harness published in the open-source repository. Reproduce it before you rely on it.
Choose by job, not by benchmark chart
- Modify an existing workbook (edit two cells in a template, keep everything else intact): only the full read/write libraries can do this at all. In this set that means WolfXL or openpyxl; XlsxWriter, PyExcelerate, python-calamine, and fastexcel structurally cannot modify an existing file.
- Generate a new workbook from data: every library here except the read-only pair applies; the timing receipts below separate them.
- Extract values out of .xlsx into Python: the read receipts below cover the read-only accelerators alongside the full engines.
- Feed a pandas, Polars, or SQL pipeline: pandas, Polars, and DuckDB wrap several of these engines and are timed separately in the published results. They move DataFrames, not workbooks: cell styles, formulas, and template structure are outside their contract.
What each library can actually do
| Library | Read .xlsx | Write .xlsx | Modify existing file | openpyxl-shaped API |
|---|---|---|---|---|
| wolfxl 2.0.1full read/write | Yes | Yes | Yes | Yes (drop-in alias) |
| openpyxl 3.1.5full read/write | Yes | Yes | Yes | It is openpyxl |
| XlsxWriter 3.2.9write-only | No | Yes | No | No |
| PyExcelerate 0.13.0write-only | No | Yes | No | No |
| python-calamine 0.8.2read-only | Yes (values) | No | No | No |
| fastexcel 0.20.2read-only | Yes (Arrow) | No | No | No |
The measured numbers, one committed run
AMD EPYC 9655, Python 3.13.15, median of 5 rounds per case. The large cases are a 200,000-row, 8-column sheet (1.6 million cells); the read fixture was written by openpyxl so no reader parses its own writer's output. Peak RSS comes from a separate pass that runs each case once per engine in a fresh process. Full tables, including mixed-type writes and DataFrame engines, are in the published results file (raw JSON, harness source).
| Library | Read 1.6M cells | Write 1.6M cells | Peak RSS, write | Peak RSS, read |
|---|---|---|---|---|
| wolfxl 2.0.1full read/write | 0.3868 s | 0.7258 s | 610 MiB | 153 MiB |
| openpyxl 3.1.5full read/write | 4.5856 s | 8.0567 s | 743 MiB | 169 MiB |
| XlsxWriter 3.2.9write-only | not supported | 4.6858 s | 430 MiB | not supported |
| PyExcelerate 0.13.0write-only | not supported | 3.6387 s | 264 MiB | not supported |
| python-calamine 0.8.2read-only | 0.5771 s | not supported | not supported | 286 MiB |
| fastexcel 0.20.2read-only | 0.4030 s | not supported | not supported | 267 MiB |
On the 1.6-million-cell workbook, WolfXL wrote in 0.73 s (11.10x openpyxl, 6.46x XlsxWriter, 5.01x PyExcelerate) and read every value back in 0.39 s (11.85x openpyxl, 1.49x python-calamine, 1.04x fastexcel).
| Library | Write 500K unique strings |
|---|---|
| wolfxl 2.0.1full read/write | 0.3091 s |
| openpyxl 3.1.5full read/write | 3.1452 s |
| XlsxWriter 3.2.9write-only | 2.1100 s |
| PyExcelerate 0.13.0write-only | 1.3407 s |
String-heavy output is where real reports spend their time: WolfXL wrote 500,000 unique strings in 0.31 s, 10.18x openpyxl and 6.83x XlsxWriter.
Where the alternatives win or tie
- Write peak memory: the write-only engines hold less memory while generating a file (XlsxWriter 430 MiB, PyExcelerate 264 MiB, WolfXL 610 MiB on the 1.6M-cell case) because they never keep a workbook model around for re-editing. That is the same design decision that makes them unable to read or modify anything.
- Pure value extraction: fastexcel (0.4030 s) ties WolfXL (0.3868 s) on the 1.6M-cell read and returns Arrow tables directly, which suits DataFrame-first pipelines that never touch workbook structure.
- Float precision: PyExcelerate, tablib, and pyexcel serialize floats with fewer significant digits than the other writers (a value like 8/7 does not round-trip bit-exactly), so their write numbers carry a precision tradeoff.
- Pure-Python portability: pylightxl installs anywhere Python runs but took 241.09 s on the 1.6M-cell write (332x WolfXL); it fits small files, not this scale.
- Small workbooks: below roughly 10,000 cells every library here is sub-second; the differences that matter show up at scale or on the modify path.
Switch from openpyxl
WolfXL Community follows the openpyxl-shaped API, so the switch is an install and an import change:
pip install wolfxl
# then
import wolfxl as openpyxlReproduce every number
The harness is public and deterministic about scope: write-only libraries run only write cases, read-only libraries only read cases, and engines that exceed the per-round budget are recorded as DNF rather than dropped.
git clone https://github.com/SynthGL/wolfxl-community
cd wolfxl-community
python -m venv .bench && .bench/bin/pip install wolfxl==2.0.1 openpyxl==3.1.5 \
xlsxwriter pyexcelerate python-calamine fastexcel pyarrow pylightxl pandas \
polars duckdb tablib pyexcel pyexcel-xlsx xlsx2csv
.bench/bin/python benchmarks/benchmark_python_excel_ecosystem.py \
--rounds 5 --output-dir /tmp/ecosystem-results --prefix my-runNumbers are from the pinned versions, hardware, and OS recorded in the results JSON. Different machines will produce different absolute times.