HMR - An Engine, a Tool, an Ecosystem.¶
一个 反应式编程 引擎 + 一个 热重载 框架。致力成为 Python 界的 Vite
HMR provides a pythonic, flexible, progressive-yet-intuitive reactive programming engine/framework, and on top of that, a fine-grained, on-demand hot-reload tool.
- 这个反应式框架实现了 push-pull reactivity:
Signal,Derived,Effect - 这个热重载工具提供了即插即用的 CLI 工具直接替代
python、uvicorn、mcp/fastmcp
由于 reactive programming 懂的人自然懂,不懂的人说明用不上,所以下面可能更多 demo 集中在热重载功能。
比如下面这是前段时间做的 mcp-hmr 的 demo
下面是更早录的 FastAPI 的例子,实现类似 uvicorn --reload 的重载,但是快得多,更环保节能:
由于当时还没发布 uvicorn-hmr
所以视频中看到的是有一个折叠了的 main.py 中的 start_server 函数,启动命令是 hmr main.py。现在不一样了。现在可以直接 uvicorn-hmr main:app 启动,不需要对代码有任何改动。可以说,任何可以被 uvicorn 正常启动的 ASGI application 都可以直接用 uvicorn-hmr 启动,零门槛享受热重载的开发体验。
为什么要有 HMR¶
说起来,我在杭州 实习 的时候,杭州的老板 看到我写的 Python 服务用
uvicorn启动时居然只有冷重载,感到十分惊讶。我们对 Python 生态的 DX 感到十分遗憾。这段经历在我内心种下了一个种子
传统的 冷重载 工具(比如 watchfiles CLI 或 uvicorn 的 --reload)本质上只是:检测文件变化,杀进程,从头重启进程。这对加载 ML 模型、建立数据库连接这类昂贵初始化来说是噩梦。
热重载 则不重启进程,而是在原进程上修修补补 ,智能地根据受改动的部分 按需重算:
- 直接修改的 python 模块会被重新执行(如果是被 import 了的模块)
- 这个模块声明的变量如果有变化,则会触发 “依赖这个变量” 的代码重算
- 一直传递下去,直到不再有受影响的代码仍未刷新的
甚至不仅保存 python 代码会触发这样的反应式细粒度热重载。如果你的代码 read 了某个文件,则改动那个文件(即使不是 .py 文件)也可以触发对应代码 rerun
这样神奇的功能都是通过反应式编程或者说 signal pattern 实现的,而不是静态分析。者带来了诸多好处,详见 下文。我另外也有一篇博客:为什么不用静态分析来实现 HMR / [csdn] / [dev.to] / [medium]
HMR 生态¶
| 包名 | 用途 |
|---|---|
| hmr | 反应式编程库和热重载核心实现 |
| hottest | 🚧 支持热重载的测试框架!想想 Vitest! |
| uvicorn-hmr | 为 ASGI 应用提供热重载的 Uvicorn 服务器 |
| mcp-hmr | 为 MCP / FastMCP 服务器启用热重载 |
| hmr-daemon | 后台守护进程,监控文件变化自动刷新模块 |
| fastapi-reloader | FastAPI 浏览器自动刷新中间件 |
双重身份¶
作为反应式引擎¶
HMR 底层是一套受 Svelte 和 SolidJS 启发的 push-pull 反应式框架:
from reactivity import signal, derived, effect
count = signal(100) # data source
@derived
def odd():
return count.get() % 2
@effect
def print_result():
print(f"{odd() = }") # effect reruns when dependencies change
# initial run prints: odd() = 0
count.set(1) # triggers effect, prints: odd() = 1
count.set(101) # odd() still 1, effect not rerun
count.update(lambda v: v + 1) # now odd() = 0, effect reruns and prints: odd() = 0
核心三元素:
- Signal:可观测的数据源,被订阅者们监听
- Derived:派生值,从其它
Signal/Derived计算而来,惰性求值且自动缓存 - Effect:副作用,当依赖变化时自动 rerun
这套设计的精妙在于"push-pull 混合":Signal 推送变化通知,Derived 被 Effect 按需拉取,既避免了不必要的重算,也不用轮询。加上 async_effect、async_derived 和 new_context() 隔离,你可以在 Python 里实现任何现代 JS 框架的反应式能力。
作为热重载工具¶
元编程也可以不 hacky —— HMR 完全基于非 monkey-patching 的 API,比如注册 import 钩子等,以最大化兼容性和防御性编程。其原理大致如下
- 通过
sys.meta_path拦截所有 import(不改你的代码) - 把模块的命名空间包装成反应式 proxy,每次
__getattr__都记录 “谁访问了哪个模块的哪个变量” - 另用
sys.addaudithook监控其它文件读取,每次open都记录 “谁访问了哪个文件” - 文件变化时,watchfiles 通知我们,只重新执行受影响的部分(如上所述)
我干了些什么骚操作¶
这一段是 AI 写的,包括这个小标题也是 AI 起的
变量级细粒度¶
大多数热重载工具是模块级的。我做到了变量级。你改了某个函数的实现,只有真正调用它的代码会重算。改了模块中你没用的常量?什么都不会发生。
push-pull 反应式¶
纯 push 会导致级联重算的浪费,纯 pull 要轮询。HMR 用"invalidation push + lazy recompute":Signal 推送变化,Derived 等到被需要时才重算,而且结果还能缓存。
绕过 CPython 限制¶
我曾给 CPython 提过 一个 issue:在类定义里访问全局变量时,如果 globals 是 dict 的子类(而不是纯 dict),就会出错。HMR 用 AST 变换优雅地绕过了这个坑,让函数级热重载能支持在函数里写 class。这个还挺复杂的,所以我写了一篇博客讲这个故事:[blog] / [csdn] / [juejin]
.pth 文件黑科技¶
hmr-daemon,会往 site-packages 扔个 .pth 文件,不改你一行代码,普通的 python script.py 启动也能在后台热重载。这是我最喜欢的"对 DX 的小关怀"。我曾经写过它的故事:[blog] / [csdn] / [juejin] / [medium]
用法¶
CLI¶
最直白的方式:
hmr main.py arg1 arg2
# or
hmr -m package.module
参数什么的跟 python 命令一模一样,只是现在文件改动会自动热重载。
uvicorn 集成¶
用 uvicorn-hmr 替代 uvicorn CLI,给你的 FastAPI / 任意 ASGI 应用加上真正的热重载体验:
uvicorn-hmr main:app
# or
uvicorn-hmr main:app --refresh # --refresh 还能自动刷浏览器
daemon thread¶
装 hmr-daemon,然后任意方式启动 Python(python script.py、进入 IPython REPL、运行其它 CLI 工具)都会自动支持热重载。通过 .pth 文件在启动时注入,对代码零改动。
MCP Server 集成¶
mcp-hmr 是一个 MCP 服务器,把 HMR 的文档和源码暴露给 Claude、Cursor 这些工具,让它们能直接查文档。
Comparisons¶
由于该项目的双重生态位,下面将分别与相关项目进行比较。
热重载工具¶
传统的冷重载工具如 watchfiles CLI 或 uvicorn --reload 只是检测到文件变化就重启整个进程,无法保留状态。
Tach 用静态分析 AST 来追踪模块依赖,做到了模块级重载和状态保留,但只能监控 .py 文件。比起来,HMR 在运行时追踪依赖,能做到变量级细粒度 —— 当你 from module import name 时,我能精确记录"谁访问了哪个模块的哪个变量"。如果 name 本身没变,就不触发重载。这比静态分析(它只知道"模块 A 导入了模块 B")能掌握更细的细节。
jurigged 和 reloadium 也实现了运行时追踪,但都依赖 monkey-patching,兼容性有限。HMR 用 sys.meta_path 钩子和自定义 ModuleType 来实现,完全避免了 monkey-patch 的坑。
另外,只有 HMR 能监控任意文件。你的代码读了某个 YAML、TOML、JSON 吗?改那些文件也会智能地重新执行依赖它的代码。这种全文件系统的追踪,加上变量级的细粒度,让 HMR 在实际项目中快得飞快。
反应式编程框架¶
框架内置反应式系统的,比如 Textual 和 Shiny for Python,通常只支持异步反应式。HMR 则同时支持同步和异步反应,而且完全框架无关。
marimo 和 IPyflow 这类交互式笔记本用静态分析来追踪单元格的依赖,有同样的局限性。RxPY 更多是个事件驱动框架,不是细粒度反应式引擎。
设计上,HMR 的 API 参考了 Svelte、SolidJS、Vue 这些现代前端框架。目标就是在 Python 里实现 Vite 那样的开发体验。
深入了解¶
想玩得更溜?去 官方文档 看 Signal、Derived、Effect 的高级用法,或者看 源码——代码写得自我说明,注释也很有料。一个 interactive 的 docs site 也正在建设中
For AI Assistants¶
还可以装个 hmr 文档的 MCP Server,让 Claude 或 Cursor 帮你读代码:
或者在你的 IDE 配置中添加这个 MCP 服务,就能让 AI 助手直接查 HMR 的文档和源码。
{
"mcpServers": {
"hmr-docs": {
"type": "http",
"url": "https://py3.online/hmr/mcp"
}
}
}
此外我们还有 llms.txt
其它资料
最初实现的最原型的版本(当时还是一个单文件脚本,甚至还没发布),录了个视频:[bilibili]
然后前段时间又录了个关于 uvicorn-hmr 细粒度 invoke 浏览器刷新的视频(这个功能现在还没发布):[bilibili]