GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • ZetCode
  • All
  • Golang
  • Python
  • C#
  • Java
  • JavaScript
  • Subscribe
  • Twitter
  • Github
  • Privacy
  • About

This view

  • Original page
  • New address

GeezerVison is showing zetcode.com — Python ConfigParser - working with configuration files

Ebooks

  • PyQt5 ebook
  • Tkinter ebook
  • SQLite Python
  • wxPython ebook
  • Windows API ebook
  • Java Swing ebook
  • Java games ebook
  • MySQL Java ebook

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.

db.ini
; 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.

reading_from_file.py
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.

sections.py
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.

app.ini
[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.

typed_values.py
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.

defaults.ini
[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.

default_section.py
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: [email protected] 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.

read_from_string.py
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.

writing.py
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.

cfg.ini
[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.

interpolation.py
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.

percent.py
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}.

paths.ini
[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.

extended.py
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.

features.ini
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.

special.py
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.

converters.py
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.

base.ini
[server]
host = 0.0.0.0
port = 8080
debug = no

The base configuration is shipped with the application.

local.ini
[server]
debug = yes

The local file overrides only the debug option.

layered.py
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

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Python tutorials.

You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.