Apache Arrow 开发者实战:向 PyArrow compute 模块贡献新特性的完整流程
2026/9/23 1:14:41 网站建设 项目流程
  • 数据工程
  • 数据分析
  • 大数据

【免费下载链接】arrow

Apache Arrow is a multi-language toolbox for accelerated data interchange and in-memory processing

项目地址:https://gitcode.com/gh_mirrors/arrow12/arrow
点击查看免费下载

导读

本文基于 Apache Arrow 官方开发者指南中的 Python 教程,完整走一遍"从零向 PyArrow 贡献一个新计算函数"的真实流程:从 Fork 仓库、构建 PyArrow、创建 Issue,到研究pc.min_max的实现链路、编写pc.tutorial_min_max新函数、补充单元测试、通过 Archery 检查代码风格,最终提交 Pull Request。读完本文,你将掌握 Arrow 开源协作的标准工作流,理解 PyArrow 中 Python 层与 C++ 计算内核的衔接方式,并能独立完成一个简单的功能贡献或 Bug 修复。

本文档对应的原始教程位于 docs/source/developers/guide/tutorials/python_tutorial.rst,读者可结合 开发者指南总览 与 分步指南 对照阅读。

本教程要贡献的新特性:模仿arrow.compute模块中已有的min_max函数,但把返回区间在两侧各扩大 1——最小值减 1、最大值加 1。这是一个专为教程虚构的函数,实际项目中并不存在,用于演示完整的贡献流程。

教程的定位与前置阅读

本教程不是一份从头到尾的逐步操作手册,而是针对一个具体案例的实战演示。它遵循 快速参考指南 中规定的步骤,并与更详细的 分步指南 相配套——当你在本教程中遇到信息缺失时,应前往分步指南查找补充资料。

特性贡献的目标模块是PyArrow 的 compute 模块,但你也可以按照完全相同的步骤来修复一个 Bug新增一个绑定(binding)。教程中涉及到的各个子环节,都有对应的详细章节:

环节详细指南
环境搭建(Git 安装等)set_up.rst
构建 Arrow 与 PyArrowbuilding.rst
查找/创建 Issuefinding_issues.rst
测试规范testing.rst
代码风格检查styling.rst
Pull Request 生命周期pr_lifecycle.rst

环境搭建:Fork、Clone 并配置 upstream

Arrow 使用 Git 进行版本控制,贡献流程基于 Pull Request。开始之前请确保 Git 已安装(安装说明见 set_up.rst)。

首先在代码托管平台(GitHub)上ForkApache Arrow 仓库,然后在本地克隆你自己的 Fork,并把官方仓库添加为upstream远程地址:

$ git clone https://github.com/<your username>/arrow.git $ cd arrow $ git remote add upstream https://github.com/apache/arrow

此后:

  • origin指向你自己 Fork 的仓库,用于推送新分支;
  • upstream指向官方仓库,用于同步最新代码。

从源码结构看,这个arrow目录同时包含 C++ 核心实现(cpp/src)、Python 绑定(python/pyarrow)、Java(java)、Go(go)等多个语言模块。本教程的操作只涉及 Python 目录下的两个文件:python/pyarrow/compute.pypython/pyarrow/tests/test_compute.py

构建 PyArrow

构建 PyArrow 的脚本因操作系统而异,本教程不赘述具体构建命令,只给出指引:

  • 构建流程的入门介绍:参见 building.rst;
  • 构建 PyArrow 的具体说明:参见分步指南中 PyArrow 构建部分(build_pyarrow小节),以及仓库中的 python/README.md 和 python/setup.py。

构建成功后,在仓库的python目录下即可导入并实验pyarrow。后续所有 Python 交互都默认在arrow/python目录下进行。

为新特性创建 GitHub Issue

在动手写代码之前,先要创建(或认领)一个 Issue,理由很实际:让社区其他人知道你在做这件事,避免重复劳动

  1. 在 Arrow 项目的 GitHub Issue 面板中点击New issue创建新 Issue;
  2. 创建后把自己指派给该 Issue——在 Issue 下添加一条take评论即可认领任务。

有关查找合适 Issue、了解 Issue 规范的更多信息,参见 finding_issues.rst。本教程对应的 Issue 编号为ARROW-14977(该编号在后续分支名、提交信息、PR 标题中都会用到)。

从更新的 main 分支创建新工作分支

在改动任何代码之前,必须先创建新的 Git 分支。从已同步的main分支出发:

$ git checkout main $ git fetch upstream $ git pull --ff-only upstream main $ git checkout -b ARROW-14977

这里有两个值得注意的细节:

  • --ff-only只允许快进(fast-forward)合并,如果 upstream 与本地有分叉会直接报错而不是自动产生合并提交,从而保证本地main与官方main严格一致;
  • 分支名直接使用 Issue 编号ARROW-14977,这是 Arrow 社区的命名惯例,让分支与 Issue 一一对应。

研究代码库:pc.min_max 的 Python 与 C++ 链路

在写新函数之前,先弄清楚已有的pc.min_max是怎么定义的、Python 层如何与 C++ 计算内核连接。教程推荐直接在 GitHub 仓库中搜索函数引用。

搜索结果指向python/pyarrow/tests/test_compute.py(该文件在本仓库中确实存在),既然该函数有测试,那么它一定在compute.py中被定义并导出。进一步查看 python/pyarrow/compute.py 可以发现:文件通过_make_global_functions()机制(见compute.py_wrap_function_decorate_compute_function等辅助函数)把 C++ 计算函数批量包装成 Python 函数;与此同时 python/pyarrow/_compute.pyx 以 Cython 形式封装了 C++ 侧的函数注册表与call_function调用入口。

在 python/pyarrow/_compute.pyx 中还能看到 Arrow 计算函数的分类体系——min_max属于"scalar_aggregate"(标量聚合)类函数:它对输入做降维归约,同类函数还有summode等;与之并列的还有hash_aggregate(如hash_sumhash_min_max)、scalarvectormeta等类别。这解释了为什么min_max的调用方式是"传入一个数组 + 聚合选项"。

在 Python 控制台中研究 pc.min_max

arrow/python目录进入 Python 控制台,实验pc.min_max的行为:

$ cd python $ python Python 3.9.7 (default, Oct 22 2021, 13:24:00) [Clang 13.0.0 (clang-1300.0.29.3)] on darwin Type "help", "copyright", "credits" or "license" for more information.
>>> import pyarrow.compute as pc >>> data = [4, 5, 6, None, 1] >>> data [4, 5, 6, None, 1] >>> pc.min_max(data) <pyarrow.StructScalar: [('min', 1), ('max', 6)]> >>> pc.min_max(data, skip_nulls=False) <pyarrow.StructScalar: [('min', None), ('max', None)]>

两个关键观察:

  1. pc.min_max接受一个普通 Python 列表,返回一个pyarrow.StructScalar,其结构包含('min', ...)('max', ...)两个字段;
  2. 默认参数skip_nulls=True会忽略空值(所以None不影响结果);当skip_nulls=False时,只要输入含空值,结果就整体为None

skip_nulls正是ScalarAggregateOptions的成员——在 python/pyarrow/compute.py 的导入列表中可以看到ScalarAggregateOptions从 Cython 层导出,而底层的选项定义位于 C++ 源码中。我们会在新函数里复用这一选项类。

第一版实现:直接调用 C++ 的 min_max

我们的新函数命名为pc.tutorial_min_max。目标行为:

  • 输入同样的数据[4, 5, 6, None, 1],默认返回[('min-', 0), ('max+', 7)](即最小值减 1、最大值加 1);
  • skip_nulls=False(空值纳入计算)时,结果与pc.min_max一致,为[('min', None), ('max', None)]

先在 python/pyarrow/compute.py 末尾添加第一版代码,验证能否从 Python 侧成功调用 C++ 的min_max内核:

def tutorial_min_max(values, skip_nulls=True): """ Add docstrings Parameters ---------- values : Array Returns ------- result : TODO Examples -------- >>> import pyarrow.compute as pc >>> data = [4, 5, 6, None, 1] >>> pc.tutorial_min_max(data) <pyarrow.StructScalar: [('min-', 0), ('max+', 7)]> """ options = ScalarAggregateOptions(skip_nulls=skip_nulls) return call_function("min_max", [values], options)

这段代码揭示了 PyArrow compute 模块的标准写法:构造一个 Options 对象(ScalarAggregateOptions),再通过call_function("min_max", [values], options)按名称调用 C++ 计算内核call_function的第一个参数是 C++ 侧注册的函数名字符串,这与 python/pyarrow/_compute.pyx 中封装 C++FunctionRegistry的实现一一对应。

重新导入并测试:

>>> import pyarrow.compute as pc >>> data = [4, 5, 6, None, 1] >>> pc.tutorial_min_max(data) <pyarrow.StructScalar: [('min', 1), ('max', 6)]>

调用成功,返回的仍是原样的('min', 1), ('max', 6)。接下来要把它改造成"区间外扩"的版本。

研究 StructScalar 的构造方法

要返回[('min-', 0), ('max+', 7)]这种自定义字段名和字段类型的结构,需要学会手工构造pyarrow.StructScalar。教程提示:在文档不足的情况下,单元测试是最好的代码示例来源——在python/pyarrow/tests/test_scalars.pytest_struct_duplicate_fields测试中可以找到StructScalar的构造范例。

在 Python 控制台亲自动手构造一个:

>>> import pyarrow as pa >>> ty = pa.struct([ ... pa.field('min-', pa.int64()), ... pa.field('max+', pa.int64()), ... ]) >>> pa.scalar([('min-', 3), ('max+', 9)], type=ty) <pyarrow.StructScalar: [('min-', 3), ('max+', 9)]>

要点:

  • pa.struct([...])定义一个结构类型,pa.field('min-', pa.int64())声明字段名(可以包含-+等字符)与字段类型;
  • pa.scalar([('min-', 3), ('max+', 9)], type=ty)把 Python 值包装成指定类型的StructScalar
  • StructScalar支持下标访问(min_max[0]min_max[1]),也支持as_py()转回普通 Python 值。

完成最终实现

结合StructScalar的构造知识和ScalarAggregateOptions的选项,在compute.py末尾完成最终版:

def tutorial_min_max(values, skip_nulls=True): """ Compute the minimum-1 and maximum+1 values of a numeric array. This is a made-up feature for the tutorial purposes. Parameters ---------- values : Array skip_nulls : bool, default True If True, ignore nulls in the input. Returns ------- result : StructScalar of min-1 and max+1 Examples -------- >>> import pyarrow.compute as pc >>> data = [4, 5, 6, None, 1] >>> pc.tutorial_min_max(data) <pyarrow.StructScalar: [('min-', 0), ('max+', 7)]> """ options = ScalarAggregateOptions(skip_nulls=skip_nulls) min_max = call_function("min_max", [values], options) if min_max[0].as_py() is not None: min_t = min_max[0].as_py()-1 max_t = min_max[1].as_py()+1 else: min_t = min_max[0].as_py() max_t = min_max[1].as_py() ty = pa.struct([ pa.field('min-', pa.int64()), pa.field('max+', pa.int64()), ]) return pa.scalar([('min-', min_t), ('max+', max_t)], type=ty)

实现逻辑拆解:

  1. 构造选项并调用 C++ 内核ScalarAggregateOptions(skip_nulls=skip_nulls)控制空值处理策略,call_function("min_max", [values], options)拿到原始聚合结果;
  2. 空值分支处理min_max[0].as_py()StructScalar的第一个字段(min)转成 Python 值;若非None,则min_t = min - 1max_t = max + 1;若为None(即skip_nulls=False且输入含空值),则保持None,与pc.min_max的行为对齐;
  3. 构造结果:用pa.struct+pa.scalar把两个修正后的值包装成字段名为'min-''max+'StructScalar返回。

添加单元测试并运行 pytest

功能完成后,必须在 python/pyarrow/tests/test_compute.py 中添加单元测试。把测试追加到该文件末尾:

def test_tutorial_min_max(): arr = [4, 5, 6, None, 1] l1 = {'min-': 0, 'max+': 7} l2 = {'min-': None, 'max+': None} assert pc.tutorial_min_max(arr).as_py() == l1 assert pc.tutorial_min_max(arr, skip_nulls=False).as_py() == l2

测试同时覆盖了两条路径:

  • 默认skip_nulls=True:空值被忽略,期望输出{'min-': 0, 'max+': 7}
  • skip_nulls=False:空值导致结果整体为None,期望输出{'min-': None, 'max+': None}

运行单个测试

pytest-k参数按名称筛选,只跑新加的测试:

$ cd python $ python -m pytest pyarrow/tests/test_compute.py -k test_tutorial_min_max ======================== test session starts ========================== platform darwin -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 rootdir: /Users/alenkafrim/repos/arrow/python, configfile: setup.cfg plugins: hypothesis-6.24.1, lazy-fixture-0.6.3 collected 204 items / 203 deselected / 1 selected pyarrow/tests/test_compute.py . [100%] ======================== 1 passed, 203 deselected in 0.16s ============

运行整个测试文件

确认单个测试通过后,运行test_compute.py的完整测试套件,确保新代码没有破坏任何既有功能:

$ python -m pytest pyarrow/tests/test_compute.py ======================== test session starts =========================== platform darwin -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 rootdir: /Users/alenkafrim/repos/arrow/python, configfile: setup.cfg plugins: hypothesis-6.24.1, lazy-fixture-0.6.3 collected 204 items pyarrow/tests/test_compute.py ................................... [ 46%] ................................................. [100%] ========================= 204 passed in 0.49s ==========================

从源码结构看,test_compute.py中既有test_tutorial_min_max这样的普通函数测试,也大量使用了 Hypothesis 属性测试(如搜索min_max时看到的min_max_arrow风格测试),其所在目录 python/pyarrow/tests 还配套有conftest.py等测试基础设施。关于测试编写规范的更多内容参见 testing.rst。

检查代码风格:Archery

Arrow 使用名为Archery的工具链检查代码是否符合 PEP 8 风格指南。运行:

$ archery lint --python --fix INFO:archery:Running Python formatter (autopep8) INFO:archery:Running Python linter (flake8) /Users/alenkafrim/repos/arrow/python/pyarrow/tests/test_compute.py:2288:80: E501 line too long (88 > 79 characters)

注意两点:

  1. --fix参数会让 Archery 尝试自动修复风格问题(内部依次调用 autopep8 格式化器与 flake8 检查器);
  2. 部分问题无法自动修复——例如上图中的E501 line too long(行超长)就需要手工调整。把超长行拆行后再次运行:
$ archery lint --python --fix INFO:archery:Running Python formatter (autopep8) INFO:archery:Running Python linter (flake8)

没有任何输出即表示检查通过。Archery 的相关源码与配置位于 dev/archery,其 Python 环境依赖可参考 ci/conda_env_archery.txt。

提交、同步与推送

检查改动内容

提交前先查看哪些文件被修改,并 diff 确认没有引入错误:

$ git status On branch ARROW-14977 Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: python/pyarrow/compute.py modified: python/pyarrow/tests/test_compute.py no changes added to commit (use "git add" and/or "git commit -a")
$ git diff diff --git a/python/pyarrow/compute.py b/python/pyarrow/compute.py index 9dac606c3..e8fc775d8 100644 --- a/python/pyarrow/compute.py +++ b/python/pyarrow/compute.py @@ -774,3 +774,45 @@ def bottom_k_unstable(values, k, sort_keys=None, *, memory_pool=None): sort_keys = map(lambda key_name: (key_name, "ascending"), sort_keys) options = SelectKOptions(k, sort_keys) return call_function("select_k_unstable", [values], options, memory_pool) + + +def tutorial_min_max(values, skip_nulls=True): + """ + Compute the minimum-1 and maximum-1 values of a numeric array. + + This is a made-up feature for the tutorial purposes. + + Parameters + ---------- + values : Array + skip_nulls : bool, default True + If True, ignore nulls in the input. + + Returns + ------- + result : StructScalar of min-1 and max+1 + + Examples + -------- + >>> import pyarrow.compute as pc + >>> data = [4, 5, 6, None, 1] + >>> pc.tutorial_min_max(data) + <pyarrow.StructScalar: [('min-', 0), ('max+', 7)]> + """ + + options = ScalarAggregateOptions(skip_nulls=skip_nulls) + min_max = call_function("min_max", [values], options) + + ...

git diff显示改动正好落在compute.py末尾(跟在bottom_k_unstable函数之后),与预期一致。

提交到分支历史

$ git commit -am "Adding a new compute feature for tutorial purposes" [ARROW-14977 170ef85be] Adding a new compute feature for tutorial purposes 2 files changed, 51 insertions(+)

git log查看提交历史:

$ git log commit 170ef85beb8ee629be651e3f93bcc4a69e29cfb8 (HEAD -> ARROW-14977) Author: Alenka Frim <frim.alenka@gmail.com> Date: Tue Dec 7 13:45:06 2021 +0100 Adding a new compute feature for tutorial purposes commit 8cebc4948ab5c5792c20a3f463e2043e01c49828 (main) Author: Sutou Kouhei <kou@clear-code.com> Date: Sun Dec 5 15:19:46 2021 +0900 ARROW-14981: [CI][Docs] Upload built documents ...

需要时 rebase 到 upstream main

如果分支创建已经有一段时间,upstreammain上可能出现了新提交。为避免合并冲突,推送前先 rebase 到最新的 upstream:

$ git pull upstream main --rebase

推送到自己的 Fork(origin)

$ git push origin ARROW-14977 Enumerating objects: 13, done. Counting objects: 100% (13/13), done. Delta compression using up to 8 threads Compressing objects: 100% (7/7), done. Writing objects: 100% (7/7), 1.19 KiB | 1.19 MiB/s, done. Total 7 (delta 6), reused 0 (delta 0), pack-reused 0 remote: Resolving deltas: 100% (6/6), completed with 6 local objects. remote: remote: Create a pull request for 'ARROW-14977' on GitHub by visiting: remote: https://github.com/AlenkaF/arrow/pull/new/ARROW-14977 remote: To https://github.com/AlenkaF/arrow.git * [new branch] ARROW-14977 -> ARROW-14977

创建 Pull Request

推送成功后,打开 Arrow 仓库(官方或你自己的 Fork)页面,会看到一条黄色的通知栏,提示你最近推送了ARROW-14977分支——点击Compare & pull request即可发起 Pull Request。

创建 PR 时注意两个要点:

  1. 标题必须与 Issue 对应。本教程应改为ARROW-14977: [Python] Add a "made-up" feature for the guide tutorial(注意与 Issue 标题保持一致,并补充正确的标点)。补充说明:本教程最初编写时项目还在使用 Jira Issue 追踪系统,因此示例标题以ARROW-为前缀;当前项目已改用 GitHub Issues,标题前缀相应地变为GH-14977: [Python] Add a "made-up" feature for the guide tutorial
  2. 编写清晰的描述,让别人一眼看懂你想做什么、改了什么。

点击Create pull request后,代码便以 PR 形式进入官方仓库,PR 会自动关联到对应的 Issue,同时 CI 开始运行。接下来等待社区评审(review):根据反馈修改代码、回复评论、解决 conversation,直到评审通过合并。有关 PR 评审、合入等完整生命周期的说明,参见 pr_lifecycle.rst。

小结与延伸阅读

本教程通过一个虚构的tutorial_min_max函数,完整演示了 Apache Arrow 的功能贡献闭环:建 Issue → 开分支 → 研究既有实现 → 编写 Python 包装(复用 C++ 计算内核)→ 构造 StructScalar 返回结果 → 补测试 → 风格检查 → 提交推送 → 发起 PR。其中最关键的技术认知是:PyArrow 的 compute 函数本质上是 python/pyarrow/compute.py 中通过call_function对 python/pyarrow/_compute.pyx 封装的 C++ 计算内核的轻量包装,理解这条链路后,你就能举一反三地扩展更多计算函数。

建议的延伸阅读路径:

  • 若要把新特性深入 C++ 层(而非仅仅包装既有内核),可阅读 cpp/src/arrow/compute 下的内核实现,以及 python/pyarrow/_compute.pyx 中FunctionKind分类体系;
  • 想参与更多语言绑定,可参照同系列的 r_tutorial.rst(R 语言教程)了解 R 侧绑定流程;
  • 想深入了解 Arrow 的整体架构,可阅读 architectural_overview.rst。
  • 数据工程
  • 数据分析
  • 大数据

【免费下载链接】arrow

Apache Arrow is a multi-language toolbox for accelerated data interchange and in-memory processing

项目地址:https://gitcode.com/gh_mirrors/arrow12/arrow
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询