Guide
Getting Started With Python Excel AutomationDeep dive

When to Use xlwings Instead of openpyxl

xlwings drives a real copy of Excel; openpyxl edits the file. Four jobs justify the cost — recalculation, objects openpyxl drops, live workbooks and native output.

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.

What driving the application buys, and what it costs openpyxl edits the file directly and runs anywhere, but cannot calculate formulas or keep every Excel object. xlwings drives a licensed copy of Excel, gaining recalculation and native output at the cost of platform and speed. openpyxl: the file runs on any server fast, testable no formula engine drops some objects xlwings: the app Excel recalculates keeps everything Windows or macOS a call per operation trade-off pay for Excel only when the value comes from Excel

Prerequisites

Bash
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.

Python
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.

Python
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.

Python
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.

Python
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.

The four jobs that justify driving Excel, and their alternatives Recalculating formulas, preserving pivot caches and slicers, editing a workbook that is open on screen, and producing native Excel output each have a file-level alternative that is only sometimes acceptable. Job Why openpyxl cannot The cheaper alternative Recalculate no formula engine compute it in pandas Keep pivot caches not round-tripped rebuild the sheet Edit a live file reads from disk ask the user to close it Native PDF export no print engine LibreOffice headless each row has an escape hatch — check it before paying for Excel

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.

Python
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

SymptomCauseFix
The script hangs with no errorExcel is showing a modal dialog nobody can seeSet app.display_alerts = False, and prefer with xw.App() so it always closes
Orphan EXCEL.EXE processes accumulateThe app was never quit after an exceptionUse the context manager, or app.quit() in a finally
Writing 20,000 rows takes minutesOne cross-process call per cellAssign a whole range once: sheet.range("A2").value = rows
Works locally, fails on the serverNo Excel installed, or no interactive sessionUse openpyxl or xlsxwriter for anything scheduled
None where a formula result should beThe workbook was never calculatedbook.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.

Python
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

Time is driven by boundary crossings, not by data volume Writing ten thousand cells one at a time makes ten thousand cross-process calls and is far slower than assigning the same block in a single range write. 10,000 single-cell writes 10,000 crossings one range assignment 1 crossing xlsxwriter, same data no Excel at all relative cost a slow xlwings script is nearly always a loop that should be one assignment

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.

Python
# 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.