skip to content

Why must you call configparser's getboolean() instead of bool() on a config value?

level: middleimportance: should knowfreq 45%

answer

  1. INI files carry no types at all
  2. which strings are falsy in Python
  3. one vocabulary, not truthiness
  4. yes/no/on/off/true/false/1/0
  5. an unknown word must raise ValueError

basics

~20 s

Every value configparser returns is a str, and bool() of any non-empty string is True — so bool('false') is True. getboolean() maps a fixed vocabulary (1/yes/true/on and 0/no/false/off, case-insensitively) to a real bool and raises ValueError on anything else.

solid answer

~40 s

INI files have no types: `cfg["extract"]["fast"]` is the string `"false"`, and `bool` on a non-empty string is `True`, so the naive conversion silently inverts the operator's intent — one of the most common configuration bugs in Python. `configparser` therefore ships typed accessors: `getboolean` recognises `"1"`, `"yes"`, `"true"`, `"on"` as true and `"0"`, `"no"`, `"false"`, `"off"` as false, case-insensitively, and raises `ValueError` for anything else; `getint` and `getfloat` just apply `int()` and `float()` and raise `ValueError` on junk. All three exist both on the parser (`cfg.getboolean(section, option)`) and on a section proxy (`cfg["extract"].getboolean("fast")`), and all take `fallback=` for a missing option. The accepted true/false words live in the parser's `BOOLEAN_STATES` mapping, and the `converters=` argument adds your own typed getters.

code

python · 15 lines
python
import configparser

cfg = configparser.ConfigParser()
cfg.read_string("[extract]\nfast = no\nworkers = 4\n")

print(bool(cfg["extract"]["fast"]))            # True - non-empty string
print(cfg["extract"].getboolean("fast"))       # False
print(cfg.getint("extract", "workers") + 1)    # 5
print(cfg.getboolean("extract", "probe", fallback=False))

cfg.read_string("[extract]\nfast = maybe\n")
try:
    cfg["extract"].getboolean("fast")
except ValueError as exc:
    print("ValueError:", exc)

go deeper

for a junior

Remember that configparser hands back strings and that every non-empty string is truthy, so bool('false') is True. Reach for getboolean, getint and getfloat instead of built-in constructors.

for a middle

Explain the mechanism: getboolean normalises the text and looks it up in a fixed true/false vocabulary, raising ValueError otherwise, while getint and getfloat delegate to the constructors. Know the parser and section-proxy call shapes and fallback=.

for a senior

Show the production judgement: a bad config value should stop startup, not fall back silently; the same trap governs environment overrides; and converters= centralises list or duration parsing rather than scattering split calls.

for a principal

Own the policy. Decide where typed configuration formats remove this class of bug entirely, what your services do on an unparsable value, and how a boolean vocabulary stays consistent across files, environment variables and command-line flags.

## The bug this method exists to prevent An INI file is untyped text. When you read `fast = false` with `configparser`, you get back the four-character string `"false"`. Passing it to `bool()` asks a completely different question — *is this string non-empty?* — and every non-empty string is truthy. So: ```python bool("false") # True bool("0") # True bool("no") # True bool("") # False (the only false string) ``` The operator wrote `false`, the program read `True`, and nothing raised. That silence is what makes it dangerous: the config knob appears to be wired up, the tests that only ever set it to `true` pass, and the wrong branch runs in production. It is the same trap as `bool(os.environ.get("DEBUG", ""))`, because environment variable values are also always strings. ## What getboolean actually does `getboolean` does not use truthiness. It normalises the string (strip and lower-case it) and looks it up in a small mapping of accepted words, exposed as `configparser.RawConfigParser.BOOLEAN_STATES`: * true: `"1"`, `"yes"`, `"true"`, `"on"` * false: `"0"`, `"no"`, `"false"`, `"off"` Anything else — `"none"`, `"disabled"`, `"y"`, `"True "` with a stray character, a typo — raises `ValueError`. That is the second half of the value: an unrecognised word is a *loud* failure at read time rather than a silently-true flag. Because `BOOLEAN_STATES` is an ordinary class attribute, a parser subclass can extend the vocabulary if a legacy file uses `enabled`/`disabled`, though widening it is usually worse than fixing the file. `getint` and `getfloat` are thinner: they call `int()` and `float()` on the string and let those raise `ValueError` on bad input. `getint` does not accept `"4.0"` and does not silently truncate; `getfloat("1_000.5")` follows the ordinary numeric-literal rules of the constructors. ## The two call shapes, and fallback All the typed accessors exist twice: ```python cfg.getboolean("extract", "fast") # parser-level: section and option cfg["extract"].getboolean("fast") # section proxy: option only ``` Both accept `fallback=`, which is what you want for an option that may be absent: `cfg.getboolean("extract", "fast", fallback=False)` returns the fallback instead of raising `NoOptionError`. They also accept `raw=` and `vars=`, the same interpolation controls as `get`. Note that `fallback` covers a *missing* option, not an *invalid* one — a present-but-unparsable value still raises `ValueError`, and that is correct: a typo in config should stop the process, not be quietly replaced by a default. ## Extending the idea with converters `ConfigParser(converters={"list": lambda text: [part.strip() for part in text.split(",") if part.strip()]})` synthesises `cfg.getlist(section, option)` and `cfg["extract"].getlist(option)` from the name you supplied. That is the module's answer to "my config has a comma-separated list": one place that owns the parsing and raises on bad input, instead of a `split` scattered through the code. ## The contrast that makes this leaf make sense A TOML parser returns *typed* values — `true` is already a `bool`, `4` is already an `int`, so there is no coercion step and no place for this bug. INI has no types, so `configparser` reintroduces them at the accessor. Environment variables are in the same category as INI: always strings, always needing an explicit conversion with an explicit boolean vocabulary. Whenever configuration crosses a text boundary, the conversion has to be deliberate; `getboolean` is what deliberate looks like. A last subtlety worth carrying: `bool` is a subclass of `int` in Python, so any generic "coerce this string to the type of the existing value" helper must test `isinstance(value, bool)` *before* it tests `isinstance(value, int)`. Get that order wrong and `"0"` becomes `True` again by a different route.

  • What does getboolean do with a value it does not recognise, such as 'maybe'?
    It raises `ValueError`. The accepted vocabulary is fixed — 1/yes/true/on and 0/no/false/off, case-insensitive — and anything outside it is an error rather than a guess. `fallback=` does not rescue it either: fallback covers a missing option, not an unparsable one.
  • Does the same trap apply to environment variables?
    Exactly the same. `os.environ` values are always `str`, so `bool(os.environ.get("DEBUG"))` is true for `"false"` and `"0"` alike, and false only when the variable is missing or empty. Every boolean environment override needs an explicit word-to-bool mapping of its own.
  • How would you add a typed accessor for a comma-separated list option?
    Pass `converters={"list": ...}` when constructing the parser; configparser then synthesises `getlist` on both the parser and every section proxy. It keeps the parsing and its error handling in one place instead of a `split` repeated at each call site.

saying these in an interview costs you the question

  • Thinks bool('false') evaluates to False
  • Believes configparser returns ints for numeric-looking values
  • Expects getboolean to accept any word meaning yes
  • Uses fallback= to paper over an unparsable value
  • Calls int() on a value without handling ValueError
  • Checks isinstance int before isinstance bool when coercing

context