- 科学计算
- 数据分析
【免费下载链接】numpy
The fundamental package for scientific computing with Python.
导读
本文讲解 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这个模块包含了两种典型的可分配变量:
charge:real*8类型的二维可分配数组;names:character(len=10)类型的一维可分配字符数组,这正是本次修复关注的对象。
模块内的setup_data(N)子程序负责(重新)分配内存并写入初值:charge填0.0d0,names填'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的验证逻辑与发布说明完全对应:
- 通过
util.F2PyTest基类将 gh21674_alloc_char.f90 编译为测试模块; - 调用
mod.setup_data(3)触发 Fortran 侧的allocate(分配后访问的场景); - 断言
charge形状为(3, 3),验证数值数组路径正常; - 读取
names,断言形状为(3,)且 dtype 类型为 Unicode('U')或字节串('S')——这正是 f2py 对 Fortran 字符数组的标准映射。若修复前,此处在读取names时就会抛出ValueError。
注意该测试被标记为@pytest.mark.slow,运行完整测试套件时默认会包含慢测试;如使用-m "not slow"过滤则会跳过。
实践:在自己的项目中复现与使用
如果你希望在本地复现该修复并验证行为,可按以下步骤操作:
- 准备 Fortran 模块:将上文 gh21674_alloc_char.f90 的内容保存为
alloc_char_mod.f90; - 编译封装:在命令行执行 f2py 生成 Python 扩展:
python -m numpy.f2py -c alloc_char_mod.f90 -m alloc_char - 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') - 回归验证:安装本仓库源码后,可运行对应测试确认修复生效:
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.
相关推荐
使用 scikit-build 构建与打包 NumPy F2PY Fortran 扩展模块
使用 scikit build 构建与打包 NumPy F2PY Fortran 扩展模块 scikit build 为 F2PY(NumPy 的 Fortra
科学计算数据分析CPython turtle 模块修复解析:关闭屏幕后重建画面不再抛出 turtle.Terminator
CPython turtle 模块修复解析:关闭屏幕后重建画面不再抛出 turtle.Terminator 导读 本文围绕 CPython 标准库 turtle
编程语言语言运行时解释器标准库在 Windows 上使用 MSYS2 与 MinGW-w64 构建 NumPy F2PY Fortran 扩展模块
在 Windows 上使用 MSYS2 与 MinGW w64 构建 NumPy F2PY Fortran 扩展模块 本文基于 doc/source/f2py/
科学计算数据分析
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考