configparser remove_option() Silent No-op on DEFAULT Fallback (tested)

[DEFAULT] values in configparser are inherited by every section that doesn't set its own override. We measured what happens when you try to remove one of those inherited values from a single section — and the answer is: nothing happens, and nothing tells you that.

1. has_option() and 'in' say the key is there

import configparser

c = configparser.ConfigParser()
c.read_string("""
[DEFAULT]
color = blue

[section1]
other = 1
""")
print(c.has_option("section1", "color"))
print("color" in c["section1"])
print(c.get("section1", "color"))
True
True
blue

Measured: section1 never set color itself — it's purely inherited from [DEFAULT]. But has_option(), the in operator, and get() all agree the key is present and has a value. By every readable signal, color "exists" in section1.

2. remove_option() and del disagree with has_option() about that same key

print(c.remove_option("section1", "color"))
print(c.get("section1", "color"))

try:
    del c["section1"]["color"]
except KeyError as e:
    print("KeyError:", e)
False
blue
KeyError: 'color'

Measured: remove_option() returns False — its documented meaning is "there was nothing to remove" — and the value is still blue afterward. No exception, no warning. del c["section1"]["color"], the more natural-looking removal syntax, raises KeyError: 'color' outright, even though "color" in c["section1"] just returned True one line earlier. Both operations only ever touch a section's own local options; neither one looks at or clears the inherited [DEFAULT] value, and neither one says so.

3. Fix: there's no "unset the inherited value" operation — set an explicit override instead

c.set("section1", "color", "red")
print(c.get("section1", "color"))

c2 = configparser.ConfigParser()
c2.read_string("""
[DEFAULT]
color = blue

[section1]
other = 1
[section2]
other = 2
""")
c2.remove_option("DEFAULT", "color")
print(c2.get("section1", "color", fallback="(none)"))
print(c2.get("section2", "color", fallback="(none)"))
red
(none)
(none)

Measured: configparser has no concept of hiding or deleting an inherited value for just one section — the two real options are to give that section its own explicit override (set(), confirmed it shadows [DEFAULT] correctly), which fixes one section, or to remove_option("DEFAULT", ...), which confirmed removing it for every section that was inheriting it, not just one. If you only want to silence it in one place, writing an explicit per-section override is the only tool that does that.

Frequently asked questions

Why does configparser's remove_option() return False and do nothing?

We measured remove_option() returning False when the key being removed only existed via [DEFAULT] fallback, not as a value explicitly set on that section. It only ever removes a section's own local options, never a [DEFAULT] value a section is inheriting.

Why does 'key' in section return True if I can't delete key from that section?

We confirmed 'in' and has_option() check the merged view, including [DEFAULT] fallback, so they return True for inherited keys. del section[key] and remove_option() only act on the section's own local dict, so they fail or no-op on the exact same key.

How do I remove a config value that's only set in [DEFAULT]?

We verified remove_option('DEFAULT', key) removes it everywhere it was being inherited, not just one section. There is no built-in way to hide a [DEFAULT] value from a single section.

How do I override a DEFAULT value for just one section in configparser?

We confirmed calling set(section, key, value) (or writing the key directly under that section in the source) creates a section-local value that shadows [DEFAULT] for that section only, without affecting other sections.

Does del config[section][key] raise an error in configparser?

Yes — we measured KeyError on del when key is only present via [DEFAULT] inheritance, even though the same key reads as present through has_option(), 'in', and get() on that same section.

Written with AI assistance. Measured on 2026-10-04 with Python 3.13.5's built-in configparser module: created a ConfigParser with a [DEFAULT] value and a section that never set its own override, then compared 'key' in section, get(), remove_option(), and del section[key] on that same key. PlainHeaders is our extension; Python's configparser module is built in.