Home› Tutorials› Standard Library
Standard LibraryPython's configparser Module: Reading and Writing INI Config Files
- 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.