Python ConfigParser
last modified October 30, 2026 Python 3.15
In this article we show how to work with configuration files in Python with
ConfigParser.
Python ConfigParser
ConfigParser is a class from the standard
configparser module which implements a basic configuration
language. The format is similar to Microsoft Windows INI files. It lets end
users customize a program without touching its code.
A configuration file consists of sections followed by key/value pairs. Section
names are written in [] brackets. Keys and values are separated
with = or :. Comment lines start with
# or ;.
ConfigParser stores all values as strings. Keys are
case-insensitive, while section names are case-sensitive.
Reading a file
In the first example, we read configuration data from a file.
; database connections [mysql] host = localhost user = user7 passwd = s$cret db = ydb [postgresql] host = localhost port = 5432 user = user8 passwd = mypwd$7 db = testdb
We have two sections of configuration data and a comment.
import configparser
config = configparser.ConfigParser()
found = config.read('db.ini')
print(found)
mysql = config['mysql']
print('MySQL configuration:')
print(f"Host: {mysql['host']}")
print(f"User: {mysql['user']}")
print(f"Database: {mysql['db']}")
pg = config['postgresql']
print('PostgreSQL configuration:')
print(f"Host: {pg['host']}")
print(f"Port: {pg['port']}")
print(f"Database: {pg['db']}")
print(config.read('missing.ini'))
The example reads configuration data for MySQL and PostgreSQL.
config = configparser.ConfigParser()
found = config.read('db.ini')
We create a ConfigParser and read the file with
read. The method returns the list of files that were read
successfully.
mysql = config['mysql']
print(f"Host: {mysql['host']}")
The parser behaves like a dictionary of sections, and each section like a dictionary of options.
print(config.read('missing.ini'))
A missing file is not an error; read simply returns an empty
list. If the file is required, check the return value or open the file
yourself and use read_file.
$ python reading_from_file.py ['db.ini'] MySQL configuration: Host: localhost User: user7 Database: ydb PostgreSQL configuration: Host: localhost Port: 5432 Database: testdb []
Sections and options
The sections method returns the section names, and
has_section checks whether a section exists.
import configparser
config = configparser.ConfigParser()
config.read('db.ini')
print(config.sections())
for name in ['mysql', 'postgresql', 'sqlite']:
print(name, config.has_section(name), name in config)
for key, value in config['postgresql'].items():
print(f'{key} = {value}')
print(config.has_option('mysql', 'port'))
print(config.options('mysql'))
We inspect the structure of the db.ini file.
print(name, config.has_section(name), name in config)
The in operator is an alternative to
has_section.
for key, value in config['postgresql'].items():
We iterate over the options of a section.
print(config.has_option('mysql', 'port'))
print(config.options('mysql'))
The has_option method checks for an option and
options lists the keys of a section.
$ python sections.py ['mysql', 'postgresql'] mysql True True postgresql True True sqlite False False host = localhost port = 5432 user = user8 passwd = mypwd$7 db = testdb False ['host', 'user', 'passwd', 'db']
Typed values
Since all values are strings, the parser provides getint,
getfloat, and getboolean methods for conversion.
[server] Port = 8080 timeout = 2.5 debug = yes workers = 4 [cache] enabled = off
The file contains numbers and boolean flags. The Port key is
written with a capital letter.
import configparser
config = configparser.ConfigParser()
config.read('app.ini')
server = config['server']
port = server.getint('port')
timeout = server.getfloat('timeout')
debug = server.getboolean('debug')
print(port, type(port).__name__)
print(timeout, type(timeout).__name__)
print(debug, type(debug).__name__)
print(config.getboolean('cache', 'enabled'))
print(server['PORT'], type(server['port']).__name__)
print(server.getint('max_clients', fallback=100))
print(server.get('host', 'localhost'))
try:
config.getint('server', 'debug')
except ValueError as e:
print(e)
We read the values with the proper types.
debug = server.getboolean('debug')
...
print(config.getboolean('cache', 'enabled'))
The getboolean method accepts 1/0,
yes/no, true/false, and
on/off, in any case.
print(server['PORT'], type(server['port']).__name__)
Keys are case-insensitive; Port, port, and
PORT are the same key. Plain indexing returns a string.
print(server.getint('max_clients', fallback=100))
print(server.get('host', 'localhost'))
The fallback value is returned when the option is missing.
try:
config.getint('server', 'debug')
except ValueError as e:
print(e)
A value that cannot be converted raises a ValueError.
$ python typed_values.py 8080 int 2.5 float True bool False 8080 str 100 localhost invalid literal for int() with base 10: 'yes'
The DEFAULT section
The special DEFAULT section provides values for all other
sections.
[DEFAULT] host = localhost timeout = 30 [mysql] user = user7 [postgresql] host = db.example.com user = user8
Both sections inherit host and timeout; the
postgresql section overrides host.
import configparser
config = configparser.ConfigParser()
config.read('defaults.ini')
print(config.sections())
for name in config.sections():
sec = config[name]
print(f"{name}: {sec['user']}@{sec['host']} timeout={sec['timeout']}")
print(dict(config.defaults()))
We print the effective values of each section.
print(config.sections())
The DEFAULT section is not listed among the sections.
print(dict(config.defaults()))
The defaults method returns the default values.
$ python default_section.py
['mysql', 'postgresql']
mysql: user7@localhost timeout=30
postgresql: user8@db.example.com timeout=30
{'host': 'localhost', 'timeout': '30'}
Reading strings and dictionaries
The read_string and read_dict methods load
configuration from a string and from a dictionary.
import configparser
cfg_data = '''
[mysql]
host = localhost
user = user7
db = ydb
'''
config = configparser.ConfigParser()
config.read_string(cfg_data)
print(dict(config['mysql']))
config.read_dict({
'mysql': {'port': 3306},
'redis': {'host': 'localhost', 'port': 6379},
})
print(config.sections())
print(dict(config['mysql']))
print(config['redis']['port'], type(config['redis']['port']).__name__)
We combine both sources in one parser.
config.read_dict({
'mysql': {'port': 3306},
'redis': {'host': 'localhost', 'port': 6379},
})
Keys of the outer dictionary are section names; the inner dictionaries hold
the options. New options are added to existing sections. Values are converted
to strings, so the port of Redis comes back as '6379'.
$ python read_from_string.py
{'host': 'localhost', 'user': 'user7', 'db': 'ydb'}
['mysql', 'redis']
{'host': 'localhost', 'user': 'user7', 'db': 'ydb', 'port': '3306'}
6379 str
Writing configuration
The write method writes the configuration to a file.
import configparser
config = configparser.ConfigParser()
config['mysql'] = {'host': 'localhost', 'user': 'user7', 'db': 'ydb'}
config.add_section('redis')
config.set('redis', 'host', 'localhost')
config['redis']['port'] = '6379'
config['mysql']['port'] = '3306'
config.remove_option('mysql', 'db')
with open('generated.ini', 'w') as f:
config.write(f)
We create a configuration in code and save it.
config['mysql'] = {'host': 'localhost', 'user': 'user7', 'db': 'ydb'}
Assigning a dictionary creates a whole section.
config.add_section('redis')
config.set('redis', 'host', 'localhost')
config['redis']['port'] = '6379'
Alternatively, we add an empty section and set the options one by one. The
set method and item assignment require string values.
config.remove_option('mysql', 'db')
Options and sections are deleted with remove_option and
remove_section. Note that comments in a file are not preserved
when the configuration is read and written back.
$ python writing.py $ cat generated.ini [mysql] host = localhost user = user7 port = 3306 [redis] host = localhost port = 6379
Interpolation
By default, ConfigParser uses basic interpolation: a value can
refer to other options of the same section (or the DEFAULT
section) with the %(name)s syntax.
[info] users_dir = C:\Users name = Jano home_dir = %(users_dir)s\%(name)s
We build the home_dir value from two other options. The trailing
s is part of the syntax.
import configparser
config = configparser.ConfigParser()
config.read('cfg.ini')
info = config['info']
print(info['users_dir'])
print(info['name'])
print(info['home_dir'])
print(info.get('home_dir', raw=True))
The example reads the values and prints them.
print(info.get('home_dir', raw=True))
With raw=True, we get the value without interpolation.
$ python interpolation.py C:\Users Jano C:\Users\Jano %(users_dir)s\%(name)s
The percent sign
With basic interpolation, a literal percent sign must be written as
%%. A single % causes an error when the value is
read.
import configparser
data = '''
[report]
title = Sales up 50%
note = 100%% done
'''
config = configparser.ConfigParser()
config.read_string(data)
try:
print(config['report']['title'])
except configparser.InterpolationSyntaxError as e:
print(e)
print(config['report']['note'])
raw = configparser.ConfigParser(interpolation=None)
raw.read_string(data)
print(raw['report']['title'])
print(raw['report']['note'])
We read values with a percent sign.
try:
print(config['report']['title'])
except configparser.InterpolationSyntaxError as e:
print(e)
The value Sales up 50% raises an
InterpolationSyntaxError.
raw = configparser.ConfigParser(interpolation=None)
If a file never uses interpolation, it is simplest to turn it off. Then every
value is returned exactly as written, including %%.
$ python percent.py
'%' must be followed by '%' or '(', found: '%'
100% done
Sales up 50%
100%% done
Extended interpolation
ExtendedInterpolation uses the ${name} syntax and
can refer to options in other sections with
${section:name}.
[common]
base = /opt/myapp
[paths]
logs = ${common:base}/logs
data = ${common:base}/data
archive = ${data}/archive
The paths are built from a base directory defined in another section.
import configparser
config = configparser.ConfigParser(
interpolation=configparser.ExtendedInterpolation())
config.read('paths.ini')
for key, value in config['paths'].items():
print(f'{key:8} {value}')
We pass an ExtendedInterpolation instance to the parser.
archive = ${data}/archive
References are resolved recursively; archive refers to
data, which refers to common:base.
$ python extended.py logs /opt/myapp/logs data /opt/myapp/data archive /opt/myapp/data/archive
Special values
Several constructor options relax the format: keys without values, values spanning several lines, inline comments, and options before the first section.
version = 2
[mysqld]
user = mysql
skip-networking
# comment line
hosts =
web1.example.com
web2.example.com
web3.example.com
pool = 10 ; inline comment
The file looks like a MySQL configuration file.
import configparser
config = configparser.ConfigParser(
allow_no_value=True,
allow_unnamed_section=True,
inline_comment_prefixes=(';',),
)
config.read('features.ini')
print(config.sections())
print(config[configparser.UNNAMED_SECTION]['version'])
mysqld = config['mysqld']
print(mysqld['skip-networking'])
print('skip-networking' in mysqld)
print(mysqld['hosts'].split())
print(repr(mysqld['pool']))
We enable the options needed to read the file.
config = configparser.ConfigParser(
allow_no_value=True,
allow_unnamed_section=True,
inline_comment_prefixes=(';',),
)
The allow_no_value option permits keys such as
skip-networking; their value is None. Python 3.13
added allow_unnamed_section for options before the first section
header; they are available under configparser.UNNAMED_SECTION.
Inline comments are disabled by default, because ; may be part of
a value.
print(mysqld['hosts'].split())
Indented lines continue the previous value. The value of hosts
contains newlines, which we split into a list.
$ python special.py [<UNNAMED_SECTION>, 'mysqld'] 2 None True ['web1.example.com', 'web2.example.com', 'web3.example.com'] '10'
Custom converters
The converters parameter adds custom get*
methods.
import configparser
data = '''
[web]
origins = localhost, example.com, test.example.com
ports = 80, 443, 8080
'''
def to_list(value):
return [item.strip() for item in value.split(',')]
config = configparser.ConfigParser(converters={'list': to_list})
config.read_string(data)
web = config['web']
print(web.getlist('origins'))
print([int(p) for p in config.getlist('web', 'ports')])
We add a getlist method for comma-separated values.
config = configparser.ConfigParser(converters={'list': to_list})
The key list creates the getlist method on both the
parser and its sections.
$ python converters.py ['localhost', 'example.com', 'test.example.com'] [80, 443, 8080]
Layered configuration
The read method accepts a list of files. Each file overrides the
values of the previous ones, which gives an easy way to combine defaults with
local settings.
[server] host = 0.0.0.0 port = 8080 debug = no
The base configuration is shipped with the application.
[server] debug = yes
The local file overrides only the debug option.
import configparser config = configparser.ConfigParser() loaded = config.read(['base.ini', 'local.ini', 'missing.ini']) print(loaded) print(dict(config['server']))
We read the base file, the local file, and a file that does not exist.
loaded = config.read(['base.ini', 'local.ini', 'missing.ini'])
Missing files are skipped, so optional configuration files need no special handling.
$ python layered.py
['base.ini', 'local.ini']
{'host': '0.0.0.0', 'port': '8080', 'debug': 'yes'}
Source
Python configparser documentation
In this article we have used ConfigParser to work with
configuration data in Python.
Author
List all Python tutorials.