munch,一个超好用的 Python 库!
按你给的文风做了这版成稿,语气和节奏只拿来校准,内容是全新写的:
cfg["redis"]["host"]
这种代码我现在一眼就烦。
不是写不了,是看着累。接口回来的 JSON 一层套一层,业务里到处都是中括号,字段名还全是字符串,少写一个字母,运行时才炸。更别说你改个结构,IDE 也帮不上什么忙,补全没有,重构更别想。
后来我在一些配置脚本、数据清洗脚本里开始用 munch,这玩意儿不大,但真顺手。它干的事很简单:把字典变成“点号访问”的对象。你原来写 cfg["redis"]["host"],换成它以后就是:
from munch import Munch
cfg = Munch({
"redis": {
"host": "127.0.0.1",
"port": 6379
}
})
print(cfg.redis.host) # 127.0.0.1
print(cfg.redis.port) # 6379
第一眼你会觉得,不就是语法糖么。
对,确实是语法糖。但很多脚本类代码,差的就是这一口糖。尤其是配置多、字段深、数据来源杂的时候,代码可读性会立刻顺一截。
我平时最常用它的地方,不是写什么框架代码,而是处理接口返回、YAML 配置、临时排障脚本。因为这些地方的数据结构往往不稳定,但又懒得专门上 dataclass 或 pydantic。上太重,写太满,最后脚本还没跑完,人已经先烦了。
比如读一份配置文件,原来常见写法是这样的:
import yaml
with open("app.yaml", "r", encoding="utf-8") as f:
cfg = yaml.safe_load(f)
dsn = (
f"mysql+pymysql://{cfg['db']['user']}:{cfg['db']['password']}"
f"@{cfg['db']['host']}:{cfg['db']['port']}/{cfg['db']['name']}"
)
换成 munch 以后,至少没那么硌眼:
import yaml
from munch import munchify
with open("app.yaml", "r", encoding="utf-8") as f:
cfg = munchify(yaml.safe_load(f))
dsn = (
f"mysql+pymysql://{cfg.db.user}:{cfg.db.password}"
f"@{cfg.db.host}:{cfg.db.port}/{cfg.db.name}"
)
munchify 这个函数挺关键。它不是只把最外层字典转一下,而是递归处理,里面嵌套的 dict、list 也一起转。这个在吃接口响应时特别省事。
举个更像现场一点的例子。比如你查订单接口超时,先拿一份返回结果落本地分析:
from munch import munchify
import json
raw = '''
{
"code": 0,
"data": {
"order_id": "A20260324001",
"user": {
"id": 9527,
"name": "leo"
},
"items": [
{"sku": "IP15-256G", "count": 1, "price": 5999},
{"sku": "CASE-09", "count": 2, "price": 39}
]
}
}
'''
resp = munchify(json.loads(raw))
print(resp.data.order_id)
print(resp.data.user.name)
for item in resp.data.items:
print(item.sku, item.count, item.price)
这种代码的好处不是“高级”,而是你排查时脑子不用老在 ["xxx"] 上打断一下。字段层级深的时候,这种打断特别明显。
不过我也不建议把 munch 吹太满。它顺手归顺手,有几个地方我一般会提前留意。
第一个,字段名如果和 dict 自带方法撞了,要小心。比如你有个字段叫 items、keys,访问时可能会跟对象方法混在一起。这种场景我就不硬上点号,直接老老实实中括号。
from munch import Munch
data = Munch({"items": ["a", "b", "c"]})
print(data["items"]) # 稳一点
# print(data.items) # 这里就别自作聪明了
第二个,它不是类型校验工具。你别拿它当 pydantic 平替。接口少字段、字段类型不对,它不会像 schema 那样替你兜底。它做的是“访问顺手”,不是“数据治理”。
所以我自己的习惯是这样的:边界层先校验,进了脚本内部再用 munch 提高可读性。别一上来全链路都点号访问,最后线上一个字段缺失,排查时还得回头猜是哪层漏了。
比如做一个导出脚本,我会先把必要字段补默认值,再转 munch:
from munch import munchify
defnormalize(row: dict) -> dict:
return {
"task_id": row.get("task_id", ""),
"status": row.get("status", "UNKNOWN"),
"owner": row.get("owner") or {},
"cost": row.get("cost", 0)
}
rows = [
{"task_id": "T1001", "owner": {"name": "张工"}, "cost": 31},
{"task_id": "T1002", "status": "DONE", "cost": 18}
]
rows = [munchify(normalize(x)) for x in rows]
for row in rows:
print(row.task_id, row.status, row.owner.get("name", "-"), row.cost)
第三个,munch 很适合脚本、小工具、配置读取、原型代码;但核心业务模型要不要用,我一般会多想一步。尤其是多人协作项目,领域对象到底该长什么样,最好还是明确一点。该写类写类,该上校验上校验。别图一时顺手,把边界弄糊了。
安装也简单:
pip install munch
用法你记两个就够了:Munch() 和 munchify()。
前者适合你自己手动构造数据,后者适合把现有 dict、list 递归转掉。真正常用的,基本也就是这两个。
我现在碰到这几类代码,基本都会先考虑一下 munch:
配置文件读取; 接口 JSON 落地分析; 临时数据处理脚本; 排障时写的小段验证代码。
不是说不用它就写不下去。是用了以后,那种“字典套字典套列表”的黏手感会轻很多。代码看起来更像在处理数据,不像在和中括号较劲。
库很小,学的成本几乎没有。能不能长期留在你的项目里,看场景;但拿来治一治那些别扭的脚本代码,通常是够用的。