Python 客户端
本节目标
读完本篇,你应能:
- 用
pip install duckdb安装官方 Python 包,并创建duckdb.connect()连接(内存库或文件库) - 用
execute/sql跑 SQL,并用fetchdf()(或df())把结果取成 Pandas DataFrame - 在 SQL 中直接引用 DataFrame 变量,或把查询结果写回 Pandas
- 了解 Apache Arrow 零拷贝互操作的入口(
fetch_arrow_table/arrow()) - 用 Relational API 链式构造查询,并理解惰性求值
- 避开 连接生命周期、默认连接与多线程使用上的常见误区
若还不熟悉从磁盘读 CSV/Parquet,请先阅读 导入与导出。
安装与环境
DuckDB Python 包可通过 pip 安装(Python 3.10+):
pip install duckdb
Conda 用户也可使用 conda install python-duckdb -c conda-forge。CLI、Python 包与扩展版本建议对齐同一主版本线,避免存储格式或符号不兼容(见 简介与安装 中的版本说明)。
连接:duckdb.connect()
duckdb.connect 返回符合 PEP 249 DB-API 的 DuckDBPyConnection,语义类似 Python 标准库里的 sqlite3。
database 参数 | 含义 |
|---|---|
省略或 :memory: | 新的匿名内存库,进程结束即丢失 |
:memory:名称 | 具名内存库;同名再次 connect 共享 catalog |
| 文件路径 | 持久化库;文件不存在时会创建(扩展名可为 .duckdb、.db 等) |
:default: | 模块级默认连接(见下文) |
import duckdb
# 持久化文件库
con = duckdb.connect("analytics.duckdb")
# 只读打开已有库
# con = duckdb.connect("analytics.duckdb", read_only=True)
# 通过 config 传入 DuckDB 设置(如 threads、memory_limit)
# con = duckdb.connect(config={"threads": 4})
连接用完后应 con.close(),或在脚本里使用 with duckdb.connect("analytics.duckdb") as con:(上下文管理器会自动关闭)。
最简用法:duckdb.sql 与默认连接
不写 connect 时,模块提供一条默认内存连接:duckdb.sql(...) 与 duckdb.connect() 上的方法都走这条连接。适合笔记本里快速试 SQL;长期脚本或要隔离 catalog 时,显式 con = duckdb.connect(...) 更清晰。
import duckdb
duckdb.sql("SELECT 42 AS answer").show()
show() 会触发执行并打印前若干行;查询本身在 fetch 之前可以是惰性的(见 Relational API)。
DB API:execute、fetchdf 与等价写法
在显式连接上,常见模式是 con.execute(sql) 或 con.sql(sql)(返回 Relation,便于链式处理),再用输出方法取结果:
import duckdb
con = duckdb.connect()
# 方式 1:DB-API 风格
con.execute("CREATE TABLE t AS SELECT 1 AS id, 'a' AS label")
rows = con.execute("SELECT * FROM t").fetchall()
print(rows) # [(1, 'a')]
# 方式 2:直接得到 Pandas DataFrame(分析场景最常用)
df = con.execute("SELECT * FROM t").fetchdf()
# 或 con.sql("SELECT * FROM t").df()
con.close()
| 方法 | 说明 |
|---|---|
fetchdf() / df() | 结果为 pandas.DataFrame |
fetch_arrow_table() / arrow() | 结果为 pyarrow.Table(需安装 pyarrow) |
fetchall() / fetchnumpy() | 元组列表或 NumPy 结构化数组 |
show() / print(rel) | 终端预览,会触发部分执行 |
参数化查询使用 ? 占位(与 SQLite 类似):con.execute("SELECT ? AS x", [42])。
与 Pandas 互操作
DuckDB 通过 replacement scan(替换扫描) 在 SQL 里按变量名找到当前 Python 进程中的 DataFrame,无需先 register(老版本习惯的手动注册仍可用,但多数场景不必)。
import duckdb
import pandas as pd
df = pd.DataFrame({"city": ["北京", "上海"], "pop_m": [21.5, 24.8]})
con = duckdb.connect()
# SQL 中直接写 DataFrame 变量名
result = con.sql("""
SELECT city, pop_m, pop_m * 1.0e6 AS pop
FROM df
WHERE pop_m > 22
""").df()
# 物化到表:CREATE TABLE ... AS SELECT * FROM df
con.sql("CREATE TABLE cities AS SELECT * FROM df")
# 追加行:append(列顺序不一致时用 by_name=True)
# con.sql("CREATE TABLE cities (city VARCHAR, pop_m DOUBLE)")
# con.append("cities", df, by_name=True)
con.close()
与 导入与导出 结合:磁盘上的 CSV/Parquet 仍可在 SQL 里用路径或 read_csv / read_parquet,Python 侧只负责 orchestrate 与 fetchdf() 做后续分析。
列顺序与源表不一致时,优先 INSERT INTO ... BY NAME SELECT * FROM df(SQL 层)或 append(..., by_name=True)(API 层)。
Apache Arrow 简述
当结果很大或要与 Polars、R Arrow 等栈对接时,用 Arrow 列式内存往往比先 materialize 成 Pandas 更省拷贝:
import duckdb
con = duckdb.connect()
rel = con.sql("SELECT * FROM range(1000) AS t(id)")
table = rel.fetch_arrow_table() # pyarrow.Table
# 或 table = rel.arrow()
con.close()
DuckDB 可在 Pandas / Arrow / NumPy 之间尽量零拷贝传递;具体类型映射见官方 Pandas integration 与 Arrow 相关指南。未安装 pyarrow 时,fetch_arrow_table 不可用,可先 pip install pyarrow。
Relational API 简例
Relational API 以 DuckDBPyRelation 为中心:每个节点代表一段尚未执行的 SQL。直到 df() / fetchdf() / write_* / show() 等输出方法被调用,引擎才真正跑查询。
import duckdb
con = duckdb.connect()
base = con.sql("FROM range(1, 6) AS t(n)")
evens = base.filter("n % 2 = 0").project("n, n * 10 AS scaled")
print(evens.fetchdf())
con.close()
链式方法(如 filter / project / aggregate / order / limit)与 SQL 关键字对应;复杂逻辑仍可混用 con.sql("...") 与 relation 变量。惰性求值意味着中间 relation 可以 r1 = con.sql("FROM huge_parquet") 再逐步加条件,避免重复写大段 SQL 字符串。
常见坑
- 连接生命周期:匿名
:memory:连接关闭后,其中表即消失;持久化库需在close()前依赖 DuckDB 的 checkpoint 行为落盘。长生命周期服务里为每个请求connect/close或维护连接池(单连接串行使用),不要假设「全局一个 con」永远有效而不关闭。 - 默认连接与多连接:
duckdb.sql(...)与duckdb.connect()若混用,catalog 可能不在你预期的那条连接上。测试/脚本里统一con = duckdb.connect("file.duckdb"),所有操作走con。 - 线程:单个
DuckDBPyConnection不是线程安全的;多线程应每线程一条连接,或在外层加锁串行访问。并行度主要靠 DuckDB 内部的threads设置(config或SET threads = ...),而不是多个线程共享同一 connection 去execute。 - Pandas 列类型:nullable 整数、时区等可能在往返后出现 dtype 变化;需要稳定 schema 时在 SQL 里
CAST,或导出 Parquet 再读(见 类型与函数)。 - 多进程写同一文件库:与 CLI 相同,多进程同时写同一
.duckdb可能失败;只读可read_only=True,写路径应单进程或应用内协调。
延伸阅读
- Python API Overview · DB API · Relational API
- Import from Pandas · Python client on GitHub
- 系列索引:DuckDB 教程
- 下一篇:常用扩展 — 安装、加载与典型扩展场景