码农高天推出Python插桩库dowhen

不改源代码,我也能给你加点功能! — 码农高天

码农高天

开发者无需修改源代码或维护fork分支,利用系统监测机制即可实现近乎零开销的热补丁与行级逻辑注入。

AI-assisted summary of 不改源代码,我也能给你加点功能! — original source. Check the original for full context.

Source published:

Key ideas

  • 码农高天发布轻量级开源插桩库 dowhen 0.1.0 版本,开发者无需改动源代码即可在特定代码行前注入回调或打热补丁(monkey patch)。 (0:00)
  • dowhen 提供了绝对行号、相对行号(如 +11)以及源码文本匹配等多维度定位机制,并支持组合校验以防第三方库更新导致补丁错位。 (1:45)
  • 功能不仅限于变量读写,dowhen 还支持控制流跳转(goto)跳过大循环、触发交互式 PDB 断点(bp)以及链式组合控制。 (3:40)
  • 由于底层基于 Python 的 System Monitoring 机制实现,dowhen 的性能损耗(overhead)极低,接近直接修改源代码的运行效率。 (3:40)

Detailed notes

## Background 该视频由 YouTube 频道“码农高天”于 2025 年 6 月 27 日发布。视频简介说明,本期内容介绍了作者近期开发的一个名为 dowhen 的 Python 库,用于在不便修改源代码的情况下,便捷地在目标代码行前插入回调以进行调试或 monkey patch。

## TL;DR 在调试或扩展 Python 标准库与第三方代码时,直接修改源码往往不可行,而使用断点调试器在频繁调用场景下又过于繁琐。为此,码农高天开发了轻量级插桩工具 dowhen,允许开发者通过链式声明在运行时读取或篡改局部变量。该库支持绝对行号、相对行号及文本匹配等鲁棒定位方案,并提供了跳过耗时逻辑的 goto、交互式断点 bp 以及上下文管理器等高级控制功能。得益于底层的系统监测(System Monitoring)机制,dowhen 拥有极低的运行时开销,非常适合测试报告转储或无侵入式代码扩展。

## Atmosphere & style 整段视频洋溢着务实、清晰的工程师探索气息,高天以标志性的平实口吻娓娓道来,从开发者日常最切身的痛点切入。讲解节奏紧凑而直奔主题,通过现场运行的交互式代码片段层层推进,自然流露出对简洁优雅 API 设计的自豪感。整体氛围轻松严谨且极具启发性,既有对技术实现细节的自信传授,又带有一种开源创作者真诚邀请社区试用与共建的热忱。

## Bulletin - [00:00](https://www.youtube.com/watch?v=yoRgehrLrmc&t=0s) 码农高天发布轻量级开源插桩库 dowhen 0.1.0 版本,开发者无需改动源代码即可在特定代码行前注入回调或打热补丁(monkey patch)。 - [01:45](https://www.youtube.com/watch?v=yoRgehrLrmc&t=105s) dowhen 提供了绝对行号、相对行号(如 +11)以及源码文本匹配等多维度定位机制,并支持组合校验以防第三方库更新导致补丁错位。 - [03:40](https://www.youtube.com/watch?v=yoRgehrLrmc&t=220s) 功能不仅限于变量读写,dowhen 还支持控制流跳转(goto)跳过大循环、触发交互式 PDB 断点(bp)以及链式组合控制。 - [03:40](https://www.youtube.com/watch?v=yoRgehrLrmc&t=220s) 由于底层基于 Python 的 System Monitoring 机制实现,dowhen 的性能损耗(overhead)极低,接近直接修改源代码的运行效率。

## Key takeaways - [00:00](https://www.youtube.com/watch?v=yoRgehrLrmc&t=0s) 在无法修改源码(如 Python 标准库)且函数被高频调用的场景下,使用传统断点调试非常繁琐,开发者核心诉求是精准获取或改写特定行的内部状态。 - [00:00](https://www.youtube.com/watch?v=yoRgehrLrmc&t=0s) dowhen 通过简洁的声明式语法实现运行时代码插桩与热补丁,一行代码即可实现对标准库内部变量的读取与打印。 - [00:00](https://www.youtube.com/watch?v=yoRgehrLrmc&t=0s) 除了读取状态,在 do 中传入赋值逻辑即可直接篡改目标函数的局部变量,强行改变函数运行路径与返回结果。 - [00:00](https://www.youtube.com/watch?v=yoRgehrLrmc&t=0s) API 语法设计灵活,when(...).do(...) 与 do(...).when(...) 等价,调用顺序可依据自然语义自由组合。 - [01:45](https://www.youtube.com/watch?v=yoRgehrLrmc&t=105s) 为避免绝对行号因文件变动而失效,dowhen 支持基于函数起始位置的相对行号定位(如 +11)。 - [01:45](https://www.youtube.com/watch?v=yoRgehrLrmc&t=105s) 支持代码文本或前缀片段匹配,由框架自动搜索对应行,提高插桩定位的鲁棒性。 - [01:45](https://www.youtube.com/watch?v=yoRgehrLrmc&t=105s) 支持相对行号与文本特征的组合校验,若库版本升级导致两者对齐失败,程序会在启动时主动抛出异常报错。 - [01:45](https://www.youtube.com/watch?v=yoRgehrLrmc&t=105s) 在执行注入时,dowhen 能根据回调函数的形参名称自动映射并注入运行时对应的局部变量。 - [01:45](https://www.youtube.com/watch?v=yoRgehrLrmc&t=105s) 回调函数可通过返回字典选择性覆写局部变量,实现条件化命中并保留其他调用场景的原生逻辑。 - [03:40](https://www.youtube.com/watch?v=yoRgehrLrmc&t=220s) 提供 goto 控制流干预功能,可在遇到耗时的大循环时直接跳过循环体并跳到后续逻辑执行。 - [03:40](https://www.youtube.com/watch?v=yoRgehrLrmc&t=220s) 内置 bp 指令,命中指定代码行时可自动中断并唤起 Python 的 pdb 交互式调试器。 - [03:40](https://www.youtube.com/watch?v=yoRgehrLrmc&t=220s) 支持链式组合调用,例如可以先执行 goto 跳过代码段,紧接着串联 do 篡改变量或返回值。 - [03:40](https://www.youtube.com/watch?v=yoRgehrLrmc&t=220s) 插桩配置会返回 handler 对象,支持手动 enable/disable,并原生兼容 with 上下文管理器实现自动清理。 - [03:40](https://www.youtube.com/watch?v=yoRgehrLrmc&t=220s) 除本地调试外,该库适用于单元测试失败自动转储(dump report)等扩展场景,避免了为了打补丁而维护专属 fork 的高昂成本。 - [03:40](https://www.youtube.com/watch?v=yoRgehrLrmc&t=220s) 基于 Python 提供的底层 System Monitoring 机制构建,运行时性能损耗极低,目前已正式发布 0.1.0 初始版本。

## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=yoRgehrLrmc&t=0s) 无法修改源代码时的痛点与 dowhen 的基本用法

在调试 Python 代码时,最直接的方法通常是在特定代码行前加一行 `print(x)`。但现实开发中经常会遇到“无法修改源代码”的场景,比如调用的函数位于 Python 标准库(如 `random.randrange`)。假设我们需要在标准库的第 302 行打印内部变量 `istart` 的值,常规方案通常是借助调试器(debugger)打断点。然而,如果该函数在运行期间被调用成百上千次,每次都停住或者配置复杂的 debugger 自定义命令,就会显得繁重又繁琐。实际上开发者的核心诉求极其纯粹:

> “无非就是想在random.randrange这个函数运行到第302行的时候打印一下iStart嘛这个就是我们对需求的描述”

码农高天为此开发了一个轻量级库——`dowhen`。通过简洁的声明式语法,只需一行代码即可在运行时对已有代码进行插桩(instrumentation)或热补丁(monkey patch):

```python # 示例用法:在特定函数执行到指定行时执行打印或修改 when("random.randrange", 302).do('print("istart:", istart)') ```

运行这段代码即可精确输出 `istart` 的值为 10。除了“读”内部状态,“写”也同样简单:如果想强制使 `istart` 的值变为 2,只需在 `do(...)` 中将赋值语句传进去。随后连续生成 5 个 100 以内的随机数,输出结果只会是 0 或 1,因为函数内部的变量已经被提前替换。高天指出,这种在运行时修改已有函数和代码行为的手段通常被称为 monkey patch 或 instrumentation,而 `dowhen` 正是一个极易上手的专业插桩工具。此外,语法设计非常灵活,`when(...).do(...)` 和 `do(...).when(...)` 完全等价,调用顺序可以根据语意习惯自由调换。

[01:45](https://www.youtube.com/watch?v=yoRgehrLrmc&t=105s) 定位目标行(when)与复杂回调注入(do)

使用绝对行号(如 302 行)在文件被编辑时容易失效。高天解释道,标准库或第三方库在特定环境中通常是稳定的,但 `dowhen` 依然提供了多种鲁棒的行定位方案:

1. **相对行号**:使用 `+11` 表示函数内部起始位置向后的第 11 行,规避外部行数变动的影响。 2. **代码文本匹配**:直接传入代码内容(如 `if step is None:`)甚至其前缀片段,框架会自动识别该行。 3. **组合条件校验**:支持同时传入相对行号与文本特征(例如 `when("random.randrange", "+11", "if step is None")`)。如果库源码发生版本变更导致两者无法在同一行对齐(例如行号误写为 `+12`),程序在启动时会立即抛出异常并明确报错,避免打错补丁。

在动作执行(`do`)方面,除执行简单的 Python 字符串语句外,还支持传入复杂的自定义 Python 回调函数。`dowhen` 会自动检测该函数的入参名(如 `istart`),并将运行时对应的局部变量自动注入给该函数。回调函数可通过返回一个字典来决定是否覆写局部变量:

```python def my_callback(istart): if istart == 100: return {"istart": 2} return {} # 不做任何改动 ```

此时调用 `random.randrange(100)` 的结果被限制在 0 或 1,而调用 `random.randrange(99)` 时则会完全恢复标准库原本的逻辑。

```mermaid flowchart TD accTitle: dowhen 执行生命周期图 accDescr: 展示当目标函数触发预设条件时,dowhen 如何执行跳转、注入回调或触发断点。

subgraph Match["条件定位 (when)"] Target["目标函数触发"] --> Condition{"匹配行定位条件"} Condition -->|"绝对行号 / 相对行号 (+N) / 代码模式"| Trigger["命中插桩点"] end

subgraph Action["注入操作 (do)"] Trigger --> Decision{"选择执行逻辑"} Decision -->|"goto"| Jump["跳转跳过代码块 (如大循环)"] Decision -->|"bp"| PDB["进入交互式 PDB 断点"] Decision -->|"callback"| Exec["自动注入局部变量并按需修改"] end ```

[03:40](https://www.youtube.com/watch?v=yoRgehrLrmc&t=220s) 高阶控制:跳转、断点、生命周期管理与适用场景

除了注入执行语句外,`dowhen` 还支持直接干预函数的控制流: - **跳过特定代码段(goto)**:当遇到耗时的大循环(例如函数 `f` 内的 `for i in...`),可直接配置 `goto` 跳到 `return x` 行,直接跳过整个循环的执行。 - **触发断点(bp)**:直接配置 `bp` 即可在指定代码行触发进入 Python 的 `pdb` 交互式调试器。 - **链式组合调用**:支持多个指令链式串联。例如先执行 `goto("return x")`,紧接着执行 `do("x = 1")`,从而在跳过循环的同时篡改返回值。

所有组合配置都会返回一个独立的 handler 对象,便于对补丁的生命周期进行精细化控制: - 可以通过 `handler.disable()` 和 `handler.enable()` 随时关闭与重启补丁。 - 支持 Python 的上下文管理器协议(`with handler:`),代码块结束时插桩会自动清理并完全移除。

针对适用场景,高天澄清道,虽然 debug 是重要场景,但它绝不仅限于此。很多生产级项目(如高天参与的 `cooldp` / 内存转储与单元测试增强工具)需要在单元测试失败时自动转储报告(dump report),必须通过 monkey patch 来扩展既有函数。如果靠传统 fork 第三方库来修改代码并重新打包发布,会引入极大的维护成本与上游同步负担:

> “而Dewen有一个很大的优势是他用了System Monitoring也就是说他的overhead非常低他几乎可以达到和直接修改原代码相似的overhead”

借助底层的系统级监测机制,`dowhen` 能将所有的 instrumentation 集中统一管理,明确区分原生逻辑与扩展逻辑。该项目目前已发布 0.1.0 版本,高天呼吁社区开发者积极尝试并反馈 bug。

- **核心指令支持**:支持 `do(...)`、`goto(...)`、`bp()` 以及链式组合。 - **底层特性**:基于 Python 的 System Monitoring(系统监测)机制实现,运行时性能损耗(overhead)极低。 - **当前状态**:刚发布 0.1.0 初始版本,属于早期开源阶段。

## Notable quotes > [03:40](https://www.youtube.com/watch?v=yoRgehrLrmc&t=220s) Dewen有一个很大的优势是他用了System Monitoring也就是说他的overhead非常低他几乎可以达到和直接修改原代码相似的overhead — 码农高天

## Mentioned 码农高天, dowhen, Python, pdb, cooldp

## Video description 这期视频我来介绍一下我这几周一直在做的一个库,叫做dowhen。这个库可以让你非常方便地在某一行代码运行之前去插入一些callback,来帮助你调试或者monkeypatch你不方便修改的代码。

[Source](https://www.youtube.com/watch?v=yoRgehrLrmc)

中文简报

码农高天推出Python插桩库dowhen 开发者无需修改源代码或维护fork分支,利用系统监测机制即可实现近乎零开销的热补丁与行级逻辑注入。 在调试或扩展 Python 标准库与第三方代码时,直接修改源码往往不可行,而使用断点调试器在频繁调用场景下又过于繁琐。为此,码农高天开发了轻量级插桩工具 dowhen,允许开发者通过链式声明在运行时读取或篡改局部变量。该库支持绝对行号、相对行号及文本匹配等鲁棒定位方案,并提供了跳过耗时逻辑的 goto、交互式断点 bp 以及上下文管理器等高级控制功能。得益于底层的系统监测(System Monitoring)机制,dowhen 拥有极低的运行时开销,非常适合测试报告转储或无侵入式代码扩展。 码农高天发布轻量级开源插桩库 dowhen 0.1.0 版本,开发者无需改动源代码即可在特定代码行前注入回调或打热补丁(monkey patch)。 (0:00) dowhen 提供了绝对行号、相对行号(如 +11)以及源码文本匹配等多维度定位机制,并支持组合校验以防第三方库更新导致补丁错位。 (1:45) 功能不仅限于变量读写,dowhen 还支持控制流跳转(goto)跳过大循环、触发交互式 PDB 断点(bp)以及链式组合控制。 (3:40) 由于底层基于 Python 的 System Monitoring 机制实现,dowhen 的性能损耗(overhead)极低,接近直接修改源代码的运行效率。 (3:40)

Reading summary

码农高天推出了轻量级 Python 插桩工具 dowhen,旨在不修改源代码的前提下,通过声明式语法在运行时精准读写函数内部变量、控制程序流并管理补丁生命周期。 痛点与核心用法:运行时非侵入式插桩 (0:00) 在调用 Python 标准库或第三方库时,开发者常遇到无法直接修改源代码来添加 print 或修改内部变量的痛点。传统调试器在面对成百上千次高频调用时设置繁琐,而直接 fork 库又会带来过高的维护负担。高天开发的 dowhen 采用声明式语法,允许开发者通过一行代码在特定函数运行到指定行时注入操作。例如使用 when("random.randrange", 302).do('print("istart:", istart)') 即可打印内部变量;若在 do 中传入赋值语句,还能直接篡改局部变量的值,改变后续执行逻辑。dowhen 的 when 和 do 语法顺序支持自由互换。 定位机制与局部变量注入 (1:45) 为了避免因代码行数变更导致补丁失效,dowhen 提供了灵活的定位方式:支持绝对行号、以函数起始为基准的相对行号(如 +11),以及直接匹配代码文本或前缀片段。开发者还可以组合相对行号与文本特征(如 when("random.randrange", "+11", "if step is None")),当两者无法在同一行对齐时,程序会在启动时报错以防止打错补丁。 在执行动作方面,do 不仅支持执行简单的 Python 字符串语句,还支持传入自定义回调函数。dowhen 会自动读取回调函数的参数名称,将运行时的局部变量注入其中。回调函数可以通过返回字典来按需覆写局部变量;若返回空字典,则保持原有执行逻辑不变。 控制流干预与补丁生命周期管理 (3:40) dowhen 支持干预函数的控制流。通过 goto 指令可以跳过耗时的大循环直接跳转到返回行,通过 bp 则能触发进入交互式 pdb 调试器。这些指令支持链式串联,例如先使用 goto 跳过循环,再紧接着使用 do 修改返回值。 所有配置均会返回一个独立的 handler 对象。开发者可以通过 handler.disable() 和 handler.enable() 随时关闭或重新启用补丁,亦支持以 with handler 上下文管理器的形式运行,在代码块结束时自动清理并移除插桩。 底层机制与生产场景考量 (3:40) 高天指出,dowhen 虽适用于日常调试,但设计定位不仅限于此。在单元测试失败时自动转储报告(如 cooldp 项目)等生产场景中,通过 monkey patch 扩展既有逻辑是常见需求。高天表示,dowhen 基于底层 System Monitoring 机制构建,运行时的性能损耗极低,几乎接近直接修改源代码的开销,同时还能将所有插桩逻辑集中统一管理。 dowhen 当前已发布 0.1.0 初始版本,属于早期开源阶段,高天呼吁社区开发者进行测试并反馈问题。

Source quotes

[3:40] 码农高天: Dewen有一个很大的优势是他用了System Monitoring也就是说他的overhead非常低他几乎可以达到和直接修改原代码相似的overhead

Original transcript

[0:00] 我们看左边有个非常简单的函数假如我们现在要调试他我们想知道在return x这行之前x的值是多少这件事很简单对吧我们就打一个printx就可以了但是有的时候我们是没办法修改原代码的我们看这里我们使用了一个非常常见的函数rent range那这个函数是在python的标准库里的我们没有办法去修改它的原代码那假如我举个最简单的例子我想在302行这里打印一下这个istart是多少我们应该怎么做呢这个时候我们可以用debug对吧我们可以设置一个断点在这儿然后来看这个值是多少但是如果这个函数被运行了很多次我想每次都打印呢那有的debugger也支持自定移命令但是你会意识到对于如此简单的一个需求你需要做的工作是比较多的那我们想做的事情其实很简单无非就是想在random.randrange这个函数运行到第302行的时候打印一下iStart嘛这个就是我们对需求的描述想做到这件事其实非常简单,你只需要加上这行代码就行了。我们把下面的print删除来避免它干扰我们运行下车代码可以看到它就打印出来了istart的值10那除了毒的需求,有时候我们也有血的需求比如在这里我想让他不管输入是多少i*都是2那么需要做的事情就是把do里面这个statement给改掉然后我们来连着打5个100以内的随机数可以看到打出来结果不是0就是1因为我们在这个函数里面啊已经偷偷的把i start改成2了那这种在运行时去修改已经存在的函数和代码的行为的手段我们一般叫monkey patch啊或者叫instrumentation而今天我要介绍的就是do one这个酷是我自己写的它是一个非常简单易用的帮助你做 instrumentation的工具那我们看我们刚才写的内容是when do对不对那在使用的时候你可以把它俩完全掉个个你可以用对问这个运行结果是一模一样的可能你会想302行也太不靠谱了你怎么能用一个绝对行数呢万一这个文件被修改了他不就不对了吗第一 当你debug的时候 第三方库和标准库很有可能是稳定的第二除了绝对行数之外也有很多其他的方式来找到这行这一行是函数里的第11行我们就可以用+11来标记它效果还是一样的这样我们就用相对行数而不是绝对行数也就意味着这个文件其他部分造成的改动都与他无关当然数行数还是稍微有点蠢对吧我如果不想数行数呢我们看这行是 if so is none 对吧我们把这行代码粘贴过来他一样是工作的我们甚至不用把它全都粘过来这样一个前缀它还是可以识别的你还可以把两个条件放到一起那么这两个条件需要同时满足它才会运行这样如果原代码改变了这两个条件凑不到一行了比如我们这里把加11改成加12他就会在第四行直接报错告诉你找不着这个代码那说完了when呢我们来说说do如果我们只需要运行一些简单的 statement 这样就足够了但是如果我的逻辑复杂了呢我们这里可以自定义一个函数这个函数的input是istart注意这里会自动把这个I*变量给传到这个函数来这个函数可以返回一个字典这个字典代表你是否想要修改局部变量在这里如果I*是100我们就把I*变成2否则的话我们就什么都不改可以看到在Rand Range是100的时候,它的输出不是0,就是1那 range range 99就恢复正常了因为callback就不再修改istarter了好那除了Due之外他还能干什么呢我们看在fx里面有一个很大的loop我不想运行这个循环我想进来之后直接跳到return x那一行你就可以做一个当F运行到4I这一行的时候,GoTo到ReturnX。这个时候我们打印F0就是0整个full loop没有被运行当然你也可以用它直接来trigger一个断点BP命令但在运行程序的时候呢他就会在4i这一行进入pdb你还可以把多个命令像这样链式的串起来在函数F运行到4i的时候,先go to return x,然后做一个x等于1这样我们打印出来的F0它就是1了那DUIN组合出来的东西实际上是返回一个handler的我们可以通过disable跟enable来控制这个instrumentation是不是生效可以看到在我们做disable之后F0就恢复了正常的运行方式当然这个handler也可以被用作一个context在with statement之后这个handler就被完全移除了那我们这期视频实际上只介绍了非常非常基础的一些功能doin还有很多其他酷炫的功能啊在这里时间有限我们就不一一展开了感兴趣的小伙伴可以去看一下文档那肯定有人问说,Doin就是一个帮助debug的工具吗?不是debug当然是它非常重要的一个领域但是有的时候我们自己的程序是必须要去monkeypatch一些标准库或者 [5:00] 商户的比如说在库尔丹派里为了让 unit test 测试出错的时候可以自动 dump report我们实际上是要给他原来那个函数做逻辑补充的但是这种monkey patch做多了维护的成本就会变高包括有时候你需要一些第三方库但是你需要改变一点第三方库的逻辑那你有可能就需要缝一下第三方库把它修改了然后跟着你自己的库一起release那这样你就又涉及到了一个跟上游同步的问题而Dewen有一个很大的优势是他用了System Monitoring也就是说他的overhead非常低他几乎可以达到和直接修改原代码相似的overhead而使用DUEN你可以把所有的instrumentation放到一起维护你可以清晰的知道哪些东西是人家原来有的哪些东西是你自己加或者自己修改的那这个酷也是我这几周刚刚做出来的啊才发了0.1.0版本在这个阶段还是欢迎大家积极试用啊如果有什么问题或者发现了什么bug都欢迎提出来好那这期视频这里啊喜欢和点赞关注不要忘记一见三连哦