How-to
Getting Started With Python Excel AutomationDeep dive

Run Python from an Excel Button with xlwings RunPython

Let Excel users run your Python code with a button — xlwings quickstart, the RunPython VBA call, Book.caller() to reach the calling workbook, passing arguments, configuring the interpreter, testing with a mock caller, and distributing the workbook.

Some automation belongs inside the workbook people already use: a Refresh button on a planning sheet that pulls the latest figures, a Validate button that checks what someone typed, a Build report button that turns the current selection into a formatted output. VBA can do the clicking; Python is better at the work behind it. xlwings joins the two: a one-line VBA macro calls RunPython, Python runs your function, and the function reads and writes the workbook that pressed the button. This guide, part of Automating Excel With xlwings: The Basics, builds that setup end to end.

What happens when the user clicks the button The user clicks a button in Excel. The button's VBA macro calls RunPython with an import and a function call. xlwings starts Python, which runs the function. The function gets the calling workbook with Book.caller, reads the inputs, and writes the results back into the workbook the user is looking at. User Excel + VBA Python 1. clicks "Build report" 2. RunPython "import report; report.main()" 3. Book.caller() → read and write 4. results on the sheet

Prerequisites

Desktop Excel on Windows or macOS, Python with xlwings, and the xlwings Excel add-in (unless you use a standalone workbook, Step 6):

Bash
pip install xlwings pandas
xlwings addin install

The add-in adds an xlwings tab to Excel's ribbon and provides the RunPython function to VBA. Macros must be enabled for the workbook, because the button runs a VBA macro.

Step 1 — Create a project with quickstart

xlwings can generate a matching workbook and Python file:

Bash
xlwings quickstart report

This creates a folder report containing report.xlsm and report.py. The workbook's VBA module already has a macro wired to the Python file, and by default xlwings looks for the Python module next to the workbook, so the two travel together. Keeping each button-driven tool in its own quickstart folder avoids the most common problem — Python not finding the module — before it starts.

Step 2 — Write the Python function

The Python side is an ordinary function. The only special line is xw.Book.caller(), which returns the workbook whose button started the run:

Python
# report.py
import pandas as pd
import xlwings as xw

def main():
    wb = xw.Book.caller()
    data = wb.sheets["Data"].range("A1").options(pd.DataFrame, header=1, index=False,
                                                 expand="table").value
    summary = (data.groupby("region", as_index=False)["amount"].sum()
                   .sort_values("amount", ascending=False))

    out = wb.sheets["Summary"]
    out.clear_contents()
    out.range("A1").options(index=False).value = summary
    out.range("D1").value = f"Built {pd.Timestamp.now():%Y-%m-%d %H:%M} from {len(data)} rows"

The function reads the whole data block in one call and writes the summary in one call — the bulk pattern from Speed Up xlwings with Bulk Reads and Writes. A button that makes the user wait is a button they stop using.

Step 3 — Wire the button

In the workbook's VBA editor (Alt+F11), the macro that calls Python is one line:

Vb
Sub BuildReport()
    RunPython "import report; report.main()"
End Sub

Then add the button: on the Developer tab choose Insert → Button (Form Control), draw it on the sheet, and assign the BuildReport macro. The string passed to RunPython is Python code, so it can import any module the interpreter can find and call any function in it. Keep it to an import and a call — logic in that string is hard to read and impossible to test.

The three pieces of a button-driven Python tool A button on the sheet is assigned a VBA macro. The macro contains a single RunPython call naming a module and function. The Python module does the work and reaches the workbook through Book.caller. Button on the sheet Developer → Insert → Button assigned macro VBA: Sub BuildReport() RunPython "import report; report.main()" starts Python report.py: main() xw.Book.caller(), read, compute, write

Step 4 — Pass arguments from the sheet

Different buttons often run the same function with different settings — one per region, say. Either pass literal arguments in the RunPython string, or better, have the function read its inputs from named cells so users change them without touching VBA:

Python
def main_for_region():
    wb = xw.Book.caller()
    region = wb.sheets["Controls"].range("SelectedRegion").value
    start = wb.sheets["Controls"].range("StartDate").value      # arrives as a datetime
    data = wb.sheets["Data"].range("A1").options(pd.DataFrame, header=1, index=False,
                                                 expand="table").value
    view = data[(data["region"] == region) & (data["order_date"] >= start)]
    wb.sheets["Summary"].range("A1").options(index=False).value = view

Named cells (SelectedRegion, StartDate) keep working when someone inserts rows above them, and a data-validation dropdown on the region cell stops typos from reaching the code. Dropdowns are covered in Add Dropdown Data Validation to Excel with openpyxl.

Step 5 — Configure the interpreter

RunPython needs to know which Python to start. Set it once in the xlwings ribbon (the Interpreter box) or, to keep the setting with the workbook, in a sheet named xlwings.conf with two columns of keys and values:

KeyValue
Interpreter_WinC:\tools\report-env\Scripts\python.exe
Interpreter_Mac/Users/shared/report-env/bin/python
PYTHONPATHC:\tools\report

Pointing at a dedicated virtual environment keeps the tool's dependencies separate from anything else on the machine. If users have different paths, the ribbon setting per user is simpler; if the machine is shared or managed, the sheet travels with the workbook.

Step 6 — Test without clicking, and distribute

Clicking a button is a slow way to debug. set_mock_caller() makes a workbook opened from Python look like the caller, so the function runs from your editor, breakpoints and all:

Python
if __name__ == "__main__":
    xw.Book("report.xlsm").set_mock_caller()
    main()

To give the tool to people who do not have the add-in, create it with xlwings quickstart report --standalone. That copies the xlwings VBA code into the workbook, so only Python and the xlwings package are needed on the user's machine. Macros must still be allowed: files that arrive by email or download are blocked by default, so distribute through a trusted network location or have users unblock the file.

Handling errors

When the Python function raises, xlwings shows the traceback in a message box — useful to you, alarming to users. Catch errors at the top of the function, show a plain message in the sheet, and keep the full traceback in a log file you can ask for:

Python
import logging
import traceback

logging.basicConfig(filename="report.log", level=logging.INFO)

def safe_main():
    wb = xw.Book.caller()
    status = wb.sheets["Controls"].range("Status")
    try:
        main()
        status.value = "Done"
    except Exception:
        logging.error(traceback.format_exc())
        status.value = "Something went wrong — the details are in report.log"

Point the button at safe_main instead of main. For turning individual functions into Excel formulas rather than button actions, see Call Python from Excel with an xlwings UDF.

Button, formula or scheduled job?

A button is one of three ways to put Python behind a workbook, and it is worth choosing deliberately. A button suits an action a person decides to take at a moment of their choosing — rebuild this summary now, validate what I just typed, export this selection. A user-defined function suits a calculation that should update like any other formula whenever its inputs change. A scheduled job suits work that must happen whether or not anyone opens the workbook, such as producing the Monday report at 06:00.

Choosing between a button, a formula and a scheduled job If a person triggers the action when they choose, use a button with RunPython. If the result should recalculate like a formula when inputs change, use an xlwings UDF. If the work must happen on a timetable without anyone opening Excel, run a scheduled Python job that writes the workbook. What starts the work? a person decides Button + RunPython explicit action inputs change xlwings UDF recalculates like a formula the clock Scheduled job no Excel session needed

Mixing them is common: a scheduled job refreshes the data sheet every morning, and a button lets a planner rebuild the summary after changing assumptions. Keep the shared logic in one Python module that both call, so the numbers agree however they were produced.

Common pitfalls

  • Module not found. The Python file must be on the interpreter's path — next to the workbook by default, or in PYTHONPATH. Most "No module named report" errors are a moved file.
  • Wrong interpreter. The ribbon or xlwings.conf points at a Python without xlwings or pandas installed. Check the interpreter path first when a button that works for you fails for a colleague.
  • Long runs with no feedback. Excel looks frozen while Python works. Write a "Working…" status before the heavy step and keep the work fast.
  • Two users, one workbook. Each click runs Python on the clicking user's machine against the copy of the workbook they have open. If the workbook lives on a shared drive, two people pressing the button at once each change their own copy — co-authoring rules still apply to what gets saved.
  • Editing the macro string. Logic inside RunPython "..." is invisible to tests and linters. Keep it to an import and one call.

Conclusion

A button-driven Python tool has three parts: a button, a one-line VBA macro calling RunPython, and a Python function that finds its workbook with xw.Book.caller(). Generate the pair with xlwings quickstart, read inputs from named cells, configure the interpreter per user or in xlwings.conf, debug with set_mock_caller(), distribute with --standalone, and wrap the entry point so users see a plain message while the traceback goes to a log.

Frequently asked questions

What does RunPython do? RunPython is a VBA function provided by xlwings. It starts Python, runs the code string you give it — typically an import and a function call — and lets that code reach the workbook that called it through xw.Book.caller().

Do users need Python installed? Yes. RunPython runs Python on the user's machine, so each user needs a Python installation with xlwings and your code's dependencies, and the workbook's configuration must point at that interpreter.

How do I test the Python side without clicking the button? Open the workbook from Python and call set_mock_caller() on it. xw.Book.caller() then returns that workbook, so you can run and debug the function from your editor.

Can I distribute the workbook without the xlwings add-in? Yes. Create the project with xlwings quickstart --standalone, which copies the xlwings VBA module into the workbook itself, so recipients do not need the add-in — they still need Python and the xlwings package.

More on xlwings

More on xlwings →