py_register('double_it', 'str(int(x)*2)', 1)
● ● ●
为什么做这个
DuckDB 最爽的一点是 LOAD 一个扩展就能获得新能力——LOAD httpfs 读 S3,LOAD spatial 做 GIS。但你没法在 SQL 里注册一个 Python 函数:
-- SQLite 可以SELECT python_function('hello');-- PostgreSQL 可以CREATE FUNCTION py_upper(text) RETURNS text LANGUAGE plpython3u AS $$ return args[0].upper() $$;-- DuckDB?没有。duckdb-python 就是填这个坑的——一个 DuckDB 社区扩展,把 CPython 嵌入 DuckDB 进程,让你在 SQL 里注册、调用 Python 函数。
● ● ●
一行注册,直接调用
LOAD './build/release/extension/python/python.duckdb_extension';SELECT py_register('double_it', 'str(int(x)*2)', 1); -- 返回 'OK'SELECT double_it('21'); -- 返回 '42' ✨SELECT double_it('99'); -- 返回 '198'SELECT py_call('double_it', '5'); -- 返回 '10'(兼容方式)注册后函数就是原生 DuckDB 标量函数,可以直接按名字调用——不需要 py_call() 包装。
函数名会做校验:double、select 等 SQL 保留字和类型名会被拒绝,数字开头、含连字符的名字也会报错,避免注册了但调不了。
● ● ●
全部函数一览
| | |
|---|
py_eval(expr, value) | | 逐行 eval Python 表达式,x 绑定到 value |
py_map(expr, value) | | 批量列 eval,一次 Python 调用处理整个 chunk |
py_register(name, expr, nargs) | | |
py_call(name, arg) | | |
py_list_functions() | | |
py_agg(value, update, finalize) | | |
py_scan(module, func) | | |
py_activate_venv(path) | | |
● ● ●
实战:分钟内从 CSV 到分析
LOAD 'python.duckdb_extension';-- 注册转换函数SELECT py_register('parse_date', 'x[:10]', 1);SELECT py_register('to_upper', 'x.upper()', 1);-- 直接 SQL + UDF 一条龙SELECT to_upper(product), SUM(revenue)::INT as totalFROM read_csv('sales.csv', columns = {'date': 'VARCHAR', 'product': 'VARCHAR', 'revenue': 'DOUBLE'})WHERE parse_date(date) > '2025-01-01'GROUP BY productORDER BY total DESC;不需要 pandas,不需要 Python 脚本——DuckDB 的 SQL 引擎 + Python 的表达能力,一个 LOAD 全搞定。
● ● ●
架构:为啥直接调用之前会炸
整个项目最头疼的问题:py_register 注册的函数直接调用时 segfault。
原因:DuckDB 的 init_extension 回调返回后,传入的连接句柄就失效了。py_register 在查询回调里拿这个失效连接调 duckdb_register_scalar_function —— 必崩。
修复:不在 init 缓存连接,而是存 database 句柄。每次 py_register 时从 database 开一条新连接,在新连接上注册函数,注册完马上断开。
init_extension → get_database(info) → DB (全局缓存)py_register → duckdb_connect(DB) → .register(con) → duckdb_disconnect
已验证平台:macOS arm64 + DuckDB 1.5.4 / 1.5.5。
● ● ●
技术栈
- quack-rs
- PyO3
- 约 1200 行 Rust,60 行 Makefile
● ● ●
兼容性
Python:扩展二进制不绑定特定版本——编译时用任意 Python 3,运行时 dlopen 当前环境的 libpython。已验证 3.11 / 3.12 / 3.13(3.14 等 PyO3 升级)。
DuckDB:与 Python 不同,DuckDB 扩展版本锁定——已验证 1.5.4 / 1.5.5。
项目地址:github.com/alitrack/duckdb-python
● ● ●
最后说两句
DuckDB 社区正在从"SQL 分析引擎"走向"可嵌入的分析平台"。LOAD 扩展机制是通向这个未来的门——httpfs 打通了存储,spatial 打通了 GIS,duckdb-python 打通了 Python 生态。
现在你可以在 SQL 里写 py_register('my_func', 'x**2 + 3*x + 1', 1),然后 SELECT my_func(5)——一行注册,直接调用。如果能用 Python 表达,就能用 SQL 调度。