The Story That Explains Config Files 🍽️Config Files समझाने वाली कहानी 🍽️一个讲清配置文件的故事 🍽️
Your Python program works the same way. The logic lives in code. The settings — database host, port, log level, feature switches — live in a configuration file. Change the file, restart the app, done: no code edits, no new release.
configparser is Python's built-in reader and writer for the simplest config format there is: INI files. Sections in square brackets, key = value lines, comments starting with # or ;. The format comes from early Windows in the 1980s, and it is still a favourite when non-programmers need to edit settings safely.आपका Python program भी ऐसे ही काम करता है। Logic code में रहता है। Settings — database host, port, log level, feature switches — एक configuration file में रहती हैं। File बदलें, app restart करें, हो गया: न code बदलना, न नया release।
configparser सबसे सादे config format, INI files, को पढ़ने और लिखने का Python का built-in औज़ार है। Square brackets में sections, key = value lines, और # या ; से शुरू होने वाले comments। यह format 1980s के शुरुआती Windows से आया है, और जब ग़ैर-programmers को settings सुरक्षित रूप से बदलनी हों तो आज भी पसंदीदा है।你的 Python 程序也是这样。逻辑 写在代码里。设置 — 数据库主机、端口、日志级别、功能开关 — 放在 配置文件 里。改文件、重启应用,搞定:不用改代码,也不用发新版本。
configparser 是 Python 内置的、用于读写最简单配置格式 — INI 文件 — 的工具。方括号里是节名,下面是 key = value 行,注释以 # 或 ; 开头。这种格式源自 20 世纪 80 年代的早期 Windows,至今仍是让非程序员安全修改设置的首选。Config files separate what changes from what does not. Code is what your program does; config is how it does it on this particular machine. Keep them apart, and one program runs on your laptop, the test server and production — each with its own settings file.
Config files बदलने वाली चीज़ों को न बदलने वाली चीज़ों से अलग करती हैं। Code बताता है program क्या करता है; config बताता है इस ख़ास मशीन पर वह कैसे करता है। इन्हें अलग रखें, और एक ही program आपके laptop, test server और production पर चलता है — हर एक की अपनी settings file के साथ।
配置文件 把会变的和不变的分开。代码决定程序 做什么;配置决定它在这台机器上 怎么做。把两者分开,同一个程序就能在你的笔记本、测试服务器和生产环境上运行 — 各自带一份设置文件。
Look for the interactive boxes: the INI Playground (Section 06), where you can type any INI text and see exactly what Python reads; the Write Round-Trip (10); the Layered Config Stack (11); and Where Am I Running? (12), which shows how the same code picks the right settings on your PC and on a server. The Playground follows Python's own parsing code and was checked against Python 3.13 on 246 test cases.
Interactive डिब्बे खोजें: INI Playground (Section 06), जहाँ आप कोई भी INI text लिखकर देख सकते हैं कि Python ठीक-ठीक क्या पढ़ता है; Write Round-Trip (10); Layered Config Stack (11); और Where Am I Running? (12), जो दिखाता है कि वही code आपके PC और server पर सही settings कैसे चुनता है। Playground Python के अपने parsing code को मानता है और 246 test cases पर Python 3.13 से जाँचा गया है।
请留意这些互动框:INI 演练场(第 06 节),你可以输入任意 INI 文本,准确看到 Python 读到了什么;写回往返(10);分层配置栈(11);以及 我在哪里运行?(12),展示同一份代码如何在你的电脑和服务器上选出正确的设置。演练场遵循 Python 自身的解析代码,并在 246 个测试用例上与 Python 3.13 核对过。
Anatomy of an INI File 🧬INI File की बनावट 🧬INI 文件的解剖 🧬
An INI file has only four building blocks. Everything else is just these, repeated.
INI file में सिर्फ़ चार building blocks होते हैं। बाक़ी सब इन्हीं का दोहराव है।
INI 文件只有四种构件,其余都是它们的重复。
* By default a ; or # after a value is kept as part of the value — see Section 09.
* Default रूप से value के बाद का ; या # value का ही हिस्सा रहता है — Section 09 देखें।
* 默认情况下,值 后面 的 ; 或 # 会被当作值的一部分 — 见第 09 节。
= or : both work. Spaces around them are removed. Keys become lowercase.
= या : दोनों चलते हैं। इनके आसपास के spaces हटते हैं। Keys lowercase बनती हैं।
= 和 : 都可以,周围的空格会被去掉。键会变成小写。
# or ; (after spaces) is a comment.
जो line (spaces के बाद) # या ; से शुरू हो, वह comment है।
(空格之后)以 # 或 ; 开头 的行是注释。
Reading a Config File — The Basics 📖Config File पढ़ना — बुनियादी बातें 📖读取配置文件 — 基础 📖
All examples in this tutorial run next to a few small INI files. Here is the first one:
इस tutorial के सारे उदाहरण कुछ छोटी INI files के साथ चलते हैं। यह पहली है:
本教程的所有示例都与几个小 INI 文件放在一起运行。这是第一个:
# app.ini
# Main application settings
[database]
host = localhost
port = 5432
user = admin
password = s3cret
[server]
debug = true
timeout = 30.5
workers = 4
[logging]
level = INFO
file = /var/log/app.log
import configparser
config = configparser.ConfigParser()
config.read("app.ini")
print(config["database"]["host"]) # dict style
print(config.get("database", "host")) # method style - same result
port = config["database"]["port"]
print(repr(port), type(port).__name__) # still a STRING
try:
print(port + 1)
except TypeError as e:
print("TypeError:", e)
config["database"]["port"] is the text '5432', not the number. And port + 1 does not quietly give "54321" — Python refuses with TypeError, as you can see above. Use getint(), getfloat() and getboolean() for anything that is not text.
config["database"]["port"] text '5432' है, नंबर नहीं। और port + 1 चुपचाप "54321" नहीं देता — Python TypeError के साथ मना करता है, जैसा ऊपर दिखा। जो text नहीं है उसके लिए getint(), getfloat() और getboolean() इस्तेमाल करें।
config["database"]["port"] 是文本 '5432',不是数字。而且 port + 1 不会悄悄得到 "54321" — 如上所示,Python 会抛出 TypeError 拒绝执行。非文本的值请用 getint()、getfloat() 和 getboolean()。
The Methods You Will Use 🧰जो Methods आप इस्तेमाल करेंगे 🧰你会用到的方法 🧰
| MethodMethod方法 | What it doesक्या करता है作用 |
|---|---|
read(files) | load one file or a list; returns the list it loaded; missing files are skipped silentlyएक file या list load करता है; load हुई files की list लौटाता है; ग़ायब files चुपचाप छूट जाती हैं加载一个文件或列表;返回成功加载的文件列表;缺失文件静默跳过 |
read_file(f) | load from an open file object — use it when the file is requiredखुले file object से load — जब file ज़रूरी हो तब从已打开的文件对象加载 — 文件必需时使用 |
read_string(s) / read_dict(d) | load from text or from a dict of dictstext या dicts की dict से load从文本或嵌套字典加载 |
sections() | section names, without DEFAULTsection नाम, DEFAULT के बिना节名列表,不含 DEFAULT |
options(s) / items(s) | keys / (key, value) pairs of a section — including DEFAULT keyssection की keys / (key, value) जोड़े — DEFAULT keys समेत某节的键 / 键值对 — 包括 DEFAULT 的键 |
defaults() | the DEFAULT keys as a dictDEFAULT keys एक dict के रूप मेंDEFAULT 的键(字典) |
has_section / has_option | True or FalseTrue या FalseTrue 或 False |
get / getint / getfloat / getboolean | read a value; fallback= covers a missing key onlyvalue पढ़ें; fallback= सिर्फ़ ग़ायब key को संभालता है读取值;fallback= 只处理 缺失 的键 |
set(s, k, v) / config[s][k] = v | change a value — must be a string (TypeError otherwise)value बदलें — string होनी चाहिए (वरना TypeError)修改值 — 必须是字符串(否则 TypeError) |
add_section / config[s] = {...} | create a section (a dict's values go through str())section बनाएँ (dict की values str() से गुज़रती हैं)创建节(字典的值会经过 str()) |
remove_section / remove_option | delete; returns True if something was removedहटाएँ; कुछ हटा तो True लौटाता है删除;删掉了就返回 True |
write(f) | write everything in memory to a file — comments are not keptmemory का सब कुछ file में लिखें — comments नहीं रहते把内存中的内容写入文件 — 注释不会保留 |
Walking Through Everythingसब कुछ देखना遍历全部内容
import configparser
config = configparser.ConfigParser()
config.read("app.ini")
for section in config.sections():
print(f"[{section}]")
for key, value in config.items(section):
print(f" {key} = {value}")
print(config.sections())
print(config.options("database"))
print("server" in config, "cache" in config) # sections work with "in"
print("port" in config["database"])
Missing Keys and Fallbacksग़ायब Keys और Fallbacks缺失的键与后备值
import configparser
config = configparser.ConfigParser()
config.read("app.ini")
ttl = config.getint("cache", "ttl", fallback=300) # no section -> fallback
name = config.get("database", "name", fallback="myapp") # no option -> fallback
print(ttl, name)
print(config.has_section("cache"), config.has_option("database", "host"))
try:
config.get("database", "name") # no fallback
except configparser.NoOptionError as e:
print(type(e).__name__ + ":", e)
config["database"]["port"] = "54x" # a broken value
try:
config.getint("database", "port", fallback=5432) # fallback does NOT help
except ValueError as e:
print("ValueError:", e)
fallback is used only when the section or key is missing. If the key exists but holds 54x, getint() still raises ValueError. That is a good thing: a typo in the file should not be hidden behind a default.
fallback सिर्फ़ तब इस्तेमाल होता है जब section या key ग़ायब हो। अगर key है पर उसमें 54x है, तो getint() फिर भी ValueError देता है। यह अच्छा है: file की typo default के पीछे नहीं छिपनी चाहिए।
fallback 只在节或键 缺失 时使用。如果键存在但值是 54x,getint() 仍会抛出 ValueError。这是好事:文件里的笔误不应该被默认值掩盖。
Getting Values with Proper Types 🔢सही Types के साथ Values पाना 🔢获取正确类型的值 🔢
import configparser
config = configparser.ConfigParser()
config.read("app.ini")
port = config.getint("database", "port")
print(port + 1)
timeout = config.getfloat("server", "timeout")
print(timeout, type(timeout).__name__)
print(config.getboolean("server", "debug"))
# Section proxies have the same getters
db = config["database"]
print(db.getint("port"), db.get("user"))
# Which words does getboolean understand?
for word in ["yes", "No", "ON", "off", "1", "0", "true", "FALSE", "maybe"]:
config["server"]["flag"] = word
try:
print(f"{word!r:8} -> {config.getboolean('server', 'flag')}")
except ValueError as e:
print(f"{word!r:8} -> ValueError: {e}")
getboolean() accepts 1/0, yes/no, true/false and on/off, in any letter case. Anything else — even y or enabled — raises ValueError, so a typo fails loudly instead of quietly meaning "off".
getboolean() 1/0, yes/no, true/false और on/off किसी भी case में स्वीकार करता है। बाक़ी कुछ भी — y या enabled भी — ValueError देता है, इसलिए typo चुपचाप "off" बनने के बजाय ज़ोर से fail होती है।
getboolean() 接受 1/0、yes/no、true/false 和 on/off,大小写不限。其他任何值 — 即使是 y 或 enabled — 都会抛出 ValueError,所以笔误会大声报错,而不是悄悄变成"关"。
Try It — The INI Playground 🧪करके देखें — INI Playground 🧪动手试试 — INI 演练场 🧪
Type or paste any INI text. Each section appears as a card; keys that fall through from [DEFAULT] are dashed. Click a key to see what get, getint, getfloat and getboolean return. Try the trap presets — they are the bugs people really hit.
कोई भी INI text लिखें या paste करें। हर section एक card के रूप में दिखता है; [DEFAULT] से आने वाली keys dashed हैं। किसी key पर click करें और देखें कि get, getint, getfloat और getboolean क्या लौटाते हैं। Trap presets आज़माएँ — ये वही bugs हैं जिनसे लोग सच में टकराते हैं।
输入或粘贴任意 INI 文本。每个节显示为一张卡片;来自 [DEFAULT] 的键用虚线框表示。点击一个键,查看 get、getint、getfloat 和 getboolean 的返回值。试试陷阱预设 — 这些都是大家真实遇到过的 bug。
The DEFAULT Section — Shared Values ⤵️DEFAULT Section — साझा Values ⤵️DEFAULT 节 — 共享的值 ⤵️
Every key in [DEFAULT] is visible in every other section, unless that section sets its own value. Put shared settings there once, and override only what is different.
[DEFAULT] की हर key हर दूसरे section में दिखती है, जब तक वह section अपनी value न रखे। साझा settings वहाँ एक बार रखें, और सिर्फ़ जो अलग है उसे override करें।
[DEFAULT] 中的每个键在 所有 其他节中都可见,除非该节自己设置了值。把共享设置写一次放在那里,只覆盖不同的部分。
# envs.ini
[DEFAULT]
log_level = INFO
timeout = 30
retries = 3
debug = no
[production]
host = prod.db.example.com
log_level = WARNING
[staging]
host = staging.db.example.com
[dev]
host = localhost
log_level = DEBUG
debug = yes
Purple ↓ lines come from [DEFAULT]. Red dashed boxes override it. Solid boxes are the section's own keys.
बैंगनी ↓ lines [DEFAULT] से आती हैं। लाल dashed boxes उसे override करते हैं। ठोस boxes section की अपनी keys हैं।
紫色 ↓ 行来自 [DEFAULT]。红色虚线框覆盖了它。实线框是该节自己的键。
import configparser
config = configparser.ConfigParser()
config.read("envs.ini")
print(config["staging"]["host"])
print(config["staging"]["log_level"]) # from DEFAULT
print(config["production"]["log_level"]) # overridden
print(config.getboolean("dev", "debug"), config.getboolean("staging", "debug"))
print(config.sections()) # DEFAULT is NOT listed
print(config.defaults())
print(config["DEFAULT"]["retries"]) # but you can still read it
print(config.options("staging")) # includes the DEFAULT keys
Interpolation — Values That Use Other Values 🔗Interpolation — दूसरी Values इस्तेमाल करने वाली Values 🔗插值 — 引用其他值的值 🔗
Interpolation lets a value refer to another value, so you write a base path or host only once. ConfigParser uses BasicInterpolation by default: %(key)s looks in the same section or in DEFAULT.
Interpolation से एक value दूसरी value की ओर इशारा कर सकती है, ताकि base path या host एक ही बार लिखना पड़े। ConfigParser default रूप से BasicInterpolation इस्तेमाल करता है: %(key)s उसी section या DEFAULT में देखता है।
插值让一个值引用另一个值,这样基础路径或主机名只需写一次。ConfigParser 默认使用 BasicInterpolation:%(key)s 会在 同一节或 DEFAULT 中查找。
# paths.ini
[DEFAULT]
home = /home/priya
[paths]
data = %(home)s/data
logs = %(home)s/logs
backup = %(data)s/backups
Python resolves references when you read a value, up to 10 levels deep. The file itself still holds the raw %(…)s text.
Python references तब हल करता है जब आप value पढ़ते हैं, 10 levels तक। File में raw %(…)s text ही रहता है।
Python 在你 读取 值时才解析引用,最多 10 层。文件本身仍保存原始的 %(…)s 文本。
import configparser
config = configparser.ConfigParser() # BasicInterpolation is the default
config.read("paths.ini")
print(config["paths"]["data"])
print(config["paths"]["backup"]) # a chain: backup -> data -> home
print(config.get("paths", "backup", raw=True)) # raw=True: no substitution
ExtendedInterpolation — Across SectionsExtendedInterpolation — Sections के पारExtendedInterpolation — 跨节引用
# api.ini
[server]
protocol = https
host = api.example.com
port = 8443
[api]
base_url = ${server:protocol}://${server:host}:${server:port}/v1
users_url = ${base_url}/users
health_url = ${base_url}/health
import configparser
config = configparser.ConfigParser(interpolation=configparser.ExtendedInterpolation())
config.read("api.ini")
print(config["api"]["base_url"])
print(config["api"]["users_url"])
print(config["api"]["health_url"])
With the default parser, every % must start a reference or be written %%. A password like 50%off! raises InterpolationSyntaxError — but only when you read that key, so the bug can hide for weeks. If your values are plain text, use ConfigParser(interpolation=None).
Default parser में हर % को reference शुरू करना चाहिए या %% लिखा जाना चाहिए। 50%off! जैसा password InterpolationSyntaxError देता है — पर सिर्फ़ तब जब आप वह key पढ़ते हैं, इसलिए bug हफ़्तों छिपा रह सकता है। अगर values सादा text हैं, तो ConfigParser(interpolation=None) इस्तेमाल करें।
使用默认解析器时,每个 % 都必须开始一个引用或写成 %%。像 50%off! 这样的密码会抛出 InterpolationSyntaxError — 但只在你 读取 这个键时才发生,所以 bug 可能潜伏好几周。如果你的值都是纯文本,请用 ConfigParser(interpolation=None)。
# secret.ini
[database]
password = 50%off!
import configparser
config = configparser.ConfigParser()
config.read("secret.ini")
try:
print(config["database"]["password"])
except configparser.InterpolationSyntaxError as e:
print(type(e).__name__ + ":", e)
# Fix 1: no interpolation at all
raw = configparser.ConfigParser(interpolation=None)
raw.read("secret.ini")
print(raw["database"]["password"])
# Fix 2: write %% in the file for a literal %
config.read_string("[database]\npassword = 50%%off!\n")
print(config["database"]["password"])
Parsing Rules That Surprise People 🪤Parsing के नियम जो लोगों को चौंकाते हैं 🪤让人意外的解析规则 🪤
Inline Comments Stay in the ValueInline Comments Value में ही रहते हैं行内注释会留在值里
# inline.ini
[server]
timeout = 30 ; seconds
host = 0.0.0.0 # listen on all interfaces
import configparser
config = configparser.ConfigParser()
config.read("inline.ini")
print(repr(config["server"]["timeout"]))
print(repr(config["server"]["host"]))
try:
config.getint("server", "timeout")
except ValueError as e:
print("ValueError:", e)
# Tell the parser which inline comment markers to allow
config = configparser.ConfigParser(inline_comment_prefixes=(";", "#"))
config.read("inline.ini")
print(config.getint("server", "timeout"), repr(config["server"]["host"]))
Values like URLs and colours often contain # (http://x/page#top, #ff0000), so by default Python only treats whole lines as comments. Either put comments on their own line, or opt in with inline_comment_prefixes=(";", "#") — a marker then counts only when it follows a space.
URLs और रंगों जैसी values में अक्सर # होता है (http://x/page#top, #ff0000), इसलिए default रूप से Python सिर्फ़ पूरी lines को comments मानता है। या तो comments अपनी अलग line पर रखें, या inline_comment_prefixes=(";", "#") से चुनें — तब चिह्न तभी गिना जाता है जब वह किसी space के बाद आए।
URL 和颜色这类值里常含有 #(http://x/page#top、#ff0000),所以默认情况下 Python 只把 整行 当作注释。要么把注释单独放一行,要么用 inline_comment_prefixes=(";", "#") 主动开启 — 这时标记只有跟在空格后面才算注释。
Keys Are Lowercase, Sections Are NotKeys lowercase हैं, Sections नहीं键不分大小写,节分
import configparser
config = configparser.ConfigParser()
config.read_string("""
[Server]
Port = 8080
""")
print(config.sections()) # section names keep their case
print(config["Server"]["PORT"], config["Server"]["port"]) # keys do not
print(list(config["Server"]))
print("server" in config) # sections ARE case-sensitive
keep = configparser.ConfigParser()
keep.optionxform = str # keep key case as written
keep.read_string("[Server]\nPort = 8080\n")
print(list(keep["Server"]))
Multi-line Values and Strict ErrorsMulti-line Values और Strict Errors多行值与严格模式错误
import configparser
config = configparser.ConfigParser()
config.read_string("""
[email]
signature = Regards,
Priya Sharma
Baddi, Himachal Pradesh
""")
print(config["email"]["signature"]) # indented lines continue the value
for text in ["host = x\n[a]\n", "[a]\nx = 1\nx = 2\n", "[a]\n[a]\n", "[a]\njust a line\n"]:
try:
configparser.ConfigParser().read_string(text)
except configparser.Error as e:
print(type(e).__name__ + ":", str(e).splitlines()[0])
Writing and Updating Config Files ✍️Config Files लिखना और बदलना ✍️写入与更新配置文件 ✍️
# messy.ini
# Team settings - edit with care!
[Server]
Host = 0.0.0.0
Port = 8080
; old value: 8000
[Database]
URL = postgres://db.local/app
Password = hunter2
import configparser, io
config = configparser.ConfigParser()
config.read("messy.ini")
config["Server"]["port"] = "9090" # values must be strings
config["cache"] = {"backend": "redis", "ttl": 3600} # dicts are converted with str()
config.remove_option("Database", "password")
try:
config.set("Server", "workers", 4) # a plain int is refused
except TypeError as e:
print("TypeError:", e)
out = io.StringIO()
config.write(out) # usually: open("app.ini", "w")
print(out.getvalue())
The parser never stores comments, original key spelling or spacing. So config.write() produces a clean, new file. That is perfect for files your program owns (user preferences, generated config). For hand-written files that people comment carefully, change them by hand — or use a round-trip library such as configupdater.
Parser comments, keys की मूल spelling या spacing कभी नहीं रखता। इसलिए config.write() एक साफ़, नई file बनाता है। यह उन files के लिए बढ़िया है जो आपका program ख़ुद संभालता है (user preferences, बनाई गई config)। जिन हाथ से लिखी files में लोग ध्यान से comments लिखते हैं, उन्हें हाथ से बदलें — या configupdater जैसी round-trip library इस्तेमाल करें।
解析器从不保存注释、键的原始拼写和空格。所以 config.write() 会生成一个干净的新文件。这非常适合程序自己管理的文件(用户偏好、生成的配置)。对于人们精心写了注释的手写文件,请手动修改 — 或使用 configupdater 这类可往返的库。
import configparser
config = configparser.ConfigParser()
config.read("app.ini")
print(config.remove_option("database", "password")) # True: it was there
print(config.remove_option("database", "password")) # False: already gone
print(config.remove_section("logging")) # True
print(config.sections())
Building a File from Scratchशुरू से File बनाना从零创建文件
import configparser, sys
config = configparser.ConfigParser()
config["DEFAULT"] = {"log_level": "INFO", "timeout": "30"}
config["database"] = {"host": "localhost", "port": "5432"}
config["server"] = {"debug": "false", "workers": "4"}
config.write(sys.stdout) # in real code: with open("new.ini", "w") as f: config.write(f)
Try It — Write Round-Tripकरके देखें — Write Round-Trip动手试试 — 写回往返
Step through a real session: read a file, change it in memory, then write(). At the last step, red lines in the old file were lost and green lines in the new file are new. Every message is exactly what Python 3.13 printed.
एक असली session में कदम-दर-कदम चलें: file पढ़ें, memory में बदलें, फिर write() करें। आख़िरी कदम पर पुरानी file की लाल lines खो गईं और नई file की हरी lines नई हैं। हर संदेश ठीक वही है जो Python 3.13 ने छापा।
逐步走完一次真实的会话:读取文件、在内存中修改,然后 write()。在最后一步,旧文件中的红色行已丢失,新文件中的绿色行是新增的。每条信息都与 Python 3.13 打印的完全一致。
If a key in memory looks like a section header (for example a key named [oops]), writing it would produce a file that reads back differently. Since Python 3.14, write() raises InvalidWriteError instead of silently writing that file.
अगर memory की कोई key section header जैसी दिखती है (जैसे [oops] नाम की key), तो उसे लिखने से ऐसी file बनेगी जो वापस पढ़ने पर अलग निकलेगी। Python 3.14 से write() चुपचाप वह file लिखने के बजाय InvalidWriteError देता है।
如果内存中的某个键看起来像节头(例如名为 [oops] 的键),写出后再读回就会不一样。从 Python 3.14 起,write() 会抛出 InvalidWriteError,而不是悄悄写出这样的文件。
Layered Config from Several Files 🥞कई Files से परतदार Config 🥞来自多个文件的分层配置 🥞
Professional apps load several files in order: defaults shipped with the app, then a system file, then the user's file, then a local file. Each layer changes only what it cares about; a later file wins.
Professional apps कई files क्रम से load करते हैं: app के साथ आए defaults, फिर system file, फिर user की file, फिर local file। हर परत सिर्फ़ वही बदलती है जिसकी उसे परवाह है; बाद वाली file जीतती है।
专业应用会按顺序加载多个文件:随应用发布的默认值、系统文件、用户文件,最后是本地文件。每一层只改它关心的部分;后加载的文件获胜。
import configparser
from pathlib import Path
config = configparser.ConfigParser()
loaded = config.read([
"defaults.ini", # shipped with the app
"etc_app.ini", # system-wide (e.g. /etc/app.ini)
Path.home() / ".app.ini", # the user's own file - may not exist
"local_app.ini", # this folder: highest priority
])
print("Loaded:", loaded)
for section in config.sections():
print(section, dict(config[section]))
print(config.read("~/.app.ini")) # read() does NOT expand ~ : nothing loaded
config.read("~/.app.ini") looks for a folder literally named ~ and finds nothing — silently. Build the path with Path.home() / ".app.ini" or os.path.expanduser("~/.app.ini").
config.read("~/.app.ini") सचमुच ~ नाम का folder खोजता है और कुछ नहीं पाता — चुपचाप। Path को Path.home() / ".app.ini" या os.path.expanduser("~/.app.ini") से बनाएँ।
config.read("~/.app.ini") 会去找一个名字就叫 ~ 的文件夹,结果什么也找不到 — 而且不报错。请用 Path.home() / ".app.ini" 或 os.path.expanduser("~/.app.ini") 构建路径。
Try It — Layered Config Stackकरके देखें — Layered Config Stack动手试试 — 分层配置栈
Click a layer to remove or restore its file. The merged result shows which file each value came from. All 16 combinations were computed by Python.
किसी layer पर click करके उसकी file हटाएँ या वापस लाएँ। मिला हुआ नतीजा दिखाता है कि हर value किस file से आई। सभी 16 combinations Python ने निकाले हैं।
点击某一层可以移除或恢复它的文件。合并结果会显示每个值来自哪个文件。全部 16 种组合都由 Python 计算得出。
When a File Is Requiredजब File ज़रूरी हो当文件是必需的
import configparser
config = configparser.ConfigParser()
print(config.read("missing.ini")) # [] - silent!
try:
with open("missing.ini", encoding="utf-8") as f:
config.read_file(f) # a required file: fail loudly
except FileNotFoundError as e:
print("FileNotFoundError:", e)
Local PC vs Server — One Code, the Right Settings Everywhere 🌍Local PC बनाम Server — एक Code, हर जगह सही Settings 🌍本地电脑与服务器 — 同一份代码,处处正确的设置 🌍
Here is the real-life question: the same code runs on your laptop and on the server. On your PC you want debug on, a local database and your own password. On the server you want debug off, the real database, and a password that is never stored in Git. How does the code know where it is running, and pick the right values?
असली सवाल यह है: वही code आपके laptop पर भी चलता है और server पर भी। PC पर आपको debug चालू, local database और अपना password चाहिए। Server पर debug बंद, असली database, और ऐसा password चाहिए जो Git में कभी न रखा जाए। Code को कैसे पता चले कि वह कहाँ चल रहा है, और वह सही values कैसे चुने?
现实中的问题是:同一份代码 既在你的笔记本上运行,也在服务器上运行。在电脑上,你希望开启调试、使用本地数据库和自己的密码。在服务器上,你希望关闭调试、使用真实数据库,并且密码 绝不 存进 Git。代码怎么知道自己运行在哪里,并选出正确的值?
The answer is a small loader with one switch and layers: the environment variable APP_ENV says which environment this is, and settings are stacked so that later layers win.
जवाब है एक छोटा loader जिसमें एक switch और परतें हैं: environment variable APP_ENV बताता है कि यह कौन-सा environment है, और settings ऐसे ढेर की जाती हैं कि बाद की परत जीते।
答案是一个小加载器,包含 一个开关 和 多个层:环境变量 APP_ENV 说明当前是哪个环境,设置按层叠加,后面的层获胜。
The code and the shared files come from Git. Each machine adds only what is special about it.
Code और साझा files Git से आती हैं। हर मशीन सिर्फ़ वही जोड़ती है जो उसकी ख़ासियत है।
代码和共享文件来自 Git。每台机器只添加自己特有的部分。
base.ini{APP_ENV}.inidevelopment.ini or production.ini, chosen by APP_ENV. In Git, but no passwords.development.ini या production.ini, APP_ENV से चुनी गई। Git में, पर कोई password नहीं।development.ini 或 production.ini,由 APP_ENV 选择。存于 Git,但 不含密码。local.ini.gitignore..gitignore में।.gitignore 中。APP_SECTION_KEYDB_PASSWORDThe Project LayoutProject की बनावट项目结构
billing/
├── main.py
├── settings.py # the loader
├── config/
│ ├── base.ini # ✅ in Git
│ ├── development.ini # ✅ in Git
│ ├── production.ini # ✅ in Git - no passwords!
│ └── local.ini # ❌ NOT in Git - your PC only
└── .gitignore # contains: config/local.ini
# config/base.ini
# Shared by every machine - committed to Git
[app]
name = Billing
log_level = INFO
[database]
host = localhost
port = 5432
name = billing
user = billing_app
password =
[server]
debug = false
port = 8000
# config/development.ini
# Your PC - committed to Git
[app]
log_level = DEBUG
[server]
debug = true
# config/production.ini
# The server - committed to Git, but NO passwords here
[database]
host = db.internal.example.com
name = billing_prod
[server]
port = 80
# config/local.ini
# Only on YOUR PC - listed in .gitignore, never committed
[database]
port = 5433
password = my-local-pass
The Loader — settings.pyLoader — settings.py加载器 — settings.py
# settings.py
import configparser
import os
from pathlib import Path
# Find config/ next to THIS file - not in whatever folder the server starts in
CONFIG_DIR = Path(__file__).resolve().parent / "config"
def load_settings():
env = os.getenv("APP_ENV", "development") # your PC: nothing to set
config = configparser.ConfigParser(interpolation=None)
env_file = CONFIG_DIR / f"{env}.ini"
if not env_file.exists(): # a typo must fail loudly
raise FileNotFoundError(f"No config for APP_ENV={env!r}: {env_file.name}")
loaded = config.read([
CONFIG_DIR / "base.ini", # 1. shared by everyone
env_file, # 2. development.ini / production.ini
CONFIG_DIR / "local.ini", # 3. this PC only (in .gitignore)
], encoding="utf-8")
# 4. Environment variables win over every file: APP_<SECTION>_<KEY>
for section in config.sections():
for key in config[section]:
name = f"APP_{section}_{key}".upper()
if name in os.environ:
config[section][key] = os.environ[name]
# 5. Secrets come ONLY from the environment on a server
if "DB_PASSWORD" in os.environ:
config["database"]["password"] = os.environ["DB_PASSWORD"]
if env == "production" and not config["database"]["password"]:
raise RuntimeError("DB_PASSWORD must be set on the server")
return config, env, loaded
# main.py
from pathlib import Path
from settings import load_settings
config, env, loaded = load_settings()
db = config["database"]
print("Environment :", env)
print("Loaded :", [Path(p).name for p in loaded])
print("Database :", f"{db['user']}@{db['host']}:{db.getint('port')}/{db['name']}")
print("Password set:", bool(db["password"]))
print("Debug :", config.getboolean("server", "debug"))
print("Log level :", config["app"]["log_level"])
print("Server port :", config.getint("server", "port"))
The Same Code, Run in Two Placesवही Code, दो जगह चलाया गया同一份代码,在两处运行
First on your PC — nothing set, local.ini present. Then on the server — APP_ENV=production and DB_PASSWORD set, no local.ini. Both outputs are real runs.
पहले आपके PC पर — कुछ set नहीं, local.ini मौजूद। फिर server पर — APP_ENV=production और DB_PASSWORD set, local.ini नहीं। दोनों outputs असली runs हैं।
先在你的电脑上 — 什么都没设置,有 local.ini。再在服务器上 — 设置了 APP_ENV=production 和 DB_PASSWORD,没有 local.ini。两段输出都是真实运行结果。
$ python main.py$ export APP_ENV=production
$ export DB_PASSWORD='Pr0d-S3cret!'
$ python main.pyHow to Set APP_ENV on Each Machineहर मशीन पर APP_ENV कैसे set करें如何在各种机器上设置 APP_ENV
| Whereकहाँ位置 | Howकैसे方法 |
|---|---|
| Your PCआपका PC你的电脑 | Nothing — the code defaults to developmentकुछ नहीं — code default रूप से development लेता है无需设置 — 代码默认 development |
| Linux / macOS | export APP_ENV=production |
| Windows PowerShell | $env:APP_ENV = "production" |
| Windows CMD | set APP_ENV=production |
| systemd service (Linux server)systemd service (Linux server)systemd 服务(Linux 服务器) | Environment=APP_ENV=productionEnvironment=DB_PASSWORD=… |
| Docker | docker run -e APP_ENV=production -e DB_PASSWORD=… image |
| Hosting panel (Render, Railway, Heroku…)Hosting panel (Render, Railway, Heroku…)托管平台面板(Render、Railway、Heroku…) | "Environment variables" settings page"Environment variables" settings page"Environment variables" 设置页面 |
Try It — Where Am I Running?करके देखें — मैं कहाँ चल रहा हूँ?动手试试 — 我在哪里运行?
Pick a machine, or set each switch yourself. The console shows the real output of python main.py for that exact setup — all 32 combinations were run with Python 3.13. Try the presets with ⚠️: they are the mistakes that cause real outages.
एक मशीन चुनें, या हर switch ख़ुद set करें। Console उसी setup के लिए python main.py का असली output दिखाता है — सभी 32 combinations Python 3.13 से चलाए गए। ⚠️ वाले presets आज़माएँ: यही गलतियाँ असली outages करती हैं।
选择一台机器,或者自己设置每个开关。控制台会显示该配置下 python main.py 的真实输出 — 全部 32 种组合都用 Python 3.13 运行过。试试带 ⚠️ 的预设:它们正是导致真实故障的错误。
On your PC you usually start the program from the project folder, so config.read("app.ini") works. On a server, a service manager (systemd, cron, a Docker entrypoint) may start it from / or another folder — and read() silently finds nothing. Build paths from Path(__file__).resolve().parent, as settings.py does.
आपके PC पर आप आमतौर पर program project folder से शुरू करते हैं, इसलिए config.read("app.ini") चलता है। Server पर service manager (systemd, cron, Docker entrypoint) उसे / या किसी दूसरे folder से शुरू कर सकता है — और read() चुपचाप कुछ नहीं पाता। Paths Path(__file__).resolve().parent से बनाएँ, जैसा settings.py करता है।
在你的电脑上,通常从项目文件夹启动程序,所以 config.read("app.ini") 能用。在服务器上,服务管理器(systemd、cron、Docker 入口)可能从 / 或其他文件夹启动它 — 这时 read() 会静默地什么也找不到。请像 settings.py 那样,用 Path(__file__).resolve().parent 构建路径。
① Default APP_ENV to development, so a new teammate can run the code with zero setup. ② Fail loudly on an unknown APP_ENV or a missing secret in production. ③ Keep local.ini and .env in .gitignore. ④ Put passwords only in server environment variables or a secrets manager. ⑤ Print which files were loaded at start-up — it ends most "works on my PC" mysteries. (Many teams also use the third-party python-dotenv package to load a local .env file into environment variables; it fits the same layers.)
① APP_ENV का default development रखें, ताकि नया साथी बिना setup के code चला सके। ② अनजान APP_ENV या production में ग़ायब secret पर ज़ोर से fail करें। ③ local.ini और .env को .gitignore में रखें। ④ Passwords सिर्फ़ server के environment variables या secrets manager में रखें। ⑤ Start-up पर छापें कि कौन-सी files load हुईं — इससे "मेरे PC पर तो चलता है" वाले ज़्यादातर रहस्य सुलझ जाते हैं। (बहुत सी टीमें local .env file को environment variables में load करने के लिए third-party python-dotenv package भी इस्तेमाल करती हैं; वह भी इन्हीं परतों में फ़िट होता है।)
① 把 APP_ENV 默认设为 development,让新同事无需任何设置就能运行代码。② 遇到未知的 APP_ENV 或生产环境缺少机密时,大声报错。③ 把 local.ini 和 .env 放进 .gitignore。④ 密码只放在服务器的环境变量或机密管理器中。⑤ 启动时打印加载了哪些文件 — 大多数"在我电脑上能跑"的谜团都会迎刃而解。(很多团队还用第三方包 python-dotenv 把本地 .env 文件加载为环境变量;它同样契合这套分层。)
Real-World Recipes 🛠️असली काम की Recipes 🛠️实战配方 🛠️
1 · Environment Switcher1 · Environment बदलना1 · 环境切换
One file, several environments. An environment variable decides which section is active; [DEFAULT] fills in the rest.
एक file, कई environments। एक environment variable तय करता है कि कौन-सा section चालू है; बाक़ी [DEFAULT] भरता है।
一个文件,多个环境。由一个环境变量决定使用哪个节;其余部分由 [DEFAULT] 补上。
import configparser, os
os.environ.setdefault("APP_ENV", "staging") # normally set outside Python
env = os.environ["APP_ENV"]
config = configparser.ConfigParser()
config.read("envs.ini")
if not config.has_section(env):
raise ValueError(f"Unknown environment: {env}")
active = config[env]
print(f"Running in {env} mode")
print(f"Database: {active['host']}")
print(f"Debug: {active.getboolean('debug')}")
print(f"Retries: {active.getint('retries')}")
2 · Feature Flags2 · Feature Flags2 · 功能开关
import configparser
config = configparser.ConfigParser()
config.read_string("""
[features]
dark_mode = true
new_dashboard = off
beta_analytics = yes
""")
def flag_enabled(name):
return config.getboolean("features", name, fallback=False)
for name in ["dark_mode", "new_dashboard", "beta_analytics", "voice_search"]:
print(f"{name:15} {flag_enabled(name)}")
3 · Saving User Preferences3 · User Preferences save करना3 · 保存用户偏好
import configparser
from pathlib import Path
CONFIG_PATH = Path.home() / ".myapp.ini"
def load_prefs():
config = configparser.ConfigParser()
config.read(CONFIG_PATH) # fine if the file does not exist yet
return config
def save_prefs(config):
with open(CONFIG_PATH, "w", encoding="utf-8") as f:
config.write(f)
prefs = load_prefs()
if not prefs.has_section("ui"):
prefs.add_section("ui")
prefs["ui"]["theme"] = "dark"
prefs["ui"]["font_size"] = "14"
prefs["ui"]["language"] = "hi"
save_prefs(prefs)
print("Saved to", CONFIG_PATH)
print(CONFIG_PATH.read_text(encoding="utf-8"))
print(load_prefs().getint("ui", "font_size"))
Passwords and API keys do not belong in a config file that goes into Git — the history keeps them forever, even after you delete the line. Read them from environment variables (os.environ["DB_PASSWORD"]), a secrets manager, or a separate file listed in .gitignore.
Passwords और API keys उस config file में नहीं होनी चाहिए जो Git में जाती है — history उन्हें हमेशा रखती है, line delete करने के बाद भी। इन्हें environment variables (os.environ["DB_PASSWORD"]), secrets manager, या .gitignore में दर्ज अलग file से पढ़ें।
密码和 API 密钥不应出现在提交到 Git 的配置文件中 — 即使删掉这一行,历史记录也会永远保留它们。请从环境变量(os.environ["DB_PASSWORD"])、机密管理器,或列在 .gitignore 中的单独文件读取。
INI vs JSON vs YAML vs TOML ⚖️INI बनाम JSON बनाम YAML बनाम TOML ⚖️INI、JSON、YAML 与 TOML ⚖️
| FormatFormat格式 | Read in PythonPython में पढ़नाPython 读取 | TypesTypes类型 | CommentsComments注释 | Best forकिसके लिए बेहतर适用场景 |
|---|---|---|---|---|
| INI | configparser (stdlib, read + write) | strings onlyसिर्फ़ strings仅字符串 | ✅ | flat settings edited by peopleलोगों द्वारा बदली जाने वाली सादी settings由人编辑的扁平设置 |
| TOML | tomllib (stdlib 3.11+, read only) | int, float, bool, date, list, tableint, float, bool, date, list, table整数、浮点、布尔、日期、列表、表 | ✅ | Python tools (pyproject.toml), typed configPython tools (pyproject.toml), typed configPython 工具(pyproject.toml)、带类型的配置 |
| JSON | json (stdlib) | numbers, bool, null, list, objectnumbers, bool, null, list, object数字、布尔、null、列表、对象 | ❌ | data made and read by programsprograms द्वारा बनाया और पढ़ा जाने वाला data由程序生成和读取的数据 |
| YAML | PyYAML (pip install)PyYAML (pip install)PyYAML(需 pip 安装) | rich, nestedसमृद्ध, nested丰富、可嵌套 | ✅ | deep nesting, DevOps (CI, Kubernetes)गहरी nesting, DevOps (CI, Kubernetes)深层嵌套、DevOps(CI、Kubernetes) |
# pyproject.toml
[project]
name = "billing"
version = "1.4.0"
dependencies = ["requests>=2.32", "rich"]
[tool.billing]
retries = 3
debug = false
timeout = 2.5
import tomllib # Python 3.11+: read TOML with no extra package
with open("pyproject.toml", "rb") as f: # tomllib needs binary mode
data = tomllib.load(f)
print(data["project"]["dependencies"]) # a real list
cfg = data["tool"]["billing"]
print(cfg) # real int, bool and float
print(type(cfg["retries"]).__name__, type(cfg["debug"]).__name__)
Flat settings that people edit, no extra package, and you also need to write the file → INI. Typed values or lists, read-only from Python → TOML with tomllib. Data exchanged between programs → JSON. Deeply nested DevOps config → YAML.
लोगों द्वारा बदली जाने वाली सादी settings, कोई अतिरिक्त package नहीं, और file लिखनी भी हो → INI। Typed values या lists, Python से सिर्फ़ पढ़ना → tomllib के साथ TOML। Programs के बीच data → JSON। गहरी nested DevOps config → YAML।
由人编辑的扁平设置、不想装额外包、还需要 写回 文件 → INI。需要类型或列表、Python 只读 → 用 tomllib 读 TOML。程序之间交换数据 → JSON。深层嵌套的 DevOps 配置 → YAML。
New in Python 3.13: Keys Before Any SectionPython 3.13 में नया: किसी Section से पहले KeysPython 3.13 新增:节之前的键
import configparser
# Python 3.13+: keys before the first [section] are allowed if you ask
config = configparser.ConfigParser(allow_unnamed_section=True)
config.read_string("""
version = 2
[app]
name = billing
""")
print(config.get(configparser.UNNAMED_SECTION, "version"))
print(config["app"]["name"])
Common Pitfalls 🕳️आम ग़लतियाँ 🕳️常见陷阱 🕳️
getint, getfloat, getboolean. When setting, pass strings — set() raises TypeError for an int.getint, getfloat, getboolean इस्तेमाल करें। Set करते समय strings दें — int पर set() TypeError देता है।getint、getfloat、getboolean。设置时传字符串 — 传 int 时 set() 会抛出 TypeError。; … after a value is part of the value. Use whole-line comments or inline_comment_prefixes.; … value का हिस्सा है। पूरी line वाले comments या inline_comment_prefixes इस्तेमाल करें।; … 属于值本身。请用整行注释或 inline_comment_prefixes。%%, or use interpolation=None when values are plain text.%% लिखें, या values सादा text हों तो interpolation=None इस्तेमाल करें।%%,或在值为纯文本时用 interpolation=None。read() returns, or use read_file(open(path)) for required files. And remember: ~ is not expanded.read() क्या लौटाता है, या ज़रूरी files के लिए read_file(open(path)) इस्तेमाल करें। और याद रखें: ~ expand नहीं होता।read() 的返回值,或对必需文件使用 read_file(open(path))。还要记住:~ 不会被展开。write().write() से overwrite न करें।write() 覆盖精心编写的文件。Golden Rules and Quiz 🏆सुनहरे नियम और Quiz 🏆黄金法则与小测验 🏆
getint, getfloat, getboolean — never do maths on config[s][k].getint, getfloat, getboolean — config[s][k] पर कभी गणना न करें।getint、getfloat、getboolean — 永远不要直接对 config[s][k] 做运算。fallback=, and let real typos fail loudly.fallback= दें, और असली typos को ज़ोर से fail होने दें।fallback=,让真正的笔误大声报错。[DEFAULT] and override only what differs.[DEFAULT] में रखें और सिर्फ़ जो अलग है उसे override करें।[DEFAULT],只覆盖不同的部分。interpolation=None for plain text, ExtendedInterpolation() for ${section:key}.interpolation=None, ${section:key} के लिए ExtendedInterpolation()।interpolation=None,${section:key} 用 ExtendedInterpolation()。inline_comment_prefixes).inline_comment_prefixes चालू करें)।inline_comment_prefixes)。read() returns; use read_file(open(path)) for required files.read() क्या लौटाता है; ज़रूरी files के लिए read_file(open(path))।read() 的返回值;必需文件用 read_file(open(path))。APP_ENV (default development), stack base.ini → {env}.ini → local.ini → environment variables, and find files with Path(__file__).APP_ENV से environment चुनें (default development), base.ini → {env}.ini → local.ini → environment variables की परतें बनाएँ, और files Path(__file__) से खोजें।APP_ENV 选择环境(默认 development),按 base.ini → {env}.ini → local.ini → 环境变量 叠加,并用 Path(__file__) 定位文件。write() overwrite a hand-commented file.write() को हाथ से comment की गई file overwrite न करने दें।write() 覆盖手写注释的文件。Quick Quiz — 8 Questionsछोटा Quiz — 8 सवाल小测验 — 8 道题
An INI file is sections, keys, values and comments. configparser reads every value as a string — use typed getters, put shared values in [DEFAULT], and pick interpolation on purpose. Watch the traps: % in values, inline comments, silent missing files, and write() losing comments. For typed or nested config, reach for TOML with tomllib.
INI file sections, keys, values और comments से बनती है। configparser हर value string के रूप में पढ़ता है — typed getters इस्तेमाल करें, साझा values [DEFAULT] में रखें, और interpolation सोच-समझकर चुनें। जालों से बचें: values में %, inline comments, चुपचाप ग़ायब files, और write() का comments खोना। Typed या nested config के लिए tomllib के साथ TOML चुनें।
INI 文件由节、键、值和注释组成。configparser 把每个值都读成字符串 — 请用带类型的读取方法,把共享值放进 [DEFAULT],有意识地选择插值方式。注意这些陷阱:值中的 %、行内注释、静默缺失的文件,以及 write() 丢失注释。需要带类型或嵌套的配置时,请用 tomllib 读 TOML。