csv.register_dialect() Overwrites 'excel' (tested)
csv.register_dialect() lets you give a dialect any name you want — including 'excel', the name of the built-in default. Nothing stops you, and nothing warns you. We measured what actually breaks afterward, and it's not what you'd expect.
1. Overwriting the built-in 'excel' dialect raises no error
import csv
csv.get_dialect("excel").delimiter
','
csv.register_dialect("excel", delimiter=";")
csv.get_dialect("excel").delimiter
';'
Measured: csv.register_dialect() has no protection for built-in names. Re-registering "excel" silently replaces it in the global dialect registry for the rest of the process — same "one global mutable setting" shape as csv.field_size_limit(), but applied to the dialect every piece of code assumes is the stable default.
2. Code that names "excel" explicitly breaks immediately
import io
f = io.StringIO("a;b\n1;2\n")
list(csv.reader(f, "excel"))
[['a', 'b'], ['1', '2']]
f2 = io.StringIO()
csv.DictWriter(f2, fieldnames=["x", "y"]).writeheader()
f2.getvalue()
'x;y\r\n'
Measured: csv.reader(f, "excel") and DictWriter/DictReader with no dialect argument both resolve the string "excel" through the registry at call time, so they pick up the overwritten delimiter immediately. DictReader/DictWriter default their dialect parameter to the literal string "excel", which is why they're affected even though you never named a dialect.
3. Plain csv.reader()/csv.writer() with no dialect argument are unaffected
f3 = io.StringIO("a;b\n1;2\n")
r = csv.reader(f3)
r.dialect.delimiter
','
list(r)
[['a;b'], ['1;2']]
f4 = io.StringIO()
csv.writer(f4).writerow(["x", "y"])
f4.getvalue()
'x,y\r\n'
Measured: this is the surprising part. csv.reader(f) and csv.writer(f) called with no dialect argument at all still use a comma, completely ignoring the overwritten "excel" registration. Their no-argument default isn't a lookup of the string "excel" in the registry — it's a separate hardcoded default baked into the C implementation. So the exact same overwrite silently changes the behavior of DictReader/DictWriter and anything that writes dialect="excel" explicitly, while leaving plain no-argument reader()/writer() calls completely untouched — in the same process, with the same registry.
4. What to actually do
Never register a custom dialect under "excel", "excel-tab", or "unix" — pick a name nobody else could plausibly be using, like a module-prefixed string ("myapp-pipe-delim"). If you're debugging inconsistent CSV parsing across one codebase and suspect this, check csv.list_dialects() and csv.get_dialect("excel").delimiter early — a built-in dialect that doesn't match its own default is a sign some import-time code registered over it. Because the registry is global and process-wide, a single third-party library doing this at import time can change DictReader behavior anywhere else in the same process without ever touching your code directly.
Frequently asked questions
Can csv.register_dialect() overwrite the built-in 'excel' dialect?
Yes — we measured it directly. csv.register_dialect("excel", delimiter=";") silently replaces the built-in dialect with no error or warning, for the rest of the process.
If 'excel' gets overwritten, does plain csv.reader(f) with no dialect argument change too?
No — we measured that csv.reader(f) and csv.writer(f) called with no dialect argument keep using a comma regardless, because their no-argument default is a separate hardcoded value, not a lookup of the string "excel".
Does DictReader get affected by overwriting the 'excel' dialect?
Yes — we measured DictReader() and DictWriter() with no dialect argument picking up the overwritten delimiter immediately, because they default their dialect parameter to the literal string "excel", which is resolved through the registry.
Is csv.register_dialect()'s global registry shared across a whole program?
Yes — it's one module-level dict for the entire process, the same pattern as csv.field_size_limit(). Any code that registers over a built-in name affects every other piece of code in that process using that name, including third-party libraries.
How do I avoid this problem in my own code?
We'd never register a custom dialect under a built-in name like "excel", "excel-tab", or "unix" — use a unique, clearly custom name instead, and check csv.list_dialects() / csv.get_dialect("excel").delimiter if parsing behaves inconsistently across a codebase.
Written with AI assistance. Measured on 2026-10-03 with Python 3.13.5's built-in csv module: called csv.register_dialect('excel', delimiter=';') to overwrite the built-in 'excel' dialect, then compared csv.reader()/csv.writer() with no dialect argument against csv.reader(f, 'excel')/DictReader()/DictWriter() in the same process, recording which ones picked up the change. PlainHeaders is our extension; Python's csv module is built in.