When to Use xlwings Instead of openpyxl
openpyxl edits the file; xlwings drives the application. That one sentence decides almost every case, because it identifies what you are actually paying for: xlwings needs Excel installed, runs only on Windows and macOS, and makes a cross-process call for every operation — and in exchange it gives you Excel's own calculation engine, its printing, its macros and its live connections. This guide, part of Choosing a Python Excel Library, sets out the four jobs where that trade is worth making.
Prerequisites
pip install xlwings
Plus a licensed installation of Excel on Windows or macOS. On Windows, xlwings sits on top of pywin32 and the same COM interface described in Automating Excel with COM and pywin32.
Job one: you need recalculated values
openpyxl does not evaluate formulas. It can write =SUM(B2:B99) into a cell, and it can read the
value Excel cached there last time the file was open — but if no copy of Excel has opened the file
since, data_only=True returns None. When the deliverable is the result, only something with a
calculation engine can produce it.
import xlwings as xw
with xw.App(visible=False) as app:
book = app.books.open("model.xlsx")
book.app.calculate() # Excel's own engine
total = book.sheets["Summary"].range("C12").value
book.save()
book.close()
print(total)
The pure-Python alternative is to compute the number yourself in pandas and write it as a literal, which is faster and testable — and the right answer whenever the formula is yours. xlwings earns its place when the formulas belong to a model somebody else maintains. Recalculate Excel Formulas Without Excel in Python covers the middle ground.
Job two: the workbook contains things openpyxl drops
Pivot table caches, slicers, some chart types, ActiveX controls and Power Query connections do not survive an openpyxl round trip. If the file must keep them and must also be edited, the edit has to happen through Excel.
import xlwings as xw
with xw.App(visible=False) as app:
book = app.books.open("dashboard.xlsm")
book.sheets["Data"].range("A1").options(index=False).value = new_frame
for sheet in book.sheets:
for table in sheet.api.PivotTables():
table.RefreshTable()
book.save()
book.close()
Note .api — that is the escape hatch to the raw COM object model, which is where anything xlwings
has not wrapped still lives.
Job three: the file is open on someone's screen
This is the one that surprises people. openpyxl reads and writes files on disk, so it cannot see
unsaved changes and cannot save over a workbook Excel has locked — the cause of the
PermissionError covered in
Handle "Permission Denied" When Writing Excel in Python.
xlwings attaches to the running instance and edits the live book.
import xlwings as xw
book = xw.books.active # whatever the user has in front of them
sheet = book.sheets["Input"]
sheet.range("B2:B10").value = [[v] for v in values]
sheet.range("B2:B10").color = (255, 242, 204)
That is also the basis of xlwings' interactive uses — a button in a workbook that calls a Python function, or a user-defined function written in Python, both covered in Call Python from Excel with an xlwings UDF.
Job four: output only Excel can produce
A PDF that paginates exactly as Excel prints it, a chart rendered as an image, a .xlsb file, a
print job with the company's page setup — all of these come from Excel's own output paths.
import xlwings as xw
with xw.App(visible=False) as app:
book = app.books.open("report.xlsx")
book.sheets["Summary"].to_pdf("summary.pdf")
book.close()
Convert an Excel File to PDF with Python compares that against the LibreOffice route, which is the one to use on a server.
Check the alternative before you commit
Each of the four jobs above has a file-level workaround, and it is worth pricing that workaround before adding a dependency on installed Excel. Sometimes the workaround is obviously better; sometimes it is obviously worse; either way the comparison is quick.
Recalculation is the one most often avoidable. If the formula is =SUM(B2:B99) and your script
produced column B in the first place, computing the total in pandas and writing the number is
faster, testable and works on a server. It stops being avoidable when the workbook is a model
somebody else owns and its formulas are the deliverable.
Native PDF export is the one most often worth replacing. LibreOffice in headless mode converts a workbook without Excel, without a desktop session and without a licence, and the output is close enough for most reports — the route taken in Convert Excel to PDF on Linux with LibreOffice.
Keeping the application from outliving the script
The failure that costs the most time with xlwings is not slowness — it is an EXCEL.EXE process left running after an exception, holding a lock on the file and, after a few scheduled runs, a noticeable share of the machine's memory. The context manager form closes the application even when the body raises, and it is worth using every time.
import xlwings as xw
def refresh(path):
with xw.App(visible=False) as app: # quits even if the body raises
app.display_alerts = False
app.screen_updating = False
book = app.books.open(path)
try:
book.app.calculate()
book.save()
finally:
book.close()
display_alerts = False is the other half. Without it a workbook that wants to ask "save changes?"
will sit on a dialog nobody can see, and the script hangs rather than fails — the single most
confusing behaviour in Excel automation, covered in more depth in
Handle COM Errors and Excel Dialog Prompts in Python.
Common pitfalls
| Symptom | Cause | Fix |
|---|---|---|
| The script hangs with no error | Excel is showing a modal dialog nobody can see | Set app.display_alerts = False, and prefer with xw.App() so it always closes |
| Orphan EXCEL.EXE processes accumulate | The app was never quit after an exception | Use the context manager, or app.quit() in a finally |
| Writing 20,000 rows takes minutes | One cross-process call per cell | Assign a whole range once: sheet.range("A2").value = rows |
| Works locally, fails on the server | No Excel installed, or no interactive session | Use openpyxl or xlsxwriter for anything scheduled |
None where a formula result should be | The workbook was never calculated | book.app.calculate() before reading |
Testing a script that needs Excel
The hidden cost of an application driver is that it makes the script hard to test. A unit test for an openpyxl function opens a temporary file and asserts on cell values; a unit test for an xlwings function needs Excel, a desktop session and several seconds of startup, which rules out running it in continuous integration.
The practical answer is to keep the Excel-dependent surface as small as possible. Put the logic in plain functions that take and return data, and let the xlwings layer do nothing but move values in and out.
def summarise(rows):
"""Pure logic — no Excel anywhere, tested in milliseconds."""
totals = {}
for region, revenue in rows:
totals[region] = totals.get(region, 0.0) + revenue
return sorted(totals.items(), key=lambda pair: -pair[1])
def write_summary(book):
"""The only part that needs Excel."""
rows = book.sheets["Raw"].range("A2").expand().value
book.sheets["Summary"].range("A2").value = summarise([(r[0], r[2]) for r in rows])
summarise is testable anywhere; write_summary is three lines that a smoke test can cover on a
developer machine. That split is worth making even in scripts that will never have a test suite,
because it is also the split that lets you swap xlwings for openpyxl later without rewriting the
part that holds the business rules. Test Excel Output with pytest
covers the assertions worth making on the file itself.
Performance and scale
The cost model is entirely about the number of boundary crossings, not the amount of data. Writing one cell and writing a 10,000-cell block cost roughly the same, because the expensive part is the call itself. Any xlwings script that is slow is almost always a loop that should have been a single range assignment.
# Slow: 10,000 round trips into Excel.
for index, value in enumerate(values, start=2):
sheet.range(f"B{index}").value = value
# Fast: one round trip.
sheet.range("B2").value = [[value] for value in values]
The other scale limit is operational rather than technical: Excel automation on a server is unsupported by Microsoft, needs an interactive desktop session, and breaks in ways that are hard to diagnose remotely. For anything that runs on a schedule, keep the file-level libraries — the argument made in Scheduling Python Excel Scripts with Cron.
Conclusion
Reach for xlwings when the value you need comes from Excel itself: recalculated formulas, objects openpyxl cannot round-trip, a workbook that is open on someone's screen, or output only Excel can produce. Everything else — bulk reads, bulk writes, formatting, scheduled jobs, anything on Linux — belongs to openpyxl and xlsxwriter, which are faster, testable and free of the requirement that a licensed copy of Excel be sitting on the machine.
Frequently asked questions
Does xlwings work on Linux? No. It drives a real copy of Excel through COM on Windows or AppleScript on macOS, so a headless Linux container cannot run it. openpyxl and xlsxwriter are the cross-platform options.
Is xlwings slower than openpyxl? Per operation, dramatically — every read or write is a cross-process call into Excel. Move whole ranges in one assignment rather than looping over cells and the difference becomes manageable, but for bulk writing a file-level library will always win.
Can xlwings run without Excel visible? It can run with the application hidden (app.visible = False), but Excel must still be installed and licensed. There is no headless mode in the sense a server operator would mean.
What happens to my script if the user has the file open? That is the case xlwings handles best. It attaches to the open workbook and edits it live, where openpyxl would either read a stale copy from disk or fail to save over a locked file.
Related
- Up one level: Choosing a Python Excel Library — where xlwings sits among the file-level libraries.
- Automating Excel with xlwings Basics — the API in depth, from ranges to macros.
- Automating Excel with COM and pywin32 — the raw interface xlwings wraps on Windows.
- Read and Write a Live Excel Workbook with xlwings — working against the copy a user has open.
- Using openpyxl for Excel File Manipulation — the cross-platform alternative for everything else.