欢迎光临
我们一直在努力

Apache Spark 的 PySpark 贡献指南:从环境搭建、代码规范到错误类体系的完整实战手册

  • 大数据
  • 数据分析
  • 批处理
  • 流处理
  • 机器学习
  • 图计算

【免费下载链接】spark

Apache Spark – A unified analytics engine for large-scale data processing

项目地址:
https://gitcode.com/gh_mirrors/sp/spark

点击查看 免费下载

Apache Spark 是一个统一的大规模数据处理分析引擎,PySpark 作为其 Python 接口,贡献方式涵盖发布版测试、文档编写、代码改动、错误报告、JIRA 维护与代码评审等多种类型。本文基于 python/docs/source/development/contributing.rst 整理 PySpark 特有的贡献规范与实操流程,并对照仓库源码逐项印证。读完本文,你将掌握:如何安装 Release Candidate 参与发布前测试、如何用 Sphinx/Jekyll 构建与验证文档、如何搭建 Conda/venv/pip 开发环境、如何编写符合规范的类型标注与代码/文档字符串/doctest、以及如何基于 error-conditions.json 与 PySparkException 体系贡献标准化的错误与异常。

一、贡献概览:PySpark 不止于写代码

Apache Spark 的通用贡献说明参见官方总指南(spark.apache.org/contributing.html),包括帮助其他用户、测试发布版本、评审改动、文档贡献、缺陷报告、JIRA 维护、代码改动等。本文页面聚焦 PySpark 的额外细节,其中最具 PySpark 特色的三条路径是:

  • 测试发布版本(Release Candidate, RC):官方发布前在 dev@spark.apache.org 邮件列表发起投票,任何用户都可以安装 RC 并验证自己的既有工作负载;
  • 文档贡献:PySpark 的发布文档由 Sphinx 生成,与 Spark 主站 Jekyll 文档相互独立;
  • 代码与错误体系贡献:代码需遵循 Pythonic 风格并与 Scala/Java API 对齐,抛错必须走标准化的 error class 体系而非任意错误消息。

仓库中 PySpark 开发文档的完整入口在 python/docs/source/development/index.rst,其中 contributing、testing、debugging、setting_ide、logger、errors 六个页面共同构成开发者文档体系。

二、通过测试发布版本贡献力量

2.1 安装 Release Candidate

官方在正式发布前会将候选版本发布到 dev@spark.apache.org 邮件列表并开启投票。候选版本可通过 pip 直接安装,例如 Spark 3.0.0 RC1:

pip install https://dist.apache.org/repos/dist/dev/spark/v3.0.0-rc1-bin/pyspark-3.0.0.tar.gz

具体的发布文件链接(形如 https://dist.apache.org/repos/dist/dev/spark/v3.0.0-rc1-bin)会在投票线程中给出。

2.2 为什么测试 RC 是重要贡献

用用户自己的既有工作负载对候选版本做回归验证,是在正式发布前阻止破坏性变更流入用户环境的关键手段。一旦发现回归(regression)、正确性问题或足够严重的性能退化,社区通常会弃用该候选版本,集中修复后纳入下一个 RC。因此,RC 测试是最直接也最容易被低估的贡献方式——它不需要写代码,只需要真实业务场景。

三、贡献文档变更

3.1 Spark 主站文档与 PySpark 文档的分工

Spark 的发布文档位于仓库根目录的 docs 目录,其构建方式与依赖在 docs/README.md 中有说明。在 docs/ 目录下执行以下命令即可在本地启动文档服务,跳过 Scala/R/SQL 文档以加快速度:

SKIP_SCALADOC=1 SKIP_RDOC=1 SKIP_SQLDOC=1 bundle exec jekyll serve –watch

而 PySpark 自身的发布文档由 Sphinx 生成,代码位于 python/docs,构建方式完全独立。仅构建 PySpark 文档时,进入 python/docs 目录执行:

make html

生成的 HTML 位于 python/docs/build/html。仓库中的 python/docs/Makefile 显示其默认参数为 SPHINXOPTS ?= "-W" "-j" "4",即以“警告即错误”的严格模式并行构建,且通过 export PYTHONPATH=$(realpath ..):$(realpath ../lib/py4j-0.10.9.9-src.zip) 将 pyspark 包与 py4j 源码 zip 加入导入路径,因此新增/修改 API 的文档后,必须先保证本地 make html 干净通过。

3.2 新 API 必须手动登记到 reference

一个容易被忽略的硬性要求:新加的 API 必须手动添加到 python/docs/source/reference 下对应的 RST 文件中,否则即便代码注释(docstring)完整,也不会出现在 PySpark 发布文档中。仓库中的 python/docs/source/reference/index.rst 及 python/docs/source/reference/pyspark.errors.rst 等文件即承担着这一“API 清单登记”职责,文章引用的 API 若想被自动索引,也需在对应 .rst 中显式声明。

四、准备代码贡献

开始写 PySpark 代码前,除阅读官方通用指南外,还需牢记以下 PySpark 特有原则:

  • Be Pythonic:遵循《The Zen of Python》(PEP 20);
  • 与 Scala/Java 侧 API 对齐:Spark 是统一引擎,各语言 API 层保持一致;
  • 允许 PySpark 特有 API:只要 Pythonic 且不与既有 API 冲突,就可以提出新 API 请求,例如 UDF 的装饰器用法;
  • 修改/扩展公共 API 时必须同步调整类型标注:详见“六、贡献与维护类型标注”。

4.1 pandas API on Spark 的三条设计原则

如果修复 pyspark.pandas 包,请遵循以下设计原则(源码目录见 python/pyspark/pandas):

原则一:大数据返回 pandas-on-Spark 结构,小数据返回 pandas 结构。

开发者常纠结某函数应返回 pandas-on-Spark 的 DataFrame/Series 还是 pandas 结构。判定标准是结果规模:结果可能很大就用 pandas-on-Spark;结果必然很小就用 pandas。例如 DataFrame.dtypes 返回 pandas Series(列数有界且很小),而 DataFrame.head() 或 Series.unique() 返回 pandas-on-Spark 结构(结果可能很大)。

原则二:为常见数据科学任务提供可发现的 API。

pandas 偏“为常见任务提供 API”,Spark 传统上偏“提供抽象、由用户组合原语”。举例:value_counts 是最常见的数据科学操作之一,pandas 的 DataFrame.value_counts() 默认排序,90% 的探索场景下用户正需要它;而 Spark 的不排序更适合构建数据管道(用户可通过显式 orderBy 获得 pandas 行为)。pandas API on Spark 应更靠拢 pandas 风格,为常见任务提供开箱即用的可发现 API——多数情况下直接实现 pandas API 即可,但像“大数据的绘图”这类 pandas 未覆盖的需求,则可以补充 Spark 自己的实现。

原则三:内置防护栏,防止用户在数据规模放大时“搬起石头砸自己的脚”。

pandas 中某些操作随数据规模增长会变得代价高昂,不能给用户留下“在 pandas API on Spark 中也能放心依赖”的错觉。因此以下能力不实现:

  • 本质上无法并行化的能力,例如逐元素命令式循环;
  • 要求把整个工作集物化到单节点内存的能力。这是不实现 pandas.DataFrame.to_xarray 的原因;_repr_html_ 将展示的记录数上限设为 1000,也是为了防止用户在 notebook 里敲一下 DataFrame 名字就打爆 driver 节点。

少数例外服务于“大数据科学”的常见模式:初始数据集大,但分析深入后工作集会变小。典型如数据科学家对数据集做聚合后希望转为本地结构,为此提供了:

  • DataFrame.to_pandas:返回 pandas DataFrame(仅 pandas API on Spark 提供);
  • DataFrame.to_numpy:返回 numpy 数组,pandas 与 pandas API on Spark 均可用。

这些函数从命名上就明确表示返回的是本地结构、需将数据物化进单节点内存,因此文档中也都显式标注了“结果必须很小”的警告。

五、环境搭建(Prerequisite 与三种方式)

5.1 前置条件

PySpark 开发需要先构建 Spark(需要合适的 JDK),详见《Building Spark》指南。需要注意:

  • 若打算为 **Spark Connect(Python 侧)**贡献代码,还需要安装 buf(Protobuf 工具链);
  • 安装依赖要求 pip >= 25.1,旧版本请先升级:

pip install –upgrade pip

仓库中的 pyproject.toml 定义了 –group dev 所安装的开发依赖组,pip install –upgrade –group dev 会按该配置安装开发所需依赖。

5.2 方式一:Conda

# Python 3.11+ 是必需的
conda create –name pyspark-dev-env python=3.11
conda activate pyspark-dev-env
pip install –upgrade –group dev

每次开始开发前记得切换回该环境:

conda activate pyspark-dev-env

之后就可以开始开发并运行测试(测试指南见 python/docs/source/development/testing.rst)。

5.3 方式二:venv

使用 Python 内置的 venv 模块创建隔离环境:

# Python 3.11+ 是必需的
python3 -m venv .venv
source .venv/bin/activate
pip install –upgrade –group dev

5.4 方式三:纯 pip

Python 3.11+ 下可直接安装:

pip install –upgrade –group dev

三种方式殊途同归:都要求 Python 3.11+ 与 pip 25.1+,最终统一通过 pip install –upgrade –group dev 装配开发依赖。构建 Spark 后即可运行测试,仓库 python/run-tests 脚本负责测试编排。

六、贡献与维护类型标注

6.1 基本规则

PySpark 的类型标注是内联(inlined)的,以便充分享受静态类型检查。默认只为公共 API 标注,且标注应尽量满足:

  • 反映底层 JVM API 的预期,帮助在 Python 解释器之外避免类型相关失败;
  • 在过宽(Any)与过窄的参数标注冲突时,优先选择较窄的一方——只要它能覆盖绝大多数典型用法;
  • 用 @overload 标注互斥的参数组合。例如用两个 __init__ 重载表达 threshold/inputCol/outputCol 与 thresholds/inputCols/outputCols 两组参数互斥:
  • @overload
    def __init__(
    self,
    *,
    threshold: float = …,
    inputCol: Optional[str] = …,
    outputCol: Optional[str] = …
    ) -> None: …
    @overload
    def __init__(
    self,
    *,
    thresholds: Optional[List[float]] = …,
    inputCols: Optional[List[str]] = …,
    outputCols: Optional[List[str]] = …
    ) -> None: …

  • 与当前稳定版 MyPy 兼容。
  • 6.2 复杂辅助类型放 .pyi stub

    复杂的支撑类型定义应放入专门的 _typing.pyi 存根文件中,仓库中的实例是 python/pyspark/sql/_typing.pyi。

    6.3 校验方式

    可以用 dev/lint-python 脚本校验标注,也可以直接调用 mypy:

    mypy –config python/mypy.ini python/pyspark

    python/mypy.ini 是 PySpark 的 mypy 配置文件,dev/lint-python 是仓库统一的 Python 静态检查脚本(见 dev/lint-python),它同时驱动下文提到的 pycodestyle 与 ruff。

    七、代码与文档字符串规范

    7.1 代码规范(Code Conventions)

    请跟随既有代码库的风格:基本等同于 PEP 8,但每行长度上限是 100 字符而非 79。命名方面还有三条约定:

    • 与 Scala/Java 对齐的公共 API 使用 camelCase(与 Python 标准库 threading 类似,其 API 本身也受 Java 启发);
    • 与之相对,functions.py 使用 snake_case,以便 API 对 SQL(及 Python)更友好;
    • pandas-on-Spark(pyspark.pandas)也用 snake_case,因为这个包不受“与其他语言 API 一致”的约束。

    PySpark 使用 pycodestyle 与 ruff 两类 linter,均由 dev/lint-python 驱动执行,因此提交前务必运行该脚本复查。

    7.2 文档字符串规范(Docstring Conventions)

    PySpark 遵循 NumPy 文档风格(numpydoc)。

    7.3 Doctest 规范

    doctest 应按逻辑分组,组间以空行分隔。例如第一个块放准备语句、第二个块演示某个参数的用法、第三个块演示另一个参数(可参考 pandas 的 DataFrame.rsub 文档示例)。PySpark doctest 应保持这种一致的块划分;当 doctest 覆盖不足或示例数量不够时,应主动补充更多 doctest。

    八、贡献标准化的错误与异常

    8.1 为什么必须走 error class 体系

    向用户抛出标准化的错误或异常时,开发者应指定 error class 与消息参数,而不是写任意错误消息。仓库中的错误条件定义在 python/pyspark/errors/error-conditions.json(约 1258 行、上百个条目),运行时由 python/pyspark/errors/error_classes.py 读取——它用 importlib.resources 加载 JSON 并构建 ERROR_CLASSES_MAP 供全局使用。异常基类实现在 python/pyspark/errors/exceptions/base.py,其中的 PySparkException 是所有 PySpark 错误异常的基类。

    8.2 标准贡献流程(五步)

  • 查重:先检查 python/pyspark/errors/error-conditions.json 中是否已有合适的 error class;若有,直接使用并跳到第 3 步;
  • 新增 class:在 error-conditions.json 中加入新条目,同时必须遵守下文“不变量”;
  • 查继承:检查异常类型是否已继承 PySparkException;若是,跳到第 5 步;
  • 混入基类:将 PySparkException 混入(mix in)到该异常中;
  • 抛出:用 error class 与消息参数抛出异常。
  • 8.3 改造前后对比

    改造前:抛出任意错误消息——

    raise ValueError("Problem A because B")

    改造后:先在 error-conditions.json 中定义模板——

    "PROBLEM_BECAUSE": {
    "message": ["Problem <problem> because <cause>"]
    }

    在 exceptions.py 中定义异常类——

    class PySparkTestError(PySparkException):
    def __init__(self, errorClass: str, messageParameters: Dict[str, str]):
    super().__init__(errorClass=errorClass, messageParameters=messageParameters)

    def getMessageParameters(self) -> Optional[Dict[str, str]]:
    return super().getMessageParameters()

    最后用 error class 和消息参数抛出——

    raise PySparkTestError("PROBLEM_BECAUSE", {"problem": "A", "cause": "B"})

    8.4 访问错误字段

    捕获继承自 PySparkException 的异常后,用 getCondition() 读取 error class(从 python/pyspark/errors/exceptions/base.py 的实现可见,getCondition() 自 4.0.0 起成为标准接口,旧的 getErrorClass() 已标记弃用并转发到 getCondition();getMessage() 会输出 [{condition}] {message} 形式的完整消息):

    try:
    …
    except PySparkException as pe:
    if pe.getCondition() == "PROBLEM_BECAUSE":
    …

    除 getCondition() / getMessage() 外,PySparkException 还提供 getMessageParameters()(消息参数字典)与 getSqlState()(SQLSTATE 字符串,无对应值时返回 None)。

    8.5 字段与不变量

    Error class(错误类):是错误类别的简洁、人类可读表示。无法归类的错误可分配带 _LEGACY_ERROR_TEMP_ 前缀和未使用序号的传统类,例如 _LEGACY_ERROR_TEMP_0053。其不变量包括:

    • 唯一;
    • 跨版本稳定(consistent across releases);
    • 按字母序排列。

    Message(消息):提供可读的错误描述,消息格式通过 C 风格 printf 语法接收字符串参数,质量须符合 Apache Spark Error Message Guidelines。其不变量为:唯一。

    从仓库实测看,error-conditions.json 中的条目除 message 外还支持 sqlState 字段(例如 ATTRIBUTE_NOT_SUPPORTED 关联 "sqlState": "0A000"),PySparkException.getSqlState() 即据此返回;开发者新增条目时可参考既有模式,让错误同时携带 SQL 状态信息,便于 JDBC 等场景识别。错误体系的测试覆盖在 python/pyspark/errors/tests/test_errors.py 中,可在新增错误类后据此补充测试。

    九、下一步:构建与测试

    环境就绪后,测试相关细节可继续阅读 python/docs/source/development/testing.rst。简要预告其核心流程:先通过 Maven 或 SBT 构建 Spark(例如 build/mvn -DskipTests clean package 或 build/sbt -Phive clean package),再运行 python/run-tests –python-executable=python3;单测可用 python/run-tests –testnames pyspark.sql.tests.test_arrow 精确指定;若修改了 Spark Connect 的 Protobuf 定义,则需先执行 dev/connect-gen-protos.sh 重新生成 Python Protobuf 客户端;调试单个测试时可直接在测试代码中插入 breakpoint() 进入交互式 pdb 会话。

    结语

    PySpark 的贡献路径多样且规范清晰:参与 RC 投票测试能直接影响发布质量;文档贡献有独立的 Sphinx 构建链路与 API 登记要求;代码贡献强调 Pythonic、API 对齐与 pandas-on-Spark 的三条设计原则;类型标注、代码/docstring/doctest 规范均有明确可执行的检查命令;错误体系则以 error-conditions.json + PySparkException 为核心,用“查重→登记→继承→混入→抛出”五步流程保证错误消息的唯一、稳定与可编程访问。对照仓库源码,上述每一条规范都有真实实现与测试背书,可直接作为在 Apache Spark 中贡献 PySpark 的落地清单。

    赞

    分享

    • 大数据
    • 数据分析
    • 批处理
    • 流处理
    • 机器学习
    • 图计算

    【免费下载链接】spark

    Apache Spark – A unified analytics engine for large-scale data processing

    项目地址:
    https://gitcode.com/gh_mirrors/sp/spark

    点击查看 免费下载

    上一篇:
    如何彻底清理HeyGem.ai系统痕迹:三阶段完整指南

    下一篇:
    TensorNetwork收缩算法深度剖析:bucket、greedy和optimal策略对比

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

    赞(0)
    未经允许不得转载:171主机测评 » Apache Spark 的 PySpark 贡献指南:从环境搭建、代码规范到错误类体系的完整实战手册
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址