为什么专业程序的设置都放在单独的文件里?今天你做出第一个”改配置不改代码”的程序
一、一个改到崩溃的真实场景
先讲一个你大概率亲历过的惨案。
假设你(或者AI)写了一个”每日打卡提醒程序”,里面写着提醒时间是早上8点。用了一周,你想改成7点半——于是你打开代码,找到那行提醒时间 = "08:00",改成"07:30",保存,重新运行。
又过了两周,你想改提醒语;再过一周,你想把打卡记录存到另一个文件夹……每次改动都得:打开代码 → 全文搜索找到那行 → 改 → 提心吊胆别改错别的地方 → 保存重跑。
问题出在哪?程序分成两种东西:逻辑(怎么判断、怎么提醒)和设置(几点提醒、存哪里、说什么话)。逻辑写好之后基本不动,设置却三天两头变。把两种东西混在一起,等于为了换个灯泡把整面墙拆了。
生活里早就有了答案:你家空调的”设置”不在空调的电路板上,而在遥控器和面板上;汽车的座椅记忆不在发动机里,而在设置菜单里。程序也一样——设置应该放在一个单独的文件里,程序每次启动先读它。
这个文件,就叫配置文件。
改配置不改代码,是”玩具程序”和”正经软件”的分水岭之一。你手机上每个App的”设置”页面,背后都对应着一个配置文件。今天,你就给自己的程序装上这道”设置面板”。
好消息是:今天没有全新的语法。配置文件用的就是上一篇的JSON存取(json.load / json.dump),加上你早就会的字典和文件判断。今天学的是一个套路——把学过的招组合成专业做法。
二、配置文件的套路:一个字典 + 一个JSON文件
先看配置文件长什么样。就是个普通的JSON文件,比如config.json:
{
"提醒时间": "07:30",
"提醒语": "该打卡啦!",
"记录文件": "打卡记录.csv",
"声音提醒": true
}
再看程序怎么用它。完整的”读配置”套路只有三步:
import json
# 第1步:打开配置文件,翻译成字典
with open("config.json", encoding="utf-8") as f:
配置 = json.load(f)
# 第2步:从字典里取设置项——第8篇的字典招全在这
print(提醒时间 := 配置["提醒时间"]) # 07:30
# 第3步:程序后面全部用变量/字典,不再出现"写死的值"
print(f"每天{配置['提醒时间']}提醒:{配置['提醒语']}")
if 配置["声音提醒"]:
print("🔔 叮叮叮!")
看到没?套路的核心就一句话:把”写死在代码里的值”搬进JSON文件,程序启动时读进来。以后想改提醒时间?用记事本打开config.json改一下就行,代码一个字都不用碰——就像调空调温度不用拆外机。
反过来,程序要把设置存回去(比如用户在程序里改了设置),就是上一篇的原样招式:
配置["提醒时间"] = "06:45" # 改字典
with open("config.json", "w", encoding="utf-8") as f:
json.dump(配置, f, ensure_ascii=False, indent=2) # 整个存回去
一读一写,配置文件管理的骨架就完了。但直接拿这套骨架上路,会撞上一个大坑——第一次运行时,config.json根本不存在。程序一启动就FileNotFoundError崩给你看。这就引出今天的主角套路。
三、核心套路:没有配置文件?自动创建默认配置
专业程序第一次启动时,如果没找到配置文件,不会崩,而是自动生成一份默认配置,顺便告诉你:”配置文件已创建,改完再运行。”下次启动,就用你改过的了。
这个套路叫”默认配置 + 自动创建“,写出来长这样:
import json
import os
配置文件名 = "config.json"
# 默认配置:程序出厂设置
默认配置 = {
"提醒时间": "08:00",
"提醒语": "该打卡啦!",
"记录文件": "打卡记录.csv",
"声音提醒": True
}
if os.path.exists(配置文件名):
# 已经有配置文件 -> 读出来用
with open(配置文件名, encoding="utf-8") as f:
配置 = json.load(f)
print("配置已加载。")
else:
# 第一次运行 -> 创建默认配置
配置 = 默认配置
with open(配置文件名, "w", encoding="utf-8") as f:
json.dump(配置, f, ensure_ascii=False, indent=2)
print(f"第一次运行,已创建默认配置文件 {配置文件名},你可以打开修改后再运行。")
print(f"当前设置:每天 {配置['提醒时间']} 提醒——{配置['提醒语']}")
今天唯一的”新面孔”是os.path.exists(文件名)——问操作系统”这个文件存在吗?”,返回True或False。就这一个函数,配一个if/else,三分钟消化。
(为什么用os.path.exists而不是try/except?两种都行,第10篇的try/except也能接住FileNotFoundError。用os.path.exists读起来更直白——”文件在吗?在就读,不在就建”,思路一眼看穿。跟AI说需求时两种都可以,看你顺口。)
跑一遍这个程序感受一下:第一次运行,它自动给你生成一份config.json;你打开把"提醒时间"改成"07:30"存盘;再运行,它就按7点半来了。改设置,全程没碰代码。这就是”正经软件”的手感。
四、进一阶:配置项”缺胳膊少腿”也不崩
再设想一个场景:你给朋友用了这个程序,朋友打开config.json手滑删掉了一行,或者你程序升级加了个新设置项”天气提醒”,而朋友的配置文件还是旧版、没这项——程序一读配置["天气提醒"],KeyError,崩了。
专业做法是”补全“:拿用户配置和默认配置逐项对比,缺哪项就用默认值补上。一个循环搞定:
# 接上一节:读出用户的 配置 之后,逐项检查
for 键, 默认值 in 默认配置.items():
if 键 not in 配置:
配置[键] = 默认值 # 缺了就补上默认值
print(f"配置缺了「{键}」,已自动补上默认值:{默认值}")
# 补完存回去,下次就齐了
with open(配置文件名, "w", encoding="utf-8") as f:
json.dump(配置, f, ensure_ascii=False, indent=2)
这个套路你以后会反复用,值得起个名字记下来——”默认配置兜底“:不管用户的配置文件残缺成什么样,程序永远有一份出厂设置托底,最多降级,绝不崩溃。你今天装的每个正经软件都是这么干的,只是你今天第一次自己写出来。
顺便送一个更简洁的写法——字典的setdefault(”设默认值”):
for 键, 默认值 in 默认配置.items():
配置.setdefault(键, 默认值) # 有则不动,无则补默认值
一行顶四行,效果一样。看不懂没关系,把两段都丢给AI说”帮我解释setdefault和if补全是不是一回事”,它会给你讲清楚。
五、实战:完整的”打卡提醒配置管理器”
把今天所有招揉成一个完整的小工具:能加载配置、改配置、保存配置、重置回出厂设置。这已经是一个正经软件的”设置模块”了:
import json
import os
配置文件名 = "config.json"
默认配置 = {
"提醒时间": "08:00",
"提醒语": "该打卡啦!",
"记录文件": "打卡记录.csv",
"声音提醒": True
}
def 读配置():
"""加载配置:有文件就读+补全,没文件就创建默认的。永远返回一个完整的字典。"""
if os.path.exists(配置文件名):
with open(配置文件名, encoding="utf-8") as f:
配置 = json.load(f)
# 默认配置兜底:缺哪项补哪项
新增 = False
for 键, 默认值 in 默认配置.items():
if 键 not in 配置:
配置[键] = 默认值
新增 = True
if 新增:
存配置(配置)
else:
配置 = 默认配置.copy() # copy一份,免得改字典时把出厂设置也改了
存配置(配置)
return 配置
def 存配置(配置):
"""把配置字典存回JSON文件,中文正常、带缩进。"""
with open(配置文件名, "w", encoding="utf-8") as f:
json.dump(配置, f, ensure_ascii=False, indent=2)
def 显示配置(配置):
print("——— 当前设置 ———")
for 键, 值 in 配置.items():
print(f" {键}:{值}")
print("—————————————")
def 修改配置(配置):
显示配置(配置)
键 = input("要改哪一项?(直接回车取消)").strip()
if 键 == "":
return
if 键 not in 配置:
print("没有这一项哦。")
return
新值 = input(f"把「{键}」改成:").strip()
# 类型跟随原值:原来是布尔就转布尔,原来是数字就转数字
if isinstance(配置[键], bool):
配置[键] = 新值 in ("True", "true", "是", "1")
elif isinstance(配置[键], int):
配置[键] = int(新值)
else:
配置[键] = 新值
存配置(配置)
print("已保存。")
def 重置配置():
存配置(默认配置.copy())
print("已恢复出厂设置。")
# ---- 主流程 ----
配置 = 读配置()
while True:
print("n1.查看设置 2.修改设置 3.恢复出厂 4.退出")
选择 = input("选:").strip()
if 选择 == "1":
显示配置(配置)
elif 选择 == "2":
修改配置(配置)
elif 选择 == "3":
重置配置()
配置 = 读配置()
elif 选择 == "4":
break
跑一下你会发现:第一次运行自动出配置文件;进”修改设置”改个提醒语,退出再进来还在(因为存回文件了);把config.json删掉再运行,它又自动长出来。这个程序已经不怕你折腾了——这就是配置文件给程序上的”保险”。
把整套代码丢给AI,说一句”帮我加一项’每周总结日’的配置,周几生成周报”,你会发现改动极其干净:默认配置加一项、代码里用一项,完事。配置和逻辑分家之后,程序的每处改动都变小了——这就是这套套路真正的回报。
六、四个必踩的坑(先打疫苗)
坑1:config.json不存在,程序直接崩。
就是第二节那个FileNotFoundError。疫苗:永远走”默认配置 + 自动创建”套路,别假设文件一定在。第一次运行的体验很重要——新手用户可不知道要先手动建配置文件。
坑2:改配置时忘了存回文件,改了等于没改。
字典改完只在内存里,程序一退出就没了。疫苗:改完立刻json.dump存回去。判断标准很简单——”我这个改动,明天启动还在吗?”不在,就是忘了存。
坑3:json.dump忘写ensure_ascii=False和encoding="utf-8"。
老朋友了,上一篇刚打过疫苗,今天再强调一次:配置里有中文(提醒语多半有),这两样缺一个,轻则满屏u63d0u9192天书,重则UnicodeDecodeError。当肌肉记忆。
坑4:默认配置字典被程序改动”污染”。
看实战代码里那句默认配置.copy()——如果不copy,程序里改配置["提醒时间"]时,其实改的就是默认配置本身(Python里字典赋值传的是”引用”,两个变量指向同一个字典)。结果”恢复出厂设置”恢复出来的还是你改过的值。疫苗:把默认配置当”模板”用之前,先.copy()一份。这个坑非常隐蔽,今天踩一次,以后少踩十年。
七、跟AI协作的正确姿势(配置特供)
配置文件这种”套路活”,正是AI最擅长一次写对的。三个要点:
- 需求里说清”配置项清单”。”帮我写一个打卡提醒程序,配置放config.json,配置项有:提醒时间、提醒语、记录文件、声音提醒(布尔)。第一次运行自动创建默认配置,配置缺项自动补默认值。”——把配置项列全,AI连兜底逻辑都给你写好。说清配置项,AI就不猜
- 让它解释”读配置”函数。AI给你写完
读配置(),别客气,追问一句:”逐行解释这个函数,特别是copy()和补全那段。”今天的新套路就藏在那几行里,问透了就是你的 - 改动配置项时整段重说。加新配置项时,把完整需求再说一遍(”配置项增加’每周总结日’,默认周日”),别只说”加个总结日”——AI可能只改一半(加了默认配置,忘了在补全循环里兜住)。让它”改完自查:新配置项在默认配置、补全、使用三处都接上了吗”
另外送你一个高频句式:”所有可变设置放进config.json,带默认值和自动创建,代码里不许出现写死的设置值“——这一句能让AI直接产出专业水准的程序骨架。
八、今日小练习
练习1:给老程序装配置
翻出你第22篇的简易记账本,把里面的写死值(比如记账文件名”账本.csv”、每月预算)搬进config.json,用”默认配置 + 自动创建”套路,改配置不用改代码。(提示:先列清单——哪些值是”设置”?然后建一个默认配置字典,把代码里那些写死的值换成配置["xxx"]。别忘了os.path.exists那三步。)
练习2:配置体检器
写一个工具:读任意一个config.json,对照一份”期望配置清单”,报告哪些项缺失、哪些项多出来、哪些项类型不对(该是布尔却是字符串),并能一键补齐缺失项。(提示:两个字典逐键对比——键 not in 配置查缺失;反过来遍历配置查多余;类型检查用isinstance(值, bool)。想把”期望清单”写灵活点?让它自己也是个JSON文件,你的工具就通用了。)
练习3:带界面的设置小面板
做一个”程序启动器”:启动时读config.json里的”上次打开的文件””主题(亮/暗)””字号”,用一句f-string把这些设置”应用”出来(打印就行,不用真做界面);支持--set 键 值的方式从命令行改配置。(提示:命令行参数用import sys,sys.argv就是参数列表——sys.argv[1]是--set、[2]是键、[3]是值。看不懂命令行参数?把这段提示原样丢给AI:”帮我实现这个,顺便解释sys.argv每一项是什么。”)
写在后面:你的程序开始”讲规矩”了
盘点今天:没有新语法,全是套路——设置和逻辑分家(配置文件该独立)、默认配置 + 自动创建(第一次运行不崩)、默认配置兜底(缺项不崩、.copy()防污染)、改完即存(改动明天还在)。
这四个套路,是从”会写代码”到”写出像样软件”的第一级台阶。你可能已经发现了:专业软件和你的小工具,语法上没有区别,区别全在这些”讲究”上。
下一篇我们换个方向——数据不总是干干净净的。CSV里的多余空格、大小写混乱、缺胳膊少腿的行……真实世界的数据是”脏”的,数据清洗就是给数据洗澡的第一步。
下篇预告
第二十九篇:数据清洗入门——拿到一份脏兮兮的表格数据,空格、错格式、缺值混在一起,怎么用字符串处理和条件筛选把它洗干净?下一篇见。
发表回复