Python configparser: Read INI Files

Published on: August 16, 2026
Reading time: 5 minutes
File and system diagram representing INI configuration with Python configparser

Python configparser reads and writes INI-style configuration files organized into sections, options, and string values. It is a practical fit for desktop programs, command-line tools, administrative scripts, and applications that need human-editable settings without requiring JSON, YAML, or a database.

INI is not governed by one universal specification. Applications differ in their rules for comments, capitalization, delimiters, empty options, multiline values, and interpolation. Configure the parser deliberately and document the exact dialect your users may write.

Create a basic INI file

[server]
host = 127.0.0.1
port = 8080
debug = false

[logging]
level = INFO
file = logs/app.log

Each bracketed header defines a section. Options use = or : as delimiters by default.

Read configuration safely

import configparser

config = configparser.ConfigParser()
loaded = config.read("app.ini", encoding="utf-8")

if not loaded:
    raise FileNotFoundError("app.ini was not loaded")

host = config["server"]["host"]
port = config["server"].getint("port")
debug = config["server"].getboolean("debug")

read() silently ignores files that cannot be opened and returns the successfully parsed filenames. Check the return value for required files or use read_file() with an already opened handle.

All values are stored as strings

The parser does not infer data types. Use getint(), getfloat(), and getboolean().

timeout = config["server"].getfloat("timeout", fallback=5.0)
workers = config["server"].getint("workers", fallback=4)
enabled = config["server"].getboolean("enabled", fallback=True)

getboolean() recognizes values such as yes/no, true/false, on/off, and 1/0. Do not call bool("false"), because every nonempty string is true.

Use fallback values correctly

On a section proxy, the second argument to get() is the fallback:

level = config["logging"].get("level", "INFO")

On the parser-level API, use the keyword-only argument:

level = config.get("logging", "level", fallback="INFO")

Values inherited from DEFAULT take precedence over a fallback, which can surprise code that checks only the selected section.

Define shared defaults

[DEFAULT]
timeout = 10
retries = 3

[api]
url = https://api.example.com

[worker]
retries = 5

Default options are visible through every section. Removing a section-specific override reveals the default again because it was never physically stored in that section.

Layer several files

Files read later override conflicting options while preserving everything else.

config.read(
    ["defaults.ini", "site.ini", "user.ini"],
    encoding="utf-8",
)

A common pattern uses versioned defaults, host-level settings, and user overrides. Validate mandatory values after all layers are loaded.

Use read_file for mandatory input

from pathlib import Path

path = Path("defaults.ini")
with path.open(encoding="utf-8") as handle:
    config.read_file(handle, source=str(path))

Open and parsing errors propagate, and the source label improves diagnostics.

Read strings and dictionaries

read_string() is convenient for tests and controlled embedded configuration.

config.read_string("""
[service]
url = https://example.com
""", source="embedded defaults")

read_dict() accepts nested mappings and converts keys and values to strings.

config.read_dict({
    "server": {"port": 8080, "debug": False},
})

Understand option-name case

Section names are case-sensitive by default, but option names are not. optionxform() normally converts keys to lowercase.

config = configparser.ConfigParser()
config.read_string("""
[Section]
MyKey = value
""")
print(list(config["Section"]))
# ['mykey']

To preserve original capitalization:

config.optionxform = str

Set it before reading data. A custom canonicalization function should be idempotent.

Basic interpolation

ConfigParser enables BasicInterpolation by default.

[paths]
base = /opt/myapp
logs = %(base)s/logs
cache = %(base)s/cache

Write %% for a literal percent sign. Interpolation occurs when a value is retrieved, not during parsing.

Extended interpolation

ExtendedInterpolation supports ${section:option} and references between sections.

from configparser import ConfigParser, ExtendedInterpolation

config = ConfigParser(interpolation=ExtendedInterpolation())
[common]
root = /srv/app

[logging]
dir = ${common:root}/logs

Use $$ for a literal dollar sign. Missing options and reference loops raise interpolation exceptions.

Disable interpolation when appropriate

Passwords, templates, regular expressions, and shell fragments may legitimately contain % or $. If substitution is not part of the format:

config = configparser.ConfigParser(interpolation=None)

For one lookup, use raw=True.

Add custom converters

The converters argument creates new get* methods.

from pathlib import Path

config = configparser.ConfigParser(
    converters={
        "path": Path,
        "list": lambda value: [item.strip() for item in value.split(",")],
    },
)

log_path = config["logging"].getpath("file")
origins = config["cors"].getlist("origins")

Conversion is not complete validation. Check ranges, extensions, filesystem roots, existence, and business constraints afterward.

Customize Boolean values

config.BOOLEAN_STATES = {
    "enabled": True,
    "disabled": False,
}

A custom vocabulary may improve readability but reduces portability. Document accepted spellings.

Allow options without values

Some existing INI dialects use bare flags.

config = configparser.ConfigParser(allow_no_value=True)
[features]
safe_mode
audit

These options produce None. Recent Python versions raise MultilineContinuationError if a valueless option is followed by an indented continuation line.

Unnamed sections

Python 3.13 added optional support for initial options before the first section header.

config = configparser.ConfigParser(allow_unnamed_section=True)
config.read_string("""
key = value

[other]
x = 1
""")
value = config[configparser.UNNAMED_SECTION]["key"]

Enable this only for compatibility with a format that already requires it.

Comments and multiline values

By default, # and ; introduce comments on otherwise empty lines. Inline comments are disabled because comment-prefix characters cannot be escaped reliably.

Multiline values must be indented deeper than their key:

[message]
text = first line
    second line
    third line

For human-maintained files, consider empty_lines_in_values=False to reduce visual ambiguity.

Keep strict mode enabled

strict=True, the modern default, rejects duplicate sections and options within one source. This catches spelling mistakes and case-normalization collisions. Keep it enabled for new applications.

Handle parsing errors clearly

try:
    with open("app.ini", encoding="utf-8") as handle:
        config.read_file(handle)
except configparser.MissingSectionHeaderError as exc:
    raise RuntimeError("Missing section header") from exc
except configparser.DuplicateOptionError as exc:
    raise RuntimeError("Duplicate option") from exc
except configparser.InterpolationError as exc:
    raise RuntimeError("Invalid interpolation") from exc

Include source and line information in diagnostics, but never print secret values.

Write configuration

from pathlib import Path

config["server"]["port"] = "9090"
with Path("app.ini").open("w", encoding="utf-8") as handle:
    config.write(handle)

All values must be strings. In Python 3.14, write() raises InvalidWriteError when the generated representation could not be read back accurately.

Write atomically

Do not overwrite an important file directly. Write to a temporary file in the same directory, flush and synchronize it, then replace the destination.

import os
from pathlib import Path
from tempfile import NamedTemporaryFile

destination = Path("app.ini")
with NamedTemporaryFile("w", encoding="utf-8", dir=destination.parent, delete=False) as temp:
    config.write(temp)
    temp.flush()
    os.fsync(temp.fileno())
    temporary = temp.name
os.replace(temporary, destination)

Control file permissions and use locking or a central service when several writers may update configuration.

Comments are not preserved

Reading and writing a file again loses original comments and formatting. If users maintain important explanations in the file, avoid automatic rewrites or choose a parser that preserves layout.

Do not store secrets in INI

INI files are plain text. Passwords, tokens, and private keys should come from a secret manager, protected environment variable, or operating-system credential store. Store only a secret identifier when configuration needs a reference.

The Python site guide explains installation paths and environments, Python importlib.resources can distribute packaged defaults, and Python fileinput handles multi-file processing.

Validate after parsing

def load_server(config):
    port = config["server"].getint("port")
    if not 1 <= port <= 65535:
        raise ValueError("port is outside the valid range")

    host = config["server"].get("host", "127.0.0.1").strip()
    if not host:
        raise ValueError("host is empty")

    return host, port

The parser validates structure and basic conversions; the application must enforce semantics.

ConfigParser, TOML, and JSON

INI is approachable for small settings. TOML has a stronger specification and native data types. JSON is widely interoperable but does not support comments. Choose based on complexity, ecosystem, and human-editing requirements.

  • Open files explicitly as UTF-8.
  • Keep strict=True.
  • Verify required files.
  • Define case-normalization behavior.
  • Disable interpolation when unused.
  • Validate every converted value.
  • Write updates atomically.
  • Keep secrets outside the INI file.

Conclusion

Python configparser provides a mature interface for INI configuration with sections, defaults, layered files, interpolation, converters, and mapping-style access. It works best when the accepted dialect remains simple and documented.

Consult the official configparser documentation and the Python tomllib documentation.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    RAM module representing object memory management with Python gc
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python gc: Control Garbage Collection

    Learn Python gc to control cyclic collection, inspect tracked objects, diagnose memory growth, and observe collection pauses.

    Ler mais

    Tempo de leitura: 6 minutos
    16/08/2026
    Lines of source code representing execution tracking with Python trace
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python trace: Track Execution

    Learn Python trace to count executed lines, follow runtime flow, list functions, combine coverage, and filter modules.

    Ler mais

    Tempo de leitura: 5 minutos
    16/08/2026
    Laptop with performance charts representing profile analysis with Python pstats
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python pstats: Analyze Profiles

    Learn Python pstats to sort, filter, merge, and interpret cProfile data, including callers, callees, internal time, and cumulative time.

    Ler mais

    Tempo de leitura: 5 minutos
    15/08/2026
    Laptop with code representing executable examples tested with Python doctest
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python doctest: Test Examples

    Learn Python doctest to execute examples in docstrings and text files, normalize output, and integrate executable documentation with CI.

    Ler mais

    Tempo de leitura: 5 minutos
    15/08/2026
    Source code on screen representing class and function browsing with Python pyclbr
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python pyclbr: Inspect Modules Safely

    Learn Python pyclbr to list classes, functions, methods, and nested definitions without importing or executing the target module.

    Ler mais

    Tempo de leitura: 5 minutos
    15/08/2026
    Monitor with binary code representing Python bytecode opcode instructions
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python opcode: Explore Bytecode

    Learn Python opcode to map bytecode instructions, arguments, jumps, caches, and stack effects through the documented dis APIs.

    Ler mais

    Tempo de leitura: 5 minutos
    15/08/2026