Python技术迷

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 落地分析; 临时数据处理脚本; 排障时写的小段验证代码。

不是说不用它就写不下去。是用了以后,那种“字典套字典套列表”的黏手感会轻很多。代码看起来更像在处理数据,不像在和中括号较劲。

库很小,学的成本几乎没有。能不能长期留在你的项目里,看场景;但拿来治一治那些别扭的脚本代码,通常是够用的。