Home Tutorials Standard Library

Standard Library

Python's configparser Module: Reading and Writing INI Config Files

Pyford Notes July 6, 2026 7 min read
Key points
  • ConfigParser reads section-based .ini files into an object that behaves like a nested dict of dicts, keyed by section then option name.
  • Every value is read back as a plain string; use getint(), getfloat(), and getboolean() for automatic type conversion instead of casting manually.
  • A [DEFAULT] section provides fallback values inherited by every other section that doesn't override them explicitly.
  • For structured config with nested lists or non-string data, JSON, TOML, or YAML fit better -- INI suits simple, flat, human-edited settings files.

The INI format ConfigParser expects

INI files organise settings into named [section] blocks, each holding key = value pairs. It's a format most at home with human-edited configuration — application settings, deployment parameters, tool configuration — rather than deeply nested or programmatically generated data:

# app.ini
[server]
host = 0.0.0.0
port = 8080
debug = false

[database]
host = localhost
port = 5432
name = myapp_db

[logging]
level = INFO

Reading a config file and accessing values

configparser.ConfigParser parses a file like this into an object that behaves like a nested mapping, section name first, then option name:

import configparser

config = configparser.ConfigParser()
config.read("app.ini")

print(config.sections())               # ['server', 'database', 'logging']
print(config["server"]["host"])          # "0.0.0.0"
print(config["database"]["port"])         # "5432"  -- note: still a string, see below

for key, value in config["logging"].items():
    print(key, "=", value)

config.read() silently does nothing if the file doesn't exist rather than raising an error, which is convenient for optional config layered over defaults, but can hide a typo'd file path; check config.read()'s return value (a list of files it actually opened) if you need to be sure a specific file was found.

Type conversion: everything starts as a string

Every value in an INI file is text, so config["server"]["port"] is the string "8080", not the integer 8080. ConfigParser provides typed getters that convert automatically instead of requiring a manual int() call everywhere:

import configparser

config = configparser.ConfigParser()
config.read("app.ini")

port = config.getint("server", "port")           # 8080, as an int
debug = config.getboolean("server", "debug")       # False, from the string "false"
timeout = config.getfloat("server", "timeout", fallback=30.0)   # 30.0 if the key is absent

getboolean() accepts a generous set of common truthy/falsy spellings — "yes"/"no", "true"/"false", "on"/"off", "1"/"0", case-insensitively — which matches how people actually write config files by hand, rather than requiring an exact "True"/"False" spelling.

DEFAULT section and per-call fallbacks

A special [DEFAULT] section supplies values every other section inherits automatically unless it overrides them, which is useful for settings shared across most sections with only a few exceptions:

# app.ini
[DEFAULT]
timeout = 30
retries = 3

[service_a]
timeout = 60      # overrides the default for this section only

[service_b]
# inherits timeout=30 and retries=3 from DEFAULT, defines nothing itself
import configparser

config = configparser.ConfigParser()
config.read("app.ini")

print(config.getint("service_a", "timeout"))   # 60 -- explicit override
print(config.getint("service_b", "timeout"))   # 30 -- inherited from DEFAULT
print(config.getint("service_b", "retries"))    # 3  -- inherited from DEFAULT

# Fallback for a key that might not exist anywhere at all
print(config.getint("service_b", "max_connections", fallback=10))

The fallback= keyword argument, accepted by every typed getter, avoids a KeyError/NoOptionError for genuinely optional settings without needing a surrounding try/except at every call site.

Writing a config file back out

ConfigParser can also build and write configuration programmatically, which is handy for a settings UI or first-run setup wizard that persists choices to disk:

import configparser

config = configparser.ConfigParser()
config["server"] = {"host": "0.0.0.0", "port": "8080"}
config["database"] = {"host": "localhost", "port": "5432", "name": "myapp_db"}

with open("generated.ini", "w") as f:
    config.write(f)

Note that values assigned this way must be strings — config["server"]["port"] = 8080 (an int) raises TypeError, since ConfigParser stores everything as text internally and only converts on the way out through the typed getters.

A less commonly needed but occasionally useful feature is interpolation: referencing one option's value from inside another, so shared fragments don't have to be repeated across the file. The default BasicInterpolation supports %(name)s references within the same section, and ExtendedInterpolation extends that to reference other sections with a ${section:option} syntax:

import configparser

config = configparser.ConfigParser(interpolation=configparser.ExtendedInterpolation())
config.read_string("""
[paths]
base_dir = /var/lib/myapp

[logging]
log_file = ${paths:base_dir}/logs/app.log
""")

print(config["logging"]["log_file"])   # /var/lib/myapp/logs/app.log -- resolved automatically

This is worth reaching for when several settings share a common prefix or root value, so changing that one value updates everything that depends on it, rather than requiring a find-and-replace across the file. For configuration that needs nested structures, lists, or genuinely typed values throughout, json, tomllib (standard library since 3.11 for reading), or a third-party YAML library are usually a better fit than stretching INI's flat, string-only model. Full API details are in the configparser documentation.