Guide
Getting Started With Python Excel AutomationDeep dive

xlwings: Run a VBA Macro From Python (Example)

A complete xlwings example for running a VBA macro from Python: open the workbook, bind the Sub with app.macro() or book.macro(), pass arguments, save, and quit Excel.

To run a VBA macro from Python with xlwings, you open the workbook in a live Excel instance, get a callable handle to the macro by name, and invoke it. xlwings routes the call through Excel's COM bridge (Windows) or AppleScript (macOS), so the Sub executes exactly as it would from the VBA editor — including any sheet edits, pivot refreshes, or exports it performs.

There are two ways to get that handle: app.macro("Name") (resolved at the Excel-application level) and book.macro("Name") (resolved against a specific workbook). Both are current and supported; app.macro() was added in xlwings 0.24.0 for application-scoped resolution, while book.macro() has been around longer. This page shows a complete, reusable example and the details that trip people up.

Running a VBA macro from Python through xlwings A Python script asks xlwings for a callable macro handle by name; the call crosses the COM (Windows) or AppleScript (macOS) bridge into a live Excel process where the VBA Sub runs, and any return value comes back to Python. A call hops from Python into live Excel and back Python your script xlwings bind by name VBA Sub runs in Excel Result back to Python crosses the COM (Windows) / AppleScript (macOS) bridge into a separate Excel process

Prerequisites

Because xlwings drives the desktop Excel application over its COM bridge (Windows) or AppleScript (macOS), the environment matters as much as the code. Before you run the example, confirm all of the following:

  • A local Excel install with an interactive desktop. This runs on Windows (Excel 2010+) or macOS (Excel 2016+) with a logged-in graphical session. It will not run on headless Linux, bare Docker images, or CI services such as GitHub Actions or AWS Lambda. If your target is a server with no Excel, you cannot run VBA there — port the macro's logic to Python instead, using pandas for data work and openpyxl for file edits, both of which run headless.
  • xlwings installed: pip install xlwings. On Windows this pulls in pywin32; macOS uses AppleScript and needs no extra COM layer.
  • A macro-enabled workbook.xlsm, .xlsb, or .xlam — that actually contains the Sub you want to call. A plain .xlsx cannot store VBA, so there is nothing to run.
  • Macros enabled for that file. An untrusted macro-enabled workbook shows a security prompt before it will run any code, and that dialog will hang an unattended job. Put the file in a Trusted Location, or allow macros under File > Options > Trust Center > Macro Settings.

If you are new to the App/Book/Sheet/Range object model this example uses, the parent guide Automating Excel with xlwings Basics walks through those objects first.

Step-by-step solution

This function opens a workbook, binds a macro by name, calls it with any positional arguments you pass, saves, and shuts Excel down in a finally block so no process is left running:

Python
import xlwings as xw
from pathlib import Path


def run_macro(workbook_path: str, macro_name: str, *args):
    """Open a macro-enabled workbook, run a VBA Sub, save, and clean up."""
    app = xw.App(visible=False, add_book=False)  # visible=True to debug dialogs
    book = None
    try:
        book = app.books.open(str(Path(workbook_path).resolve()))

        # Qualify the name with the workbook so resolution is unambiguous
        macro = app.macro(f"'{book.name}'!{macro_name}")
        macro(*args)  # arguments are passed positionally

        book.save()
    finally:
        if book is not None:
            book.close()
        app.quit()


if __name__ == "__main__":
    run_macro(r"C:\reports\monthly_summary.xlsm", "FormatAndExport", True)

The matching VBA lives in a standard module in the workbook:

Vb
Sub FormatAndExport(ByVal exportPdf As Boolean)
    ' ... formatting / export logic ...
End Sub

Using book.macro() instead

If you are on an older xlwings, or simply prefer scoping to the workbook you already have open, book.macro() is equivalent. It doesn't need the 'Workbook'! qualifier because the workbook is implied:

Python
book.macro(macro_name)(*args)

Both forms end up calling the same Sub; they differ only in where xlwings looks up the name and therefore whether you must spell out the workbook:

app.macro() versus book.macro() name resolution app.macro() resolves the name at the Excel application level, so it needs an explicit 'Workbook'!Name qualifier. book.macro() resolves against a specific workbook, so the qualifier is implied. Both paths reach the same VBA Sub. Two ways to resolve the macro name app.macro(...) "'Book'!Name" Application scope needs an explicit 'Workbook'! qualifier book.macro(...) "Name" Workbook scope qualifier is implied VBA Sub same result

Passing arguments

xlwings forwards arguments to VBA positionally — there are no keyword arguments. Order matters, and the types map naturally: Python bool to VBA Boolean, int/float to numeric types, str to String.

Python
# VBA: Sub BuildReport(ByVal region As String, ByVal year As Long)
run_macro(r"C:\reports\summary.xlsm", "BuildReport", "North", 2024)

For VBA Optional parameters you want to skip, pass None (it maps to VBA's missing/Empty) or an explicit default like "" or 0 to be unambiguous. Macros that take no arguments are just called with ():

Python
book.macro("RefreshAll")()

Common pitfalls and gotchas

AttributeError: 'App' object has no attribute 'macro' — your xlwings predates 0.24.0. Either upgrade with pip install --upgrade xlwings, or switch to the book.macro() form, which works on older versions.

Macro not found / 'WorkbookName'!Name errors — Excel resolves the name strictly. Wrap workbook names that contain spaces in single quotes: app.macro("'My Report.xlsm'!Build"). For a macro in your personal macro workbook, qualify it with that file: app.macro("'PERSONAL.XLSB'!MyRoutine").

com_error / pywintypes.com_error (Windows) or an AppleScript timeout (macOS) — Excel is blocked, often by a modal dialog or a macro-security prompt. Set app.display_alerts = False before opening, verify the file isn't already open elsewhere, and terminate any orphaned process (taskkill /F /IM EXCEL.EXE on Windows) before retrying.

Excel process lingers after the script ends — an exception escaped before app.quit(). Keep the try/finally so book.close() and app.quit() always run, even on failure.

Headless / server run fails outright — expected: there is no Excel to drive. Move the logic to pandas/openpyxl as noted above, or run on a Windows workstation with Excel installed.

Performance and scale notes

The single most important number here is the cost of a cross-process call. Every macro(...) invocation, every .value read, and every property you touch is a COM round-trip into a separate Excel process, and those round-trips dominate wall-clock time far more than the VBA itself does. A few practical consequences:

  • Do the heavy lifting inside VBA, not across the bridge. One macro call that loops over 50,000 rows in-process is dramatically faster than 50,000 individual reads or writes from Python. If you find yourself calling small macros in a tight Python loop, push that loop down into a single VBA Sub.
  • Reuse one App for a batch. Launching Excel costs a second or two. When you run macros across many workbooks, start one xw.App(visible=False, add_book=False), loop over the files opening and closing each Book, and quit the app once at the end — rather than spawning a fresh Excel per file.
  • Keep Excel quiet during the run. visible=False avoids screen repaints, and setting app.display_alerts = False and app.screen_updating = False before a long macro removes prompts and redraw overhead. Restore them (or just quit) afterwards.
  • Expect roughly linear scaling with workbook count, not data size. Because the work happens in native Excel, a macro that formats a large sheet is not much slower per row than a small one; the fixed per-file open/save/close overhead is usually what adds up across a batch.

For scheduled, unattended batches — the common production case — combine this with a real scheduler rather than leaving a script running. See running a Python Excel script on Windows Task Scheduler for the desktop-session and permissions details that trip up headless macro runs.

Where a macro belongs

Running a macro compared with rewriting it in Python Calling an existing macro suits logic that already lives in the workbook and is maintained by its owner. Rewriting it in Python suits anything that must run unattended, because a macro needs Excel. call the macro logic owned elsewhere interactive use needs Excel present rewrite in Python runs on a server testable no Excel dependency

Conclusion

Running a VBA macro from Python is a two-line operation once the setup is right: get a callable with app.macro("'Book'!Name") or book.macro("Name"), then call it with positional arguments. The parts that actually cause trouble are around that call — a live Excel install, a macro-enabled file, disabled security prompts, and a try/finally that always quits the app. Get those four right and the macro runs identically to clicking it in the VBA editor, while your Python code stays in control of the open–run–save–quit lifecycle.

Frequently asked questions

What's the difference between app.macro() and book.macro()?app.macro() resolves the name at the Excel-application level and needs a 'Workbook'!Name qualifier; book.macro() resolves against a specific workbook so the qualifier is implied. Both are current — app.macro() was added in xlwings 0.24.0, while book.macro() works on older versions too.

Can I pass keyword arguments to a VBA macro? No. xlwings forwards arguments positionally only, so order matters. Types map naturally: Python bool to VBA Boolean, int/float to numeric types, and str to String.

How do I skip an Optional VBA parameter? Pass None, which maps to VBA's missing/Empty, or pass an explicit default like "" or 0 to be unambiguous. A macro that takes no arguments is just called with ().

Why do I get AttributeError: 'App' object has no attribute 'macro'? Your xlwings predates 0.24.0. Either upgrade with pip install --upgrade xlwings, or switch to the book.macro() form, which works on older releases.

Why does my unattended run hang on a macro-security prompt? An untrusted macro-enabled file prompts before enabling macros, and that dialog blocks a headless run. Put the workbook in a Trusted Location, or enable macros under File > Options > Trust Center > Macro Settings.