The configparser module in the Python standard library reads and writes INI-style configuration files. INI files organize settings into named sections with key-value pairs, making them easy for both humans and programs to read without any special tooling. Use configparser for application settings, deployment configurations, and any structured data that needs to be edited by hand.
A typical INI file looks like this:
[app]
name = MyApp
version = 2.0
debug = true
[database]
host = localhost
port = 5432Loading that file and reading two of its values shows how the section and key names map directly onto the structure of the file on disk:
import configparser
config = configparser.ConfigParser()
config.read("settings.ini")
print(config["app"]["name"]) # MyApp
print(config["database"]["host"]) # localhostConfigParser() creates a parser. .read("settings.ini") loads the file. Settings are accessed with dictionary-style syntax: config["section"]["key"].
Reading configuration values
Access values by section and key, keeping in mind that INI files have no concept of types, so every value comes back as a plain string regardless of how it looks.
import configparser
config = configparser.ConfigParser()
config.read_string("""
[server]
host = 0.0.0.0
port = 8080
debug = true
[logging]
level = INFO
file = app.log
""")With the sample config loaded, reading a few values shows they all come back as strings, including "8080" and "true":
print(config["server"]["host"]) # 0.0.0.0
print(config["server"]["port"]) # 8080
print(config["server"]["debug"]) # trueEvery value from configparser is a string, even the ones that look numeric or boolean in the file. Use the typed getters when you need a specific type instead of manually converting each value:
port = config.getint("server", "port")
debug = config.getboolean("server", "debug")
level = config.get("logging", "level")
print(type(port), port) # <class 'int'> 8080
print(type(debug), debug) # <class 'bool'> True| Method | Returns |
|---|---|
| .get(section, key) | String |
| .getint(section, key) | Integer |
| .getfloat(section, key) | Float |
| .getboolean(section, key) | Boolean |
| .get(section, key, fallback=...) | String or fallback |
getboolean recognizes true, false, yes, no, on, off, 1, and 0 (case-insensitive), so config files written by hand in slightly different styles still parse the same way.
Setting default values
Define a [DEFAULT] section for values shared across all sections.
import configparser
config = configparser.ConfigParser()
config.read_string("""
[DEFAULT]
retries = 3
timeout = 30
[api]
url = https://api.example.com
[database]
host = localhost
port = 5432
timeout = 60
""")The api section never sets its own retries or timeout, but it inherits both from DEFAULT, while database overrides timeout with its own value:
print(config["api"].getint("retries")) # 3
print(config["api"].getint("timeout")) # 30
print(config["database"].getint("timeout")) # 60api inherits retries = 3 and timeout = 30 from DEFAULT. database overrides timeout to 60 but still inherits retries = 3.
Defaults are a good place for settings that apply to most sections, like connection timeouts or retry counts that rarely change per environment. Each section can still override a default with its own value when it genuinely needs to behave differently.
Writing configuration files
Modify values and write the updated configuration back to a file. A ConfigParser object behaves like a nested dictionary, so building one from scratch in code uses the same square-bracket syntax as reading one.
import configparser
from io import StringIO
config = configparser.ConfigParser()
config["server"] = {"host": "0.0.0.0", "port": "8080"}
config["database"] = {"host": "localhost", "port": "5432"}
output = StringIO()
config.write(output)
print(output.getvalue())Writing to an in-memory StringIO instead of a real file makes it easy to preview the generated INI text, which reproduces the familiar [section] and key = value layout:
[server]
host = 0.0.0.0
port = 8080
[database]
host = localhost
port = 5432Assign a dictionary to config["section"] to set all keys at once, which is often quicker than assigning each key individually. Use config["section"]["key"] = value to set individual keys. Call .write(file) to save to a file object, whether that is a real file or an in-memory buffer.
To update an existing configuration:
import configparser
config = configparser.ConfigParser()
config.read("settings.ini")
config["server"]["host"] = "192.168.1.10"
with open("settings.ini", "w") as file:
config.write(file)Checking for sections and keys
Reading a value that does not exist raises an error, so it helps to check first. Use in to check whether a section or key exists.
import configparser
config = configparser.ConfigParser()
config.read_string("""
[server]
host = localhost
""")
print("server" in config) # True
print("database" in config) # False
print("host" in config["server"]) # True
print("port" in config["server"]) # FalseYou can also list all sections (excluding DEFAULT, which is treated specially) and all keys within a specific section, which is useful for debugging or validating a config file at startup:
print(config.sections()) # ['server']
print(list(config["server"].keys())) # ['host']Interpolation: referencing other values
Repeating the same path or value across multiple keys invites typos and makes updates error-prone. ConfigParser supports basic variable substitution within the same section or from DEFAULT to avoid that duplication.
import configparser
config = configparser.ConfigParser()
config.read_string("""
[DEFAULT]
base_dir = /opt/myapp
[paths]
logs = %(base_dir)s/logs
data = %(base_dir)s/data
""")
print(config["paths"]["logs"]) # /opt/myapp/logs%(base_dir)s references the base_dir value from DEFAULT. The s indicates string substitution, which is the only interpolation type BasicInterpolation supports. Use %(key)s syntax to reuse values across sections and avoid duplication.
Practical example: application configuration loader
Build a simple configuration system that loads settings with type conversion and fallbacks.
import configparser
def load_app_config(config_path="app.ini"):
config = configparser.ConfigParser()
config["DEFAULT"] = {"debug": "false", "log_level": "INFO", "max_connections": "10"}
config["app"] = {}
config.read(config_path)
return {
"debug": config.getboolean("app", "debug"),
"log_level": config.get("app", "log_level"),
"host": config.get("server", "host", fallback="127.0.0.1"),
"port": config.getint("server", "port", fallback=8000),
"max_connections": config.getint("app", "max_connections"),
}Calling the function when no app.ini file exists yet falls back entirely to the defaults defined in code, so the caller never has to check whether the file was actually found:
settings = load_app_config()
for key, value in settings.items():
print(f"{key}: {value}")Since there is no app.ini on disk in this example, every value in the output comes straight from the DEFAULT section and the fallback arguments:
debug: False
log_level: INFO
host: 127.0.0.1
port: 8000
max_connections: 10The function defines sensible defaults in DEFAULT, reads an optional config file, and returns a dictionary with typed values. Missing sections or keys fall back to defaults without errors.
Common mistakes
Forgetting that all values are strings. config["server"]["port"] returns "8080", not 8080. Use .getint() and .getfloat() for numeric values.
Writing to a file without preserving structure. configparser.write() does not preserve comments, blank lines, or key ordering from the original file, so a round trip of reading and immediately rewriting a hand-edited file will silently strip its formatting. If you need to preserve formatting, use a different approach or write the file from scratch each time.
Using configparser for deeply nested data. INI files have exactly two levels: sections and keys. If you need lists, nested objects, or complex structures, use JSON or YAML instead.
Not providing fallbacks for optional settings. A missing section or key raises a KeyError, which can crash a program over a setting that was never strictly required. Use fallback in .get() or check with in before accessing.
Rune AI
Key Insights
- Use
configparser.ConfigParser()to create a parser and.read(filename)to load an INI file. - Access values with
parser['section']['key']orparser.get('section', 'key'). - Use
.getint(),.getfloat(), and.getboolean()for typed access. - Use
parser['DEFAULT']to set default values shared across all sections. - Write configuration changes with
parser.write(file). - INI files use
[section]headers andkey = valuepairs.
Frequently Asked Questions
Should I use configparser or JSON for configuration?
Does configparser preserve comments when writing?
How do I handle config files with duplicate keys?
Conclusion
configparser is the standard way to handle INI-style configuration files in Python. Use sections to organize settings, access values with dictionary-style syntax, and provide defaults for settings that may be missing. For simple flat configuration, configparser is clearer than JSON. For nested data, use JSON or YAML.
More in this topic
Format Dates and Times in Python
Learn how to use strftime and strptime to format dates as strings and parse date strings into Python datetime objects.
What Is the Python Standard Library?
Learn what the Python standard library is, why it matters, and how to use its built-in modules without installing anything extra.
Encode and Decode Base64 Data in Python
Learn how to use Python's base64 module to encode binary data as text and decode Base64 strings back to bytes.