skip to content

In configparser, what does the DEFAULT section do and how does interpolation expand a value?

level: middleimportance: should knowfreq 40%

answer

  1. one section every other section inherits
  2. sections() quietly leaves it out
  3. substitution happens on read, not on parse
  4. percent-name-s, and doubling a literal percent
  5. ExtendedInterpolation reaches across sections

basics

~20 s

configparser's DEFAULT section holds options every other section inherits, and it never shows up in sections(). BasicInterpolation then expands %(name)s inside a value on read, resolving the name in that section first and DEFAULT second.

solid answer

~40 s

`configparser.ConfigParser` treats the section literally named `DEFAULT` (the value of `configparser.DEFAULTSECT`) as a fallback layer: any option missing from a section is served from it, `cfg.defaults()` shows it, and `cfg.sections()` deliberately omits it. On top of that, `ConfigParser` interpolates by default with `BasicInterpolation`: when you read a value, `%(name)s` is replaced by the value of `name` looked up in the same section and then in `DEFAULT`, recursively, so a literal percent sign must be written `%%`. A missing name raises `InterpolationMissingOptionError`; malformed syntax raises `InterpolationSyntaxError`. Pass `interpolation=configparser.ExtendedInterpolation()` for `${section:option}` cross-section references, `interpolation=None` to switch it off, or `raw=True` to a single `get()` to read the stored text unchanged.

code

python · 13 lines
python
import configparser

cfg = configparser.ConfigParser()
cfg.read_string(
    "[DEFAULT]\nroot = /srv/media\nretries = 3\n\n"
    "[extract]\noutput = %(root)s/out\n"
)

print(cfg.sections())                       # ['extract'] - DEFAULT is hidden
print(cfg["extract"]["output"])             # /srv/media/out
print(cfg["extract"]["retries"])            # '3' inherited from DEFAULT
print(cfg.get("extract", "output", raw=True))
print(dict(cfg.defaults()))

go deeper

for a junior

Recall that a section literally called DEFAULT supplies fallback options to every other section, and that values you read may have had %(name)s placeholders expanded. Knowing the two features exist is enough here.

for a middle

Explain the mechanics: defaults are a lookup layer, interpolation is substitution performed on read and recursively, %% escapes a literal percent, and ExtendedInterpolation adds ${section:option}. Name the errors that a missing or malformed reference raises.

for a senior

Judge when to use them at all. Argue about DEFAULT leaking into every section, using raw=True before rewriting a file, and choosing interpolation=None when operators paste values containing percent signs into config.

for a principal

Own the format decision across services: where INI with interpolation still earns its place against typed TOML, how much templating belongs in a config file versus in code, and how you keep operator-editable configuration reviewable.

## Two mechanisms that people conflate `configparser` parses INI-style files, and it has two separate features that both look like "one value coming from somewhere else". Keeping them apart is most of the interview answer. **Defaults** are a *lookup* layer. **Interpolation** is a *substitution* layer inside a single value. They compose, but they are independent: `RawConfigParser` still has defaults and does no interpolation at all. ## The DEFAULT section The section whose name is exactly `DEFAULT` — available as the constant `configparser.DEFAULTSECT` — is special. Every option in it is visible from every other section: ```ini [DEFAULT] root = /srv/media retries = 3 [extract] output = %(root)s/out ``` Here `cfg["extract"]["retries"]` returns `"3"` even though `[extract]` never mentions it. The consequences worth stating out loud: * `cfg.sections()` returns `['extract']`. `DEFAULT` is *not* a section by that method's definition, which trips people writing "loop over every section" code. * Iterating a section proxy, or asking `"retries" in cfg["extract"]`, *does* include the defaults. So `sections()` hides `DEFAULT` while membership tests reveal it. * `cfg.defaults()` returns the defaults mapping directly. * You can also inject defaults programmatically with `ConfigParser(defaults=...)`, which loads them into the same `DEFAULT` layer. * Lookup precedence in `get()` is: the `vars` argument you pass in, then the section, then `DEFAULT`, then `fallback=`. The design cost is that `DEFAULT` leaks everywhere. Once a key is in it, no section can be *without* that key, and "which section actually set this?" stops having a simple answer. For anything but genuinely global fallbacks, an explicit key per section is easier to operate. ## Interpolation `ConfigParser` uses `BasicInterpolation` unless you say otherwise. Substitution happens **on read**, not at parse time: the file stores `%(root)s/out` and `get()` expands it. The name is resolved in the current section, then in `DEFAULT`. Expansion is recursive — a substituted value containing another reference is expanded too — up to a depth limit, beyond which you get `InterpolationDepthError`. Because `%` introduces a reference, a literal percent must be doubled: `discount = 15%%` reads back as `15%`. A stray single `%` is rejected when the value is set, so this fails loudly rather than silently. A reference to a name that does not exist raises `InterpolationMissingOptionError` at read time, which is a genuinely useful property: a typo in a template surfaces the moment the value is used. `ExtendedInterpolation` swaps the syntax and adds cross-section reach: `${option}` for the current section and `${section:option}` for another one, with `$$` as the literal dollar. Choose it when config naturally has a `[paths]` section other sections build on — `BasicInterpolation` simply cannot reference another section. Two escape hatches: construct the parser with `interpolation=None` to store and return values verbatim, or pass `raw=True` to a single `get()` / `items()` call when you want the unexpanded text — for example when you are about to write the file back out. ## Everything is still a string Interpolation and defaults never change the type: a value read from an INI file is `str`, always. Numbers and booleans come back as text, which is why the module ships `getint`, `getfloat` and `getboolean`. That is the other half of using this module correctly. ## Writing, and version history Unlike a TOML parser, `configparser` can write: `cfg.write(fp)` emits the raw, un-interpolated values, so an INI file round-trips (comments are lost). That makes it the standard library's only editable configuration format. Version notes worth knowing: the "magic interpolation" behaviour was made explicit and pluggable in 3.2; `SafeConfigParser` and `LegacyInterpolation` were **removed in 3.12**, so the modern `ConfigParser` with `BasicInterpolation` *is* what the old "safe" parser did; and 3.13 added the ability to accept an unnamed leading section via the `allow_unnamed_section` argument, for files whose first options appear before any header.

  • How do you read the stored text without any substitution?
    Either construct the parser with `interpolation=None`, which disables it globally, or pass `raw=True` to a single `get()` or `items()` call. The second is what you want before writing a file back out, so templates survive the round trip instead of being flattened.
  • A value must contain a literal percent sign. What do you write in the file?
    Double it: `%%`. With `BasicInterpolation` a lone `%` not followed by `(` or `%` is rejected when the value is set, so the mistake fails loudly rather than producing a corrupt value. Under `ExtendedInterpolation` the equivalent escape is `$$`.
  • Why does sections() omit DEFAULT while 'retries' in cfg['extract'] returns True?
    `DEFAULT` is a fallback layer rather than a section, so `sections()` excludes it by definition. But every section proxy resolves through that layer, so membership tests and iteration over a section include the inherited keys. Code that walks `sections()` and assumes it saw every key is wrong.

saying these in an interview costs you the question

  • Thinks DEFAULT is returned by sections()
  • Expects %(root)s to come back literally from get()
  • Writes a single % in a value and expects it to survive
  • Claims BasicInterpolation can reference another section
  • Believes interpolation happens while the file is parsed
  • Assumes a section proxy hides inherited DEFAULT keys

context