news 2026/9/20 6:38:26

NumPy f2py 修复:Fortran 90 模块中 allocatable 字符数组分配后访问不再抛 ValueError

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
NumPy f2py 修复:Fortran 90 模块中 allocatable 字符数组分配后访问不再抛 ValueError
  • 科学计算
  • 数据分析

【免费下载链接】numpy

The fundamental package for scientific computing with Python.

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

导读

本文讲解 NumPy f2py 的一项 Bug 修复(变更号 gh-30971):被 f2py 封装后的 Fortran 90 模块中,allocatable可分配字符数组在 Fortran 侧完成内存分配之后,Python 侧访问该数组时不再抛出ValueError。文章以本次变更说明为主体,结合仓库中的回归测试 Fortran 源码(gh21674_alloc_char.f90)、f2py 代码生成规则(f90mod_rules.py)与运行时支撑层(fortranobject.c)展开分析。读完本文,你将理解 f2py 如何处理allocatable变量、如何复现并回归验证该问题,以及如何在真实项目中安全使用被封装的可分配字符数组。

变更概述:分配后访问不再抛 ValueError

本次变更对应的发布说明位于 doc/release/upcoming_changes/30971.change.rst,核心内容只有一句话:被 f2py 封装的 Fortran 90 模块中,allocatable字符数组在完成分配(allocation)后访问时,不再抛出ValueError

在修复之前,典型的失败场景是:用户编写一个包含character(len=...), allocatable, dimension(:) :: names这类模块变量的 Fortran 90 模块,用 f2py 编译成 Python 扩展后,先调用模块内子程序对数组进行allocate,再在 Python 侧读取mod.names,此时会因 f2py 生成代码的维度处理缺陷而抛出ValueError,导致数据无法正常取回。

该问题在仓库的测试代码中对应 gh-21674(见 test_modules.py 中的注释# gh-21674: allocatable character arrays should work after allocation),本次 30971 变更即是对该问题的正式修复确认。

复现问题所需的 Fortran 90 模块

仓库在 numpy/f2py/tests/src/modules/gh21674_alloc_char.f90 中提供了一个最小可复现的 Fortran 90 模块,内容如下:

module alloc_char_mod implicit none real*8, dimension(:,:), allocatable :: charge character(len=10), allocatable, dimension(:) :: names contains subroutine setup_data(N) integer, intent(in) :: N integer :: ierr if (allocated(charge)) deallocate(charge) if (allocated(names)) deallocate(names) allocate(charge(N, N), stat=ierr) allocate(names(N), stat=ierr) charge = 0.0d0 names = 'test' end subroutine setup_data end module alloc_char_mod

这个模块包含了两种典型的可分配变量:

  • chargereal*8类型的二维可分配数组;
  • namescharacter(len=10)类型的一维可分配字符数组,这正是本次修复关注的对象。

模块内的setup_data(N)子程序负责(重新)分配内存并写入初值:charge0.0d0names'test'。同时它体现了规范写法——先检查allocated()状态并deallocate旧内存,避免重复分配导致的内存泄漏或分配失败。

f2py 对 allocatable 模块变量的处理机制

要理解这次修复,需要先弄清 f2py 处理 Fortran 90 模块变量的底层机制。

识别 allocatable 变量

f2py 的辅助函数isallocatable定义在 numpy/f2py/auxfuncs.py:

def isallocatable(var): return 'attrspec' in var and 'allocatable' in var['attrspec']

即:只要变量的属性说明(attrspec)中包含allocatable关键字,f2py 就认为它是可分配变量。

代码生成阶段:为 allocatable 变量生成专用 getter

在 numpy/f2py/f90mod_rules.py 的模块封装代码生成逻辑中,对allocatable变量与普通变量的处理路径截然不同:

if isallocatable(var): fargs.append(f"f2py_{m['name']}_getdims_{n}") efargs.append(fargs[-1]) sargs.append( f'void (*{n})(int*,npy_intp*,void(*)(char*,npy_intp*),int*)') sargsp.append('void (*)(int*,npy_intp*,void(*)(char*,npy_intp*),int*)') iadd(f"\tf2py_{m['name']}_def[i_f2py++].func = {n};") fadd(f'subroutine {fargs[-1]}(r,s,f2pysetdata,flag)') fadd(f"use {m['name']}, only: d => {undo_rmbadname1(n)}\n") fadd('integer flag\n') fhooks[0] = fhooks[0] + fgetdims1 dms = range(1, int(dm['rank']) + 1) alloc_args = ','.join(f's({i})' for i in dms) fadd(f' allocate(d({alloc_args}))\n') fhooks[0] = fhooks[0] + use_fgetdims2 fadd(f'end subroutine {fargs[-1]}') else: fargs.append(n) sargs.append(f'char *{n}') sargsp.append('char*') iadd(f"\tf2py_{m['name']}_def[i_f2py++].data = {n};")

从源码结构可以清晰地看到两者的区别:

  • 普通(非 allocatable)变量:f2py 直接把变量的内存地址(data指针)登记进FortranDataDef定义表,Python 侧访问时直接读写该内存;
  • allocatable 变量:由于内存尚未分配,无法预先取得数据指针,因此 f2py 为其生成一个名为f2py_<module>_getdims_<var>的专用 getter 子程序,并以函数指针(.func)的形式挂载到FortranDataDef中。Python 侧在访问时才调用该 getter,查询数组当前是否已分配、各维度的实际长度,并获取数据指针。

运行时支撑:按状态分发访问路径

在 f2py 的运行时层 numpy/f2py/src/fortranobject.c 中,FortranDataDef结构通过不同字段区分两种变量:

  • 第 294 行附近:if (fp->defs[i].rank != -1)判断是 F90 allocatable 数组(见第 336 行注释);
  • 第 417 行附近:if (fp->defs[i].func != NULL)表示该变量是 allocatable,需要调用 getter 函数获取当前维度与数据;
  • 第 441 行附近:else分支对应非 allocatable 变量,直接使用登记的数据指针。

修复前的问题即出在这一分发路径上:当func != NULL时,f2py 需要先调用 Fortran 侧 getter 查询维度;若 getter 生成代码对字符数组的处理不正确(例如未正确处理character(len=*)的元素长度、维度回调参数),就会在访问已分配的字符数组时抛出不正确的ValueError。本次变更(gh-30971)修正了这一路径,使字符数组在分配后能够正确取回维度与数据。

回归测试:如何验证该修复

仓库在 numpy/f2py/tests/test_modules.py 中提供了专门的回归测试类:

@pytest.mark.slow class TestAllocatableCharacterArray(util.F2PyTest): sources = [ util.getpath("tests", "src", "modules", "gh21674_alloc_char.f90") ] def test_allocatable_char_roundtrip(self): # gh-21674: allocatable character arrays should work after allocation mod = self.module.alloc_char_mod mod.setup_data(3) assert mod.charge.shape == (3, 3) names = mod.names assert names.shape == (3,) assert names.dtype.kind == 'U' or names.dtype.kind == 'S'

测试用例test_allocatable_char_roundtrip的验证逻辑与发布说明完全对应:

  1. 通过util.F2PyTest基类将 gh21674_alloc_char.f90 编译为测试模块;
  2. 调用mod.setup_data(3)触发 Fortran 侧的allocate(分配后访问的场景);
  3. 断言charge形状为(3, 3),验证数值数组路径正常;
  4. 读取names,断言形状为(3,)且 dtype 类型为 Unicode('U')或字节串('S')——这正是 f2py 对 Fortran 字符数组的标准映射。若修复前,此处在读取names时就会抛出ValueError

注意该测试被标记为@pytest.mark.slow,运行完整测试套件时默认会包含慢测试;如使用-m "not slow"过滤则会跳过。

实践:在自己的项目中复现与使用

如果你希望在本地复现该修复并验证行为,可按以下步骤操作:

  1. 准备 Fortran 模块:将上文 gh21674_alloc_char.f90 的内容保存为alloc_char_mod.f90
  2. 编译封装:在命令行执行 f2py 生成 Python 扩展:
    python -m numpy.f2py -c alloc_char_mod.f90 -m alloc_char
  3. Python 侧验证
    import alloc_char mod = alloc_char.alloc_char_mod mod.setup_data(3) # Fortran 侧分配内存并初始化 print(mod.charge.shape) # (3, 3) print(mod.names) # array(['test', 'test', 'test'], dtype='<U10')
  4. 回归验证:安装本仓库源码后,可运行对应测试确认修复生效:
    python -m pytest numpy/f2py/tests/test_modules.py::TestAllocatableCharacterArray

关键文件速查

  • 变更说明:doc/release/upcoming_changes/30971.change.rst
  • 回归测试 Fortran 源码:numpy/f2py/tests/src/modules/gh21674_alloc_char.f90
  • 回归测试用例:numpy/f2py/tests/test_modules.py
  • allocatable 判定函数:numpy/f2py/auxfuncs.py
  • 模块封装代码生成:numpy/f2py/f90mod_rules.py
  • 运行时维度/数据分发:numpy/f2py/src/fortranobject.c

总结

gh-30971 变更修复了 f2py 封装 Fortran 90 模块时,allocatable字符数组在分配后访问抛出ValueError的问题。f2py 通过"为 allocatable 变量生成 getter 子程序并以函数指针挂载"的机制延迟获取维度与数据指针,本次修复确保该机制对字符数组同样正确生效。仓库中的回归测试(gh-21674 用例)从形状、dtype 与取值三个层面验证了分配后读取的完整往返(roundtrip)行为,为使用者提供了可直接复用的范例。

  • 科学计算
  • 数据分析

【免费下载链接】numpy

The fundamental package for scientific computing with Python.

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

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 6:37:27

OpenClaw智能体开发框架:构建人格化AI助手的技术解析

1. 项目概述OpenClaw作为新一代智能体开发框架&#xff0c;其Agent抽象层设计理念正在重塑我们构建"人格化"助手的方式。在传统大模型应用开发中&#xff0c;开发者往往需要直接处理原始API调用、上下文管理和输出解析等底层细节&#xff0c;这种开发模式既低效又难以…

作者头像 李华
网站建设 2026/9/20 6:36:06

GxP过程控制系统验证:基于风险的关键性评估与FMEA实践

简介&#xff1a;这份ISPE GAMP良好实践指南第二版&#xff0c;面向制药与生物技术企业的验证、质量及自动化工程师&#xff0c;帮助其以基于风险的方法设计、实施和维护GxP过程控制系统&#xff0c;并应对FDA 21 CFR Part 11等法规要求。压缩包内仅含1个PDF文件&#xff0c;约…

作者头像 李华
网站建设 2026/9/20 6:35:05

RustDesk 自托管远程桌面部署实战

RustDesk 自托管远程桌面部署实战 【免费下载链接】rustdesk An open-source remote desktop application designed for self-hosting, as an alternative to TeamViewer. 项目地址: https://gitcode.com/GitHub_Trending/ru/rustdesk 晚上九点&#xff0c;客户电脑卡死…

作者头像 李华
网站建设 2026/9/20 6:33:59

如何完整备份QQ空间历史说说:GetQzonehistory使用指南

如何完整备份QQ空间历史说说&#xff1a;GetQzonehistory使用指南 【免费下载链接】GetQzonehistory 获取QQ空间发布的历史说说 项目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 想翻一条几年前发的说说&#xff0c;时间线里却只剩近几天的记录。Get…

作者头像 李华
网站建设 2026/9/20 6:32:56

HarmonyOS Web调试实战:DevTools完整使用指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华