Python技术迷

munch,一个超好用的 Python 库!

接口返回的 JSON 一层套一层,代码里全是 resp["data"]["user"]["profile"]["name"]。

这玩意我第一眼就不太想看。不是因为 Python 的 dict 不好,而是这种代码一多,排查字段缺失的时候,眼睛会先废掉。

比如接口日志里是这样的:

resp = {
"code": 0,
"data": {
"user": {
"id": 9527,
"profile": {
"name": "dong",
"city": "hangzhou"
            }
        },
"order": {
"pay": {
"amount": 12800,
"currency": "CNY"
            }
        }
    }
}

正常写法一般是:

name = resp["data"]["user"]["profile"]["name"]
amount = resp["data"]["order"]["pay"]["amount"]

能跑,没毛病。

但业务代码里出现十几次这种东西,就开始难受了。字段路径长,括号多,改字段的时候容易漏,线上报一个 KeyError,你还得从日志里一层层翻。

这时候可以看一下 munch。

它干的事情很简单:把 dict 变成可以用点号访问的对象。官方 README 里也说得很直白,Munch 是一个支持属性访问的 dictionary,本身还是 dict 的子类,所以原来 dict 的方法照样能用。

安装也没什么花活:

pip install munch

用起来大概是这样:

from munch import munchify

body = munchify(resp)

name = body.data.user.profile.name
amount = body.data.order.pay.amount

print(name, amount)

这段代码我愿意在项目里留着。

不是为了炫技,是因为它读起来更像业务。尤其是处理配置、接口返回、任务上下文这种多层结构时,body.data.user.profile.name 比一堆中括号舒服很多。

不过我一般不会在最外层接口里直接乱用。我的习惯是先做一次收口,把接口数据转成内部对象,再往下传。

比如支付回调,外部来的字段可能很散:

from munch import munchify

defparse_pay_callback(raw: dict):
    event = munchify(raw)

return munchify({
"trade_no": event.data.trade.no,
"user_id": event.data.buyer.uid,
"paid_amount": int(event.data.payment.amount),
"channel": event.data.payment.channel,
"paid_at": event.data.payment.finished_at,
    })

callback = {
"data": {
"trade": {"no": "T20260629001"},
"buyer": {"uid": 7788},
"payment": {
"amount": "16800",
"channel": "wechat",
"finished_at": "2026-06-29 10:21:03"
        }
    }
}

pay = parse_pay_callback(callback)

print(pay.trade_no)
print(pay.paid_amount)

这里有个细节。

我没有让全项目都去依赖原始回调结构。外部字段怎么套,是外部的事。进到我们系统以后,就变成 pay.trade_no、pay.paid_amount 这种内部字段。

这比到处写:

raw["data"]["payment"]["amount"]

要稳一点。

再说一个我觉得更实用的场景:配置文件。

很多 Python 小工具,配置从 YAML、JSON、环境变量拼出来,最后变成一个大 dict。然后代码里到处都是:

timeout = config["http"]["timeout"]
workers = config["job"]["workers"]

我不太喜欢这种写法,尤其是脚本跑在线上机器上,一旦少字段,异常位置经常不够直观。

可以这么写:

from munch import DefaultMunch

defload_runtime_config(raw: dict):
    missing = object()
    cfg = DefaultMunch.fromDict(raw, missing)

    required = {
"http.timeout": cfg.http.timeout,
"http.base_url": cfg.http.base_url,
"job.workers": cfg.job.workers,
    }

    lost = [k for k, v in required.items() if v is missing]
if lost:
raise RuntimeError(f"config missing: {', '.join(lost)}")

return cfg

config = load_runtime_config({
"http": {
"base_url": "https://api.example.com",
"timeout": 3
    },
"job": {
"workers": 4
    }
})

print(config.http.timeout)
print(config.job.workers)

DefaultMunch 这个东西挺适合做配置校验。字段不存在时,不是马上炸,而是返回一个默认值。你可以自己统一判断缺了哪些字段。

我一般不建议在业务深处才等它炸。配置启动时就该查完,不然后面任务跑了一半才发现少一个字段,日志看着更烦。

还有一个小坑,得提前说。

munch 虽然能点号访问,但不是所有 key 都适合点号访问。

比如这种:

bad = munchify({
"order-id": "A001",
"class": "vip",
"items": [1, 2, 3]
})

order-id 这种带横杠的 key,不能写成 bad.order-id。class 是 Python 关键字,也别这么玩。items 更容易误会,因为 dict 本身就有 items() 方法。

这种字段老老实实用中括号:

print(bad["order-id"])
print(bad["class"])
print(bad["items"])

所以我的判断很简单:

内部字段,命名干净,适合用 munch。

外部接口,字段奇怪,先清洗,再转。

别为了点号访问,把所有 dict 都换成 munch。那就过了。

munch 还有一个舒服的地方,它能转回普通 dict。这个在写入 Redis、发 MQ、做 JSON 序列化时挺重要。官方文档里也提到,Munch 可以递归地在普通 dict 和 Munch 之间转换,并且支持 JSON/YAML 序列化相关用法。

比如:

from munch import munchify, unmunchify
import json

task = munchify({
"name": "sync_user_tag",
"retry": {
"max": 3,
"interval": 5
    }
})

task.retry.max += 1

payload = unmunchify(task)
message = json.dumps(payload, ensure_ascii=False)

print(message)

这里我会明确转回 dict 再序列化。虽然直接序列化很多时候也能跑,但边界处别偷懒。进系统可以舒服点,出系统最好规矩点。

面试官问 munch,一般不是想听你背 API。

他大概率想看你知不知道这种库适合放在哪。

我的答案会是:它适合处理多层 dict,尤其是配置、接口返回、任务上下文这些读多写少的数据结构。它能让代码更短、更像业务字段。但别拿它替代所有 dict,字段名不规范、跟 dict 方法冲突、需要严格类型约束的地方,就要收着点。

小工具可以直接上。

生产项目里,最好在入口层做一次转换和清洗,不要让外部脏字段一路钻到业务深处。

这就是我对 munch 的看法:不是大杀器,但很顺手。顺手到你写过几次嵌套 JSON 解析以后,就会知道它省的不是几行代码,是少看几眼括号。