- 数据工程
- 数据分析
- 大数据
【免费下载链接】arrow
Apache Arrow is a multi-language toolbox for accelerated data interchange and in-memory processing
导读
本文基于 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 与 PyArrow | building.rst |
| 查找/创建 Issue | finding_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.py和python/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,理由很实际:让社区其他人知道你在做这件事,避免重复劳动。
- 在 Arrow 项目的 GitHub Issue 面板中点击New issue创建新 Issue;
- 创建后把自己指派给该 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"(标量聚合)类函数:它对输入做降维归约,同类函数还有sum、mode等;与之并列的还有hash_aggregate(如hash_sum、hash_min_max)、scalar、vector、meta等类别。这解释了为什么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)]>两个关键观察:
pc.min_max接受一个普通 Python 列表,返回一个pyarrow.StructScalar,其结构包含('min', ...)与('max', ...)两个字段;- 默认参数
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.py的test_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)实现逻辑拆解:
- 构造选项并调用 C++ 内核:
ScalarAggregateOptions(skip_nulls=skip_nulls)控制空值处理策略,call_function("min_max", [values], options)拿到原始聚合结果; - 空值分支处理:
min_max[0].as_py()把StructScalar的第一个字段(min)转成 Python 值;若非None,则min_t = min - 1、max_t = max + 1;若为None(即skip_nulls=False且输入含空值),则保持None,与pc.min_max的行为对齐; - 构造结果:用
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)注意两点:
--fix参数会让 Archery 尝试自动修复风格问题(内部依次调用 autopep8 格式化器与 flake8 检查器);- 但部分问题无法自动修复——例如上图中的
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 时注意两个要点:
- 标题必须与 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; - 编写清晰的描述,让别人一眼看懂你想做什么、改了什么。
点击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
相关推荐
Apache Arrow 贡献实战:从零为 PyArrow compute 模块添加新功能的完整教程
Apache Arrow 贡献实战:从零为 PyArrow compute 模块添加新功能的完整教程 本文以 Apache Arrow 官方开发者指南中的 Py
大数据数据分析数据工程序列化Apache Arrow 开发者与贡献者指南:从开发入口、协作流程到代码评审的完整地图
Apache Arrow 开发者与贡献者指南:从开发入口、协作流程到代码评审的完整地图 Apache Arrow 是一个跨语言的大型开源项目,仓库同时维护 C+
大数据数据分析数据工程序列化Apache Arrow 贡献者实战指南:从环境搭建到提交第一个 PR 的完整流程
Apache Arrow 贡献者实战指南:从环境搭建到提交第一个 PR 的完整流程 本篇指南基于 Apache Arrow 官方开发者文档中的 "Steps i
大数据数据分析数据工程序列化
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考