PDEP-9: 允许第三方项目使用标准 API 注册 pandas 连接器
- 创建日期: 2023 年 3 月 5 日
- 状态: 已拒绝
- 讨论: #51799 #53005
- 作者: Marc Garcia
- 修订版本: 1
PDEP 摘要
本文档建议,实现 I/O 或内存连接器到 pandas 的第三方项目可以使用 Python 的入口点系统注册它们,并通过通常的 pandas I/O 接口使它们可供 pandas 用户使用。例如,独立于 pandas 的包可以实现来自 DuckDB 的读取器和写入 Delta Lake 的写入器,并且当安装在用户环境中时,用户将能够像在 pandas 中实现一样使用它们。例如
import pandas
pandas.load_io_plugins()
df = pandas.DataFrame.read_duckdb("SELECT * FROM 'my_dataset.parquet';")
df.to_deltalake('/delta/my_dataset')
这将允许轻松扩展现有的连接器数量,添加对新格式和数据库引擎、数据湖技术、核心外连接器、新的 ADBC 接口等的支持,同时降低 pandas 代码库的维护成本。
当前状态
pandas 支持使用 I/O 连接器导入和导出来自不同格式的数据,目前在 pandas/io
中实现,以及连接到内存结构,如 Python 结构或其他库格式。在许多情况下,这些连接器包装现有的 Python 库,而在其他情况下,pandas 实现读取和写入特定格式的逻辑。
在某些情况下,对于同一格式存在不同的引擎。使用这些连接器的 API 是 pandas.read_<format>(engine='<engine-name>', ...)
导入数据,以及 DataFrame.to_<format>(engine='<engine-name>', ...)
导出数据。
对于导出到内存的对象(如 Python 字典),API 与 I/O 相同,DataFrame.to_<format>(...)
。对于从内存中的对象导入的格式,API 不同,使用 from_
前缀而不是 read_
,DataFrame.from_<format>(...)
。
在某些情况下,pandas API 提供了 DataFrame.to_*
方法,这些方法不用于将数据导出到磁盘或内存对象,而是用于转换 DataFrame
的索引: DataFrame.to_period
和 DataFrame.to_timestamp
。
连接器的依赖项默认情况下不会加载,而是在使用连接器时导入。如果未安装依赖项,则会引发 ImportError
。
>>> pandas.read_gbq(query)
Traceback (most recent call last):
...
ImportError: Missing optional dependency 'pandas-gbq'.
pandas-gbq is required to load data from Google BigQuery.
See the docs: https://pandas-gbq.readthedocs.io.
Use pip or conda to install pandas-gbq.
支持的格式
格式列表可以在 IO 指南 中找到。接下来将展示一个更详细的表格,包括内存对象以及 DataFrame 样式器中的 I/O 连接器。
格式 | 读取器 | 写入器 | 引擎 |
---|---|---|---|
CSV | X | X | c , python , pyarrow |
FWF | X | c , python , pyarrow |
|
JSON | X | X | ujson , pyarrow |
HTML | X | X | lxml , bs4/html5lib (参数 flavor ) |
LaTeX | X | ||
XML | X | X | lxml , etree (参数 parser ) |
剪贴板 | X | X | |
Excel | X | X | xlrd , openpyxl , odf , pyxlsb (每个引擎支持不同的文件格式) |
HDF5 | X | X | |
Feather | X | X | |
Parquet | X | X | pyarrow , fastparquet |
ORC | X | X | |
Stata | X | X | |
SAS | X | ||
SPSS | X | ||
Pickle | X | X | |
SQL | X | X | sqlalchemy , dbapi2 (从 con 参数的类型推断) |
BigQuery | X | X | |
字典 | X | X | |
记录 | X | X | |
字符串 | X | ||
Markdown | X | ||
xarray | X |
在撰写本文档时,io/
模块包含近 100,000 行 Python、C 和 Cython 代码。
没有客观标准来判断何时将格式包含在 pandas 中,上面的列表主要是开发人员对在 pandas 中实现特定格式的连接器感兴趣的结果。
可以使用 pandas 处理的数据的现有格式数量不断增加,即使对于流行的格式,pandas 也难以跟上。可能需要连接到 PyArrow、PySpark、Iceberg、DuckDB、Hive、Polars 以及许多其他格式。
同时,正如 2019 年用户调查 所示,一些格式并不常用。这些不太流行的格式包括 SPSS、SAS、Google BigQuery 和 Stata。请注意,调查中只包含 I/O 格式(不包括记录或 xarray 等内存格式)。
支持所有格式的维护成本不仅在于维护代码和审查拉取请求,还在于在 CI 系统上安装依赖项、编译代码、运行测试等方面花费大量时间。
在某些情况下,一些连接器的主要维护人员不是 pandas 核心开发团队的成员,而是专门从事某种格式的人员。
建议
虽然当前的 pandas 方法运行得相当好,但很难找到一个稳定的解决方案,既能避免 pandas 的维护成本过高,又能让用户以简单直观的方式与他们感兴趣的所有不同格式和表示形式进行交互。
第三方软件包已经能够实现与 pandas 的连接器,但存在一些限制。
- 鉴于 pandas 本身支持的格式数量众多,第三方连接器很可能被视为二等公民,不够重要,无法使用或得不到良好支持。
- 没有用于外部 I/O 连接器的标准 API,用户需要单独学习每个连接器。由于 pandas I/O API 使用 read/to 而不是 read/write 或 from/to,导致不一致,因此开发人员在许多情况下会忽略该约定。此外,即使开发人员遵循 pandas 约定,命名空间也会不同,因为连接器开发人员很少会将他们的函数猴子补丁到
pandas
或DataFrame
命名空间中。 - 使用第三方 I/O 连接器导出数据时,无法进行方法链,除非作者对
DataFrame
类进行猴子补丁,这应该避免。
本文件建议以标准方式向第三方库开放 pandas I/O 连接器的开发,以克服这些限制。
提案实现
实施此提案不需要对 pandas 进行重大更改,并将使用接下来定义的 API。
用户 API
用户可以使用标准打包工具(pip、conda 等)安装实现 pandas 连接器的第三方软件包。这些连接器应该实现入口点,pandas 将使用这些入口点自动创建相应的 pandas.read_*
、pandas.DataFrame.to_*
和 pandas.Series.to_*
方法。此接口不会创建任意函数或方法名称,只允许 read_*
和 to_*
模式。
只需安装相应的软件包并调用函数 pandas.load_io_plugins()
,用户就可以使用以下代码
import pandas
pandas.load_io_plugins()
df = pandas.read_duckdb("SELECT * FROM 'dataset.parquet';")
df.to_hive(hive_conn, "hive_table")
此 API 允许方法链
(pandas.read_duckdb("SELECT * FROM 'dataset.parquet';")
.to_hive(hive_conn, "hive_table"))
预计 I/O 函数和方法的总数会很少,因为用户通常只使用一小部分格式。如果将不太流行的格式(例如 SAS、SPSS、BigQuery 等)从 pandas 核心移到第三方包中,实际上可以减少当前状态下的数量。移动这些连接器不属于本提案的一部分,可以在以后的单独提案中讨论。
插件注册
第三方包将实现 入口点 来定义它们在 dataframe.io
组下实现的连接器。
例如,假设项目 pandas_duckdb
实现了一个 read_duckdb
函数,可以使用 pyproject.toml
来定义下一个入口点
[project.entry-points."dataframe.io"]
reader_duckdb = "pandas_duckdb:read_duckdb"
当用户调用 pandas.load_io_plugins()
时,它将读取 dataframe.io
组的入口点注册表,并动态地在 pandas
、pandas.DataFrame
和 pandas.Series
命名空间中为它们创建方法。只有以 reader_
或 writer_
开头的入口点才会被 pandas 处理,并且在入口点中注册的函数将被 pandas 用户在相应的 pandas 命名空间中使用。关键字 reader_
和 writer_
之后的文本将用于函数的名称。在上面的示例中,入口点名称 reader_duckdb
将创建 pandas.read_duckdb
。具有名称 writer_hive
的入口点将创建方法 DataFrame.to_hive
和 Series.to_hive
。
不以 reader_
或 writer_
开头的入口点将被此接口忽略,但不会引发异常,因为它们可以用于此 API 的未来扩展或其他相关的 dataframe I/O 接口。
内部 API
连接器将使用 dataframe 交换 API 向 pandas 提供数据。当从连接器读取数据时,在将其作为对 pandas.read_<format>
的响应返回给用户之前,将从数据交换接口解析数据并将其转换为 pandas DataFrame。在实践中,连接器很可能返回 pandas DataFrame 或 PyArrow 表,但接口将支持实现 dataframe 交换 API 的任何对象。
连接器指南
为了为用户提供更好、更一致的体验,将创建指南来统一术语和行为。接下来定义了一些需要统一的主题。
避免名称冲突的指南。由于预计某些格式会存在多个实现,就像现在已经发生的那样,将创建有关如何命名连接器的指南。最简单的方法可能是使用 to_<format>_<implementation-id>
类型的字符串作为格式,如果预计可能存在多个连接器。例如,对于 LanceDB,很可能只存在一个连接器,可以使用名称 lance
(这将创建 pandas.read_lance
或 DataFrame.to_lance
)。但是,如果基于 Arrow2 Rust 实现的新 csv
阅读器,指南可以建议使用 csv_arrow2
来创建 pandas.read_csv_arrow2
等。
参数的存在和命名,由于许多连接器可能提供类似的功能,例如仅加载数据中的部分列,或处理路径。对连接器开发人员的建议示例可能是
columns
:使用此参数允许用户加载部分列。允许列表或元组。path
:如果数据集是文件磁盘中的文件,请使用此参数。允许字符串、pathlib.Path
对象或文件描述符。对于字符串对象,允许自动下载的 URL、自动解压缩的压缩文件等。可以推荐特定库以更轻松、更一致的方式处理这些问题。schema
:对于没有模式的数据集(例如csv
),允许提供 Apache Arrow 模式实例,如果没有提供,则自动推断类型。
请注意,以上只是说明指南的示例,而不是指南的提案,指南将在本 PDEP 批准后独立开发。
连接器注册和文档。为了简化连接器及其文档的发现,可以鼓励连接器开发人员在中心位置注册他们的项目,并使用标准结构进行文档编制。这将允许创建统一的网站来查找可用的连接器及其文档。它还允许为特定实现定制文档,并包含其最终 API。
连接器示例
本节列出了可以立即从本提案中受益的特定连接器示例。
PyArrow 目前提供 Table.from_pandas
和 Table.to_pandas
。使用新接口,它还可以注册 DataFrame.from_pyarrow
和 DataFrame.to_pyarrow
,因此当 PyArrow 安装在环境中时,pandas 用户可以使用他们习惯的接口使用转换器。在 #51760 中讨论了与 PyArrow 表更好的集成。
当前 API:
pyarrow.Table.from_pandas(table.to_pandas()
.query('my_col > 0'))
建议的 API:
(pandas.read_pyarrow(table)
.query('my_col > 0')
.to_pyarrow())
Polars、Vaex 和其他数据帧框架可以从第三方项目中受益,这些项目使与 pandas 的互操作性使用更明确的 API。在 #47368 中请求了与 Polars 的集成。
当前 API:
polars.DataFrame(df.to_pandas()
.query('my_col > 0'))
建议的 API:
(pandas.read_polars(df)
.query('my_col > 0')
.to_polars())
DuckDB 提供了一个内存外引擎,能够在加载数据之前推送谓词,从而更好地利用内存并显着减少加载时间。pandas 由于其急切的性质,无法轻松地自行实现这一点,但可以从 DuckDB 加载器中受益。加载器可以已经在 pandas 内部实现(它已经在 #45678 中提出),或者作为具有任意 API 的第三方扩展。但本提案将允许创建具有标准且直观 API 的第三方扩展
pandas.read_duckdb("SELECT *
FROM 'dataset.parquet'
WHERE my_col > 0")
内存外算法将一些操作(如过滤或分组)推送到数据的加载。虽然目前还不可行,但可以使用此接口开发实现内存外算法的连接器。
大数据系统(如 Hive、Iceberg、Presto 等)可以从将数据加载到 pandas 的标准方法中受益。同样,可以将查询结果作为 Arrow 返回的常规 SQL 数据库,也将受益于比基于 SQL Alchemy 和 Python 结构的现有连接器更好、更快的连接器。
任何其他格式,包括**特定领域格式**,都可以轻松地使用清晰直观的 API 实现 pandas 连接器。
限制
本提案的实施存在一些限制,在此讨论。
- **缺乏对多个引擎的支持。** 当前的 pandas I/O API 支持对同一格式使用多个引擎(对于同一函数或方法名称)。例如
read_csv(engine='pyarrow', ...)
。支持引擎要求特定格式的所有引擎使用相同的签名(相同的参数),这不是理想的。不同的连接器可能具有不同的参数,使用*args
和**kwargs
会为用户提供更复杂、更难的操作体验。因此,本提案更倾向于使用唯一的函数和方法名称,而不是支持引擎选项。 - **缺乏对连接器类型检查的支持。** 本 PDEP 提案动态创建函数和方法,而这些方法不支持使用存根进行类型检查。对于 pandas 的其他动态创建组件(如自定义访问器)也是如此。
- **对当前 I/O API 没有改进。** 在本提案的讨论中,已经考虑改进当前的 pandas I/O API,以解决使用
read
/to
(而不是例如read
/write
)的不一致性,避免对非 I/O 操作使用to_
前缀方法,或使用专用命名空间(例如DataFrame.io
)来连接器。所有这些更改都超出了本 PDEP 的范围。
未来计划
本 PDEP 专门用于支持现有或未来连接器的更好 API。对现有 pandas 代码库中的任何连接器进行更改不在本 PDEP 的范围内。
与本 PDEP 相关的未来讨论的一些想法包括
-
在导入 pandas 时自动加载 I/O 插件。
-
从 pandas 代码库中删除一些使用频率最低的连接器,例如 SAS、SPSS 或 Google BigQuery,并将它们移动到使用此接口注册的第三方连接器。
-
讨论一个更好的 Pandas 连接器 API。例如,使用
read_*
方法而不是from_*
方法,重命名未用作 I/O 连接器的to_*
方法,使用一致的术语,例如 from/to、read/write、load/dump 等,或为连接器使用专用命名空间(例如pandas.io
而不是通用的pandas
命名空间)。 -
将
DataFrame
构造函数支持的一些格式实现为 I/O 连接器。
PDEP-9 历史
- 2023 年 3 月 5 日:初始版本
- 2023 年 5 月 30 日:主要重构,以使用 Pandas 现有 API、数据帧交换 API,并使用户明确加载插件
- 2023 年 6 月 13 日:PDEP 在经过多次迭代后没有得到任何支持,并且已被作者拒绝关闭