观潮 · 模式预报与气象 AI 观察
xarray 2026.9.0 发布:Zarr V3 变长 chunk 可读、Arrow PyCapsule 导出、flox 加速 groupby 中位数,以及 Python 3.11 的最后支持
xarray 2026.9.0 有四条新能力值得注意,其中三条和 Zarr / Arrow / 大数组性能直接相关。但最需要先看的是另一条:这是支持 Python 3.11 的最后一个版本,之后的版本一律要求 Python 3.12 及以上。
正式通知:Python 3.11 到了弃用节点
release notes 原文写得很直接:
> This is the last xarray release that will support Python 3.11. Future releases will require Python 3.12 or later.
对气象数据处理来说这条影响面很大——大量机构的 Python 环境还停在 3.11(尤其是配合 conda + 老版本 GDAL/netCDF4 的环境,升级 Python 往往牵动一串科学计算库)。现在不是"要不要升",而是"升 Python 之前得先把这一版锁住"。
如果你的项目依赖链里有 Python 3.11-only 的旧库,现在的做法是明确 pin 在 2026.9.0 或更早的版本上,等依赖链清理完再升。
四条新能力
Zarr V3 变长 chunk 的读取支持
支持读取 Zarr V3 rectilinear(variable-sized)chunks(PR #11592,作者 Tom Nicholas)。
"变长chunk"和常规 chunk 的区别是:常规 Zarr chunking 是均匀切块,变长 chunk 允许每一块的边界落在实际数据断点上。对多源拼接的气象数据(比如不同模式、不同分辨率的格点拼在一起)来说,这能显著减少填充数据。
具体差在哪,举个例子。 假设存一个 10000 天的逐时序列,均匀chunk 按 1000 天切:第 1 块是第 1–1000 天,第 2 块是 1001–2000 天……但如果实际数据只有第 5000–7000 天有值(前面是模式起报的空白期,后面是归档结束),均匀切法下这两千个有效值会被打散到 3 个 chunk 里,不可避免地在每个 chunk 里混入大量填充值。而变长 chunk 可以让chunk 边界直接落在第 5000 天和第 7001 天上,有效数据全部落在两个完整 chunk 里,填充为零。
Zarr V3 把这种能力写进了格式规范,V2 没有。这也是为什么这一条只支持读:写出变长 chunk 需要 zarr 格式和写入端同时支持,而xarray 这里的定位是读取端先兼容。
对气象数据的实际意义有两层。一层是省空间——多模式、多分辨率拼接的集合数据,填充值往往能占到三成以上。另一层是省IO:读一个 chunk 才解码一次有效数据,均匀填充的 chunk 解码出来还得再丢掉。
注意这条是只读支持(Read-only support),写侧的变化另有一条:把 BooleanCoder 从 zarr 写入中移除(PR #11318)。也就是说,如果你要生成变长 chunk 的 Zarr V3 数据,xarray 这一版还不能直接写,得靠别的工具产出、xarray 负责读。
同时还有一条 round-trip 修复:zarr v3 往返时保留 string dtype(PR #11474)——这个修复对"用 zarr 存带字符串标签的数据再读回来"的场景是必需的。
Arrow PyCapsule 导出
DataArray 新增 Arrow PyCapsule 导出,新增了 __arrow_c_stream__ 函数(PR #11338,作者 Jules Chéron)。
这条和 DataTree 支持 apply_ufunc(PR #11552)是配套的——Arrow 是把 xarray 数据零拷贝交给 DuckDB、Polars、Arrow 生态的工具。以前从 xarray 转到这些引擎需要序列化,往返一趟数据量大的时候内存峰值很难看。有了 PyCapsule 之后,xarray 数据可以直接被 Arrow 生态消费。
具体一点说,PyCapsule 是一种 C 接口约定:一方(xarray)提供一个指向内存中 Arrow 格式数据的指针,另一方(Polars / DuckDB)直接读这块内存,双方都不需要把数据转成自己的内部格式。这跟「把 numpy 数组交给 pandas」是同一类操作,区别在于 Arrow 格式本身就是列式的、且能表达嵌套和异构类型。
为什么这对气象数据有意义:一次典型的后处理是「xarray 读格点 → 转成表 → 用 SQL 或 DataFrame 算聚合 → 画图」。过去中间那步转换会让内存翻倍甚至更多(原始 + 序列化副本同时在内存里)。零拷贝之后这个峰值消掉了,对做集合预报、全球格点这类数据量大的任务比较实际。
apply_ufunc 支持 DataTree 这一点尤其有用:xarray 的 DataTree 用来表示层级化数据集(比如多层的目录结构、或者按模式成员组织的集合),之前 apply_ufunc 不支持它,很多按成员批量处理的任务只能手工循环。现在可以一次 apply_ufunc 作用到整棵树,不用再逐成员 for 循环——集合预报的批量后处理是最直接的受益场景。
flox 加速 groupby 中位数
groupby/resample 操作中的中位数计算改用 flox(PR #11239,作者 sdiebolt)。
flox 是 xarray 作者们专门为这类操作写的分组计算库。中位数计算(median)在 xarray 里长期是一个性能痛点——它不是可结合(associative)的操作,没法像 sum/mean 那样分块聚合后合并,只能把所有数据集中到一起排序。这意味着数据量大时代价极高。换成 flox 之后有明显的改进空间。
为什么中位数特别慢,值得说清楚。 sum 和 mean 满足结合律:把数据分成 100 块各自求和,再把 100 个和加起来,和一次性求总和的结果完全相同。所以并行/分块计算只要各块独立算完再合并就行。median 不满足这个性质——每个块的中位数合起来,不是整体的中位数(极端情况:99 个 1 加 1 个 1000,整体中位数是 1,各块中位数合并后可能是 1 也可能不是)。要算整体中位数,必须掌握全局排序后的第 k 个值。
这意味着中位数计算天然无法通过「切块 → 并行 → 合并」加速,只能把所有数据收到一起。这也是它在xarray 里慢的根本原因。flox 的思路不是硬把它变成可结合操作,而是换一种数据布局——按 group 预先组织好数据,让排序和取位在缓存友好的内存布局上完成。release notes 没有给出具体加速倍数,但把它单列进首段的新能力清单,说明维护者认为这是本版的主要改进之一。
对气象业务来说,中位数用在哪很具体:算某个站点某段时间的降水中位数作为阈值(用来判断"这场雨算不算异常")、算模式集合成员的中位预报(比算术平均更抗离群值)、以及在时间序列上做滑动窗口中位数滤波去除尖峰噪声。这三类都是把大量数据集中排序的形状,属于能直接吃到 flox 收益的场景。
这一版里 sdiebolt 还修了三个相关 bug,都和 transpose / groupby 有关:
- 转置后坐标变换的 shape 修复(PR #11513)
- 转置的
CoordinateTransformIndex支撑数组的对齐与拷贝修复(PR #11532) - 非前导维groupby 归约后坐标保留修复(PR #11290)
其他值得注意的变更
release notes 的首段把这一版的能力列成一句,先完整引用其范围:
> 这一版新增了 Zarr V3 变长 chunk 的读取支持、DataArray的 Arrow PyCapsule 导出、一个新的DataArray.plot.lines绘图方法、apply_ufunc对DataTree的支持,以及 flox 加速的 groupby 中位数。同时 bottleneck 默认被禁用,zarr-python 2 的剩余兼容代码已被移除。
这段话里有五项新能力 + 两项行为变更,后面逐条展开。其中 DataArray.plot.lines 和「zarr-python 2 兼容代码移除」是前面几条没展开的,先补上。
`DataArray.plot.lines`:画多序列曲线不用再绕路
新增的这个方法专门用于在同一个坐标轴上画多条线。以前画一组随时间或随纬度变化的曲线,常见做法是手动构造二维数组再循环 plot(),或者绕道 pandas.DataFrame。这个方法把这件事收进 xarray 自身。
对气象业务来说它的直接用处是:格点数据抽成时间序列、多个站点或多个模式成员叠在一张图上对比,不用再自己拼 DataFrame。
zarr-python 2 兼容代码移除
release notes 明确写了「剩余的 zarr-python 2 兼容代码已移除」。这意味着这一版起对 zarr-python 2 的兼容面进一步收窄。如果你的环境里锁着 zarr-python 2,升级 xarray 前需要先确认这批兼容代码是不是你正在用的。
和前面 Zarr V3 那条放在一起看,这一版在 Zarr 上的方向是明确的:往 V3 走,同时收掉 V2 的历史包袱。
变更 · 说明
**`bottleneck` 默认禁用**(PR #11461,@takluyver) · 如果你的工作流依赖 bottleneck 的加速(roll / 对数指数等操作的 nan 处理),默认行为变了。
**dask rolling 的 bottleneck dtype 元数据修复**(PR #11449,@mrocklin) · 与上一条配套
**h5netcdf 与 netcdf4 的编码统一**(PR #11067) · 同一个文件用不同引擎打开,编码元数据的处理现在一致了
**groupby/resample 中位数用 flox** · 见上
`spharmgrid` 与 `GeoUtils` 进ecosystem 列表 · 球谐变换与地理工具的生态位
文档从 nbsphinx/jupyter-sphinx 迁移到 myst_nb · 文档构建工具变化
会咬人的几个 bug 修复
这一版 PR 数量里有一部分是边界情况修复,值得单独拎出来:
str.replace(..., n=0)会替换所有出现而不是零个(PR #11545)——n=0的语义应该是"不替换",实际是"替换全部"。这个 bug 如果出现在你的处理脚本里,会导致数据被无声地全量改写。- 空维度上的
roll修复(PR #11613)、空RangeIndex的arange负数 size 修复(PR #11624)——空维度和空数组是典型的"平时不碰,一碰就崩"路径。 diff()对不存在的维度现在会报错(PR #11628)——以前会静默返回结果。- cftime 数组沿部分维度 rechunk 的 bug 修复(PR #11576)——对用 cftime 处理 CFTime 日期的气象数据有直接影响。
- 稀疏
COO数据在to_series()/to_dataframe()时不再 densify(PR #11528)——以前会把稀疏数据强制展开成稠密,内存会爆。 pd.DateOffset在isinstance判断中的行为变化做了适配(PR #11548)——升级 pandas 后isinstance判断语义变了,xarray 跟进了适配。
对气象数据处理最直接的三条
把上面的变更按"跟业务代码的关系"排一下,优先级和上面的分类不一样:
优先级 · 变更 · 影响
1 · `str.replace(..., n=0)` 语义修正 · **静默改写数据**,最危险
2 · cftime 数组 rechunk 修复 · 用 CFTime 处理格点时间的业务会直接受益
3 · 稀疏 `COO` 不再 densify · 内存峰值直接改善
第一条要单独说。n=0 在 pandas 的语义里是"不做替换",但 xarray 之前的实现会替换全部。这个 bug 的杀伤力在于它是静默的:脚本跑得通,结果全错。如果你的代码里有 str.replace(old, new, n=0) 这种写法,升级后行为会变,而且不会有任何警告。
第二条对气象业务最实用。CFTime 是处理 360-day calendar、no-leap calendar 这类非标准日期的标准做法,格点化的预报输出里很常见。rechunk bug 在按成员维度 rechunk 时会算错,属于结果错但不报错的那一类。
第三条在用稀疏数组时能省掉一次强制展开。原来 to_series() / to_dataframe() 会把稀疏 COO densify,百万级元素就可能把内存打满;现在保持稀疏。
作者承认的限制
- Python 3.11 只剩这一个版本。 这是明确的弃用时间表,不是"可能"。
bottleneck默认禁用这一条没有说明是否提供开关、后续是否恢复,也没有说明是否所有 bottleneck 加速路径都受影响。release notes 里只有一行 PR 引用。- Zarr V3 变长 chunk 只有读取支持,写入侧未提及。
- 这一版共有 45 位贡献者,其中 7 位是新贡献者。
变更来源的 PR 号有一处需要留意。 PyPI 页面上 2026.9.0 这个版本的变更说明引用的 GitHub release 标签是 v2026.07.0,两者版本号并不同名。这说明 xarray 的 PyPI 版本号和 GitHub release 标签在某个阶段开始不一致,引用时以 PyPI 的 2026.9.0 为准,但去找release notes 时要按 v2026.07.0 搜。
依赖数 45 这个数字没有说明是否包含可选依赖。 如果只统计核心依赖,那可选 extras(如 dask、netcdf4、arrow 相关)的实际安装体积还要另算。
一句话判断
如果你现在跑在 Python 3.11,先不要升 Python,把 xarray pin 在 <= 2026.9.0;如果你想动手升级依赖链,groupby/resample 的中位数性能和 Arrow/zero-copy 这两条是这个版本最实质的改进,值得优先验证;而 bottleneck 默认禁用这条需要你明确确认一下自己的处理脚本有没有依赖它。