Introduction
Python's built-in configparser module reads and writes INI files without any external dependencies. To read: create a ConfigParser instance, call config.read('file.ini'), then access values with config['section']['key']. To write: set values on the ConfigParser object and call config.write(open('file.ini', 'w')). The module handles sections, key-value pairs, defaults, interpolation, and type conversion out of the box.
INI File Structure
1; config.ini
2[DEFAULT]
3debug = false
4log_level = INFO
5
6[database]
7host = localhost
8port = 5432
9name = myapp
10user = admin
11password = secret
12
13[server]
14host = 0.0.0.0
15port = 8080
16workers = 4
Sections are enclosed in [], keys use = or : as delimiters, and comments start with ; or #.
Reading an INI File
1import configparser
2
3config = configparser.ConfigParser()
4config.read('config.ini')
5
6# List all sections
7print(config.sections()) # ['database', 'server']
8
9# Access values
10db_host = config['database']['host']
11db_port = config['database']['port']
12print(f"Database: {db_host}:{db_port}") # Database: localhost:5432
13
14# Check if a section or key exists
15print(config.has_section('database')) # True
16print(config.has_option('database', 'host')) # True
17
18# Iterate over a section
19for key, value in config['database'].items():
20 print(f" {key} = {value}")
21# Includes DEFAULT values: debug, log_level, host, port, name, user, password
Type Conversion
All values are stored as strings. Use typed getters for conversion:
1config = configparser.ConfigParser()
2config.read('config.ini')
3
4# String (default)
5host = config.get('database', 'host') # 'localhost'
6
7# Integer
8port = config.getint('database', 'port') # 5432
9
10# Float
11timeout = config.getfloat('server', 'timeout', fallback=30.0) # 30.0
12
13# Boolean — recognizes yes/no, on/off, true/false, 1/0
14debug = config.getboolean('DEFAULT', 'debug') # False
15
16# With fallback for missing keys
17max_conn = config.getint('database', 'max_connections', fallback=10)
Writing an INI File
1import configparser
2
3config = configparser.ConfigParser()
4
5# Set values
6config['DEFAULT'] = {'debug': 'false', 'log_level': 'INFO'}
7
8config['database'] = {
9 'host': 'localhost',
10 'port': '5432',
11 'name': 'myapp',
12 'user': 'admin',
13 'password': 'secret'
14}
15
16config['server'] = {
17 'host': '0.0.0.0',
18 'port': '8080',
19 'workers': '4'
20}
21
22# Write to file
23with open('config.ini', 'w') as f:
24 config.write(f)
Modifying an Existing File
1config = configparser.ConfigParser()
2config.read('config.ini')
3
4# Update a value
5config['database']['port'] = '3306'
6
7# Add a new section
8config['redis'] = {
9 'host': 'localhost',
10 'port': '6379',
11 'db': '0'
12}
13
14# Remove a key
15config.remove_option('database', 'password')
16
17# Remove a section
18config.remove_section('server')
19
20# Write changes back
21with open('config.ini', 'w') as f:
22 config.write(f)
Interpolation (Variable Substitution)
configparser supports referencing other values within the same section or DEFAULT:
1# config_with_interpolation.ini
2# [paths]
3# home = /home/user
4# data = %(home)s/data
5# logs = %(home)s/logs
6
7config = configparser.ConfigParser()
8config.read('config_with_interpolation.ini')
9
10print(config['paths']['data']) # /home/user/data
11print(config['paths']['logs']) # /home/user/logs
12
13# Extended interpolation uses ${section:key} syntax
14config = configparser.ConfigParser(
15 interpolation=configparser.ExtendedInterpolation()
16)
17# [database]
18# host = localhost
19# [connection]
20# url = postgresql://${database:host}:5432/mydb
Reading from a String
1config = configparser.ConfigParser()
2
3ini_string = """
4[app]
5name = MyApp
6version = 1.0.0
7"""
8
9config.read_string(ini_string)
10print(config['app']['name']) # MyApp
11
12# Reading from a dictionary
13config.read_dict({
14 'app': {'name': 'MyApp', 'version': '2.0.0'},
15 'features': {'dark_mode': 'true'}
16})
Real-World Example
1import configparser
2from pathlib import Path
3
4def load_config(path='config.ini'):
5 config = configparser.ConfigParser()
6
7 # Read default config, then override with local
8 config.read(['config.defaults.ini', path])
9
10 return {
11 'db': {
12 'host': config.get('database', 'host'),
13 'port': config.getint('database', 'port'),
14 'name': config.get('database', 'name'),
15 },
16 'server': {
17 'host': config.get('server', 'host', fallback='127.0.0.1'),
18 'port': config.getint('server', 'port', fallback=8000),
19 'debug': config.getboolean('server', 'debug', fallback=False),
20 }
21 }
22
23settings = load_config()
24print(f"Server running at {settings['server']['host']}:{settings['server']['port']}")
Common Pitfalls
All values are strings: config['server']['port'] returns '8080' (a string), not 8080. Use getint(), getfloat(), or getboolean() for typed values. Passing the string directly to code expecting an integer causes TypeError.
DEFAULT section bleeds into all sections: Keys in [DEFAULT] appear in every section when iterating with config['section'].items(). This is by design — DEFAULT provides fallback values. If you want section-specific keys only, check config.options('section') and filter out DEFAULT keys.
Case-insensitive keys by default: configparser lowercases all keys. config['DB']['Host'] and config['DB']['host'] are the same. To preserve case, set config.optionxform = str before reading.
read() silently ignores missing files: config.read('nonexistent.ini') returns an empty list and does not raise an error. Check the return value or use config.read_file(open('file.ini')) which raises FileNotFoundError if the file does not exist.
Multi-line values need indentation: To store multi-line values, indent continuation lines with whitespace. Unindented lines are treated as new keys, causing MissingSectionHeaderError or corrupted data.
Summary
Use configparser.ConfigParser() with read(), get(), and write() for INI file operations
Use getint(), getfloat(), getboolean() for type-safe value retrieval with optional fallback defaults
The [DEFAULT] section provides fallback values inherited by all sections
Use %(key)s for basic interpolation or ExtendedInterpolation for cross-section references
Always open the file with 'w' mode when writing — config.write() outputs the entire configuration