When pandas shows AttributeError on OpenPyXLWriter.book, stop setting book and use ExcelWriter append mode with if_sheet_exists.
What This Error Message Really Means
Quick check: you’re trying to append to an existing Excel file and your code still assigns to writer.book or writer.sheets. In current pandas releases, those are read-only properties, so attempts to set them raise the exact message you saw: AttributeError: property ‘book’ of ‘OpenPyXLWriter’ object has no setter. In plain terms, pandas no longer lets you patch those internals directly; use the supported ExcelWriter parameters instead.
The modern path is simple: create an ExcelWriter with the openpyxl engine, open in append mode using mode="a", then choose how to handle an existing sheet with if_sheet_exists. That combination covers new-sheet adds, clean sheet replacement, or same-sheet overlays without touching private attributes.
Root Causes And The Fix In One Place
Here are the common patterns that trigger the exception and what to do instead. Pick the one that matches your code.
- Setting
writer.book = load_workbook(...)— Remove the assignment. Open the file withmode="a"onExcelWriterand control collisions withif_sheet_exists. - Setting
writer.sheets = {name: wb[name] ...}— Drop this entirely. The writer manages its sheet map; steer behavior via documented options. - Appending below current rows — Read the last used row from the target sheet, then pass
startrow=toto_excel(). Withopenpyxl, usews.max_row. - Missing overlay/replace behavior — If
if_sheet_existsis not recognized, upgrade pandas to a recent version.
import pandas as pd
from openpyxl import load_workbook
path = "report.xlsx"
df = pd.DataFrame({"A": [1, 2], "B": [3, 4]})
# Append to an existing sheet, preserving content
with pd.ExcelWriter(path, engine="openpyxl", mode="a", if_sheet_exists="overlay") as writer:
# Find last used row (optional but handy)
book = load_workbook(path)
ws = book["Sheet1"]
start = ws.max_row # write after last row
df.to_excel(writer, sheet_name="Sheet1", index=False, header=False, startrow=start)
Why AttributeError: Property ‘Book’ Of ‘OpenPyXLWriter’ Object Has No Setter Appears
Short reason: pandas exposes book and sheets as properties with no public setter. Assigning to them is blocked, which triggers the message that the property has no setter. This aligns with standard Python property rules and avoids reliance on internals that change across versions.
The documented surface is: choose an engine (openpyxl for .xlsx), set the file mode, and call DataFrame.to_excel(). When a target sheet already exists, set if_sheet_exists to "error", "new", "replace", or "overlay". That keeps intent clear and code compatible.
Working, Modern Patterns For Appending
These drop the brittle writer.book/writer.sheets edits and rely on documented behavior.
Append To A New Sheet Without Touching Others
with pd.ExcelWriter("data.xlsx", engine="openpyxl", mode="a") as w:
df.to_excel(w, sheet_name="NewSheet", index=False)
Write Over An Existing Sheet Cleanly
with pd.ExcelWriter(
"data.xlsx",
engine="openpyxl",
mode="a",
if_sheet_exists="replace"
) as w:
df.to_excel(w, sheet_name="Sheet1", index=False)
Overlay Columns Or Rows In Place
with pd.ExcelWriter(
"data.xlsx",
engine="openpyxl",
mode="a",
if_sheet_exists="overlay"
) as w:
df1.to_excel(w, sheet_name="Sheet1")
# place df2 three columns to the right on the same sheet
df2.to_excel(w, sheet_name="Sheet1", startcol=3, header=False, index=False)
Note: overlay requires a compatible pandas version. Update if your install doesn’t recognize the parameter.
Version Checks, Engine Choices, And Edge Cases
- Pandas version matters — The
if_sheet_existsparameter shipped in the 1.3/1.4 era and is documented across current releases. Update a pinned older environment to unlock these safer patterns. - Engine choice — Use
openpyxlto edit existing.xlsxfiles. Usexlsxwriterwhen creating a brand-new workbook and you want speed and clean formatting defaults. - Repeated writes — Keep the writer inside a short
withblock. Open, write, close. Don’t share a single writer across long code paths. - Formulas and formatting —
openpyxlpreserves common items, but very complex features may not round-trip perfectly. Test on a copy when files include pivots, slicers, or volatile formulas. - Large exports — Split content across sheets, drop the DataFrame index unless needed, and pre-set date formats via
ExcelWriter(..., date_format="YYYY-MM-DD").
# Create a fresh file with XlsxWriter (no append)
with pd.ExcelWriter("fresh.xlsx", engine="xlsxwriter", date_format="YYYY-MM-DD") as w:
df.to_excel(w, index=False, sheet_name="Data")
Common Pitfalls And Safe Workarounds
- Appending to the wrong sheet — Default append behavior can suffix names (
Sheet1→Sheet1 1). Pickif_sheet_exists="replace"or"overlay"to control outcomes. - Headers duplicated mid-sheet — When appending, pass
header=Falseso column labels don’t reappear in the middle of the data. - Index column appearing — Pass
index=Falseunless you truly want the index saved. - Open file locked by Excel — Close the workbook in Excel before writing; Windows holds an exclusive lock.
- Need exact placement — Read
ws.max_rowandws.max_columnto position withstartrowandstartcol, then suppress headers as needed.
Quick Reference: Causes, Fixes, And Notes
| Symptom | Cause | Fix |
|---|---|---|
AttributeError on writer.book |
Property has no public setter | Create writer with mode="a"; choose if_sheet_exists |
AttributeError on writer.sheets |
Sheets map is read-only | Stop assigning; let pandas manage sheets internally |
| Data goes to suffixed sheet | Name collision while appending | Use if_sheet_exists="replace" or "overlay" |
| Rows overwrite header | No start row computed | Get ws.max_row and pass startrow= |
overlay not available |
Old pandas | Upgrade pandas to a recent release |
Sources And Further Reading
See the pandas documentation for ExcelWriter append mode and the background on the read-only properties, along with community threads that reported the exact error and the modern fixes. The examples above follow those recommendations and avoid touching internal attributes.
