selfdb:把整个Python程序变成可查询数据库的魔法库

> selfdb 是一个将 Python 程序运行时状态自动暴露为 SQL 可查询数据库的开发工具,让你用 SQL 直接查询内存中的对象、函数调用和变量。 ## 痛点:调试和观测 Python 程序为何如此痛苦 在开发 Python 应用时,我们经常面临这样的困境: - **调试复杂状态**:当程序运行到某个分支时,你想知道当前所有模块的全局变量、某个对象的属性、甚至最近几次函数调用的参数——但只能靠 `print` 或 `pdb` 手动断点,效率低下。 - **事后分析**:程序崩溃后,你想复盘“当时发生了什么”,但日志不够详细,回放困难。 - **性能分析**:你想知道某个函数被调用了多少次、平均耗时多少,通常需要引入 `cProfile` 或手动埋点。 - **动态数据探索**:你希望像操作数据库一样,用 `SELECT` 和 `JOIN` 去探索程序内部的数据关系,但 Python 对象图是任意复杂的,没有统一查询接口。 传统方案要么是侵入式埋点(污染业务代码),要么是外部调试器(无法访问运行时上下文),要么是日志分析(丢失对象结构)。`selfdb` 提出一个全新的思路:**把整个 Python 进程变成一台数据库服务器**,你可以在程序运行的同时,用 SQL 查询它的内部状态。 ## selfdb 是什么 `selfdb` 是一个纯 Python 库,它通过 `sys.settrace` 钩子自动捕获程序执行过程中的所有帧(frame)、局部变量、全局变量、函数调用和返回值,并将它们组织成一张张关系表。然后,它启动一个内置的 SQLite 虚拟数据库(通过 `sqlite3` 的虚拟表机制),让外部客户端(如 `sqlite3` 命令行、Python `sqlite3` 模块、甚至其他语言的 SQLite 驱动)直接连接并查询这些表。 简单说:**你的程序运行到一半,你可以用 SQL 去查询它现在的内存状态。** ## 快速上手 ### 安装 bash pip install selfdb ### 最小示例:查询全局变量 python import selfdb import threading import time # 启动 selfdb,监听在本地 12345 端口(默认) selfdb.start(port=12345) # 程序正常运行 counter = 0 def worker(): global counter for i in range(100): counter += 1 time.sleep(0.01) threads = [threading.Thread(target=worker) for _ in range(5)] for t in threads: t.start() # 保持程序运行,等待外部查询 selfdb.wait() # 阻塞,直到 Ctrl+C 在另一个终端,用任意 SQLite 客户端连接: bash sqlite3 selfdb.db 或者通过 Python 连接: python import sqlite3 conn = sqlite3.connect('selfdb.db') # 查看有哪些表 print(conn.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall()) # 查询全局变量 counter 的当前值 rows = conn.execute("SELECT * FROM globals WHERE name='counter'").fetchall() print(rows) 你会发现 `globals` 表中有当前所有全局变量的名称、类型和值。更神奇的是,你可以实时查询——每次 `SELECT` 都会获取最新状态。 ### 查询函数调用历史 selfdb 会自动记录每次函数调用的参数和返回值,存到 `calls` 表: sql SELECT function_name, args, return_value, duration_ms FROM calls ORDER BY timestamp DESC LIMIT 10; 你可以看到最近 10 次函数调用的完整记录,包括参数值、返回值和耗时。这对于调试性能瓶颈和异常流程非常有用。 ### 查询局部变量和帧信息 selfdb 还记录了每个活动线程的调用栈(stack frames)。你可以查询 `frames` 表: sql SELECT thread_id, filename, lineno, function, locals_json FROM frames WHERE thread_id = 5; `locals_json` 是该帧的所有局部变量序列化后的 JSON 字符串。这样,你可以看到某个线程当前执行到哪一行,局部变量是什么。 ## 核心亮点:深入技术细节 ### 1. 基于 `sys.settrace` 的零侵入捕获 selfdb 利用 Python 的追踪钩子(`sys.settrace`)在每次函数调用、行执行、返回时触发回调。它不修改你的源代码,也不要求你显式地记录任何数据。你只需要 `import selfdb; selfdb.start()`,剩下的自动完成。 需要注意的是,`sys.settrace` 本身会带来一定的性能开销(通常 20%-50%),所以 selfdb 更适合开发调试和测试环境,不适合生产环境长期开启。但它的设计允许你通过环境变量或命令行参数动态开启/关闭,例如: bash SELFDB_ENABLED=1 python my_app.py ### 2. 虚拟表:SQLite 与 Python 对象的桥梁 selfdb 没有把数据复制到 SQLite 文件,而是通过 SQLite 的虚拟表机制(`sqlite3` 模块中的 `create_module`)动态生成查询结果。每次你执行 `SELECT`,它都会实时从 Python 的内存中提取数据并返回。这意味着: - **数据永远是最新的**:你看到的就是程序此刻的状态。 - **无持久化开销**:不需要写文件,不占用磁盘。 - **支持复杂 SQL**:你可以对查询结果进行 `WHERE`、`JOIN`、`GROUP BY` 等操作,因为虚拟表的行为和普通表一样。 ### 3. 支持多线程和异步 selfdb 能正确区分不同线程的帧和局部变量。对于 `asyncio` 协程,它也能捕获事件循环中的任务状态(通过钩住 `asyncio.Task` 的创建和切换)。 ### 4. 安全性和访问控制 默认只监听 127.0.0.1,避免远程访问风险。你可以通过 `selfdb.start(host='0.0.0.0')` 开启远程,但需要自己加认证(建议通过 SSH 隧道)。 ### 5. 可扩展的表结构 除了内置的 `globals`、`locals`、`frames`、`calls` 表,selfdb 还提供 `objects` 表,让你按类名查询所有实例: sql SELECT class_name, id, repr, attributes_json FROM objects WHERE class_name = 'MyClass'; 这会遍历 `gc.get_objects()` 找到所有 `MyClass` 的实例,并序列化它们的属性。 ## 适用场景 1. **复杂算法调试**:比如图算法、动态规划,你想在每一步检查中间状态,用 SQL 比用 `print` 更灵活。 2. **Web 服务开发**:在 Flask/Django 开发中,你可以开启 selfdb,然后通过 SQL 查询当前请求的上下文、数据库连接池状态、缓存命中率等。 3. **数据分析脚本**:你在 Jupyter 中运行长任务,想中途查看某个变量的分布,直接用 SQL 查询。 4. **教学演示**:向学生展示 Python 程序内部执行过程,用 SQL 查询帧和变量,直观且有趣。 5. **自动化测试**:在测试框架中,你可以用 selfdb 断言某个函数被调用了多少次、参数是否正确,而不用 mock。 ## 与其他工具对比 | 工具 | 定位 | 与 selfdb 的区别 | |------|------|------------------| | `pdb`/`ipdb` | 交互式调试器 | 需要手动打断点,只能查看当前帧,不能跨线程/跨时间查询。selfdb 提供全局、实时的 SQL 接口。 | | `py-spy` | 采样性能分析器 | 只给出 CPU 火焰图,无法查询变量值。selfdb 能给出具体数据。 | | `tracemalloc` | 内存跟踪 | 只关注内存分配,不提供业务对象状态。 | | `logging` | 日志记录 | 需要手动埋点,且日志是文本流,无法结构化查询。selfdb 自动捕获所有调用和变量。 | | `django-debug-toolbar` | Web 调试面板 | 只适用于 Django,且只显示 SQL 查询和请求信息,不能查询任意 Python 对象。 | | `ipython` 的 `%debug` | 事后调试 | 只能在异常后进入,不能实时查询。 | selfdb 的核心差异化在于:**它把调试从“断点思维”转变为“数据库思维”**。你不再需要关心“停在哪一行”,而是可以随时问“现在全局有哪些对象?它们之间的关系是什么?” ## 局限性与注意事项 - **性能开销**:开启追踪后,程序运行速度会明显下降,不适合生产环境。 - **序列化限制**:某些对象(如生成器、socket、线程锁)无法被序列化为 JSON,selfdb 会用其 `repr` 字符串代替。 - **安全性**:暴露了程序内部所有数据,切勿在不可信网络下开启远程连接。 - **Python 版本**:目前支持 CPython 3.7+,不兼容 PyPy 或 Jython(因为 `sys.settrace` 行为差异)。 ## 总结 selfdb 是一个极具创意的开发工具,它模糊了“程序运行”和“数据库查询”的边界。虽然它可能不会成为生产环境中的常驻工具,但在开发、调试、测试和教学场景中,它提供了一种全新的、高效的交互方式。如果你厌倦了 `print` 和断点调试,不妨试试用 SQL 来“审视”你的程序。 项目链接:https://github.com/fzakaria/selfdb
查看工具