三种封装方式 - 快速入门#
使用 F2PY 将 Fortran 或 C 函数封装到 Python 中,包括以下步骤:
创建所谓的 signature file ,其中包含 Fortran 或 C 函数的包装器的描述,也称为函数的签名.对于 Fortran 例程,F2PY 可以通过扫描 Fortran 源代码并跟踪创建包装函数所需的所有相关信息来创建初始签名文件.
(可选)可以编辑 F2PY 创建的签名文件,以优化包装函数,使其“更智能”和更“Python 化”.
F2PY 读取签名文件并写入包含 Fortran/C/Python 绑定的 Python C/API 模块.
F2PY 编译所有源文件并构建包含包装器的扩展模块.
在构建扩展模块时,F2PY 使用
meson并且曾经使用numpy.distutils.对于不同的构建系统,请参见 F2PY 和构建系统 .
备注
有关迁移信息,请参见 1 迁移到 meson .
根据您的操作系统,您可能需要单独安装 Python 开发头文件(提供文件
Python.h).在基于 Linux Debian 的发行版中,此软件包应称为python3-dev,在基于 Fedora 的发行版中,它称为python3-devel.对于 macOS,具体取决于 Python 的安装方式,结果可能会有所不同.在 Windows 中,通常已安装头文件,请参见 F2PY 和 Windows .
备注
F2PY 支持 SciPy 在其上测试的所有操作系统,因此他们的 system dependencies panel 是一个很好的参考.
根据情况,这些步骤可以在单个复合命令中执行,也可以逐步执行;在后一种情况下,可以省略某些步骤或将其与其他步骤组合.
下面,我们将描述三种使用 F2PY 与 Fortran 77 的典型方法.可以按难度递增的顺序阅读这些内容,而且还可满足不同级别的访问权限,具体取决于是否可以自由修改 Fortran 代码.
以下示例 Fortran 77 代码将用于说明,将其另存为 fib1.f :
C FILE: FIB1.F
SUBROUTINE FIB(A,N)
C
C CALCULATE FIRST N FIBONACCI NUMBERS
C
INTEGER N
REAL*8 A(N)
DO I=1,N
IF (I.EQ.1) THEN
A(I) = 0.0D0
ELSEIF (I.EQ.2) THEN
A(I) = 1.0D0
ELSE
A(I) = A(I-1) + A(I-2)
ENDIF
ENDDO
END
C END FILE FIB1.F
备注
F2PY 解析 Fortran/C 签名来构建用于 Python 的包装函数.但是,它不是编译器,不会检查源代码中的其他错误,也不会实现整个语言标准.某些错误可能会静默地传递(或作为警告),需要用户验证.
快速方法#
要快速包装 Fortran 子程序 FIB 以在 Python 中使用,请运行
python -m numpy.f2py -c fib1.f -m fib1
或者,如果 f2py 命令行工具可用,
f2py -c fib1.f -m fib1
备注
因为 f2py 命令可能并非在所有系统中都可用,尤其是在 Windows 上,我们将在本指南中使用 python -m numpy.f2py 命令.
此命令编译并包装 fib1.f ( -c ) 以在当前目录中创建扩展模块 fib1.so ( -m ).执行 python -m numpy.f2py 可以查看命令行选项的列表. 现在,在 Python 中,Fortran 子程序 FIB 可以通过 fib1.fib 访问:
>>> import numpy as np
>>> import fib1
>>> print(fib1.fib.__doc__)
fib(a,[n])
Wrapper for ``fib``.
Parameters
----------
a : input rank-1 array('d') with bounds (n)
Other parameters
----------------
n : input int, optional
Default: len(a)
>>> a = np.zeros(8, 'd')
>>> fib1.fib(a)
>>> print(a)
[ 0. 1. 1. 2. 3. 5. 8. 13.]
备注
请注意,F2PY 识别出第二个参数
n是第一个数组参数a的维度.由于默认情况下所有参数都是输入参数,因此 F2PY 得出结论,n可以是可选的,默认值为len(a).可以为可选的
n使用不同的值:>>> a1 = np.zeros(8, 'd') >>> fib1.fib(a1, 6) >>> print(a1) [ 0. 1. 1. 2. 3. 5. 0. 0.]
但是,当它与输入数组
a不兼容时,会引发异常:>>> fib1.fib(a, 10) Traceback (most recent call last): File "<stdin>", line 1, in <module> fib.error: (len(a)>=n) failed for 1st keyword n: fib:n=10 >>>
F2PY 实现了相关参数之间的基本兼容性检查,以避免意外崩溃.
当一个 NumPy 数组是 Fortran contiguous 并且具有与假定的 Fortran 类型相对应的
dtype时,其 C 指针将直接传递给 Fortran.否则,F2PY 将创建输入数组的连续副本(具有正确的
dtype)并将该副本的 C 指针传递给 Fortran 子程序.因此,对输入数组(的副本)的任何可能更改都不会影响原始参数,如下所示:>>> a = np.ones(8, 'i') >>> fib1.fib(a) >>> print(a) [1 1 1 1 1 1 1 1]
显然,这是出乎意料的,因为 Fortran 通常按引用传递.上面的例子适用于
dtype=float被认为是偶然的.F2PY 提供了一个
intent(inplace)属性,该属性修改输入数组的属性,以便 Fortran 例程所做的任何更改都将反映在输入参数中.例如,如果指定intent(inplace) a指令(有关详细信息,请参见 属性 ),则上面的示例将读取:>>> a = np.ones(8, 'i') >>> fib1.fib(a) >>> print(a) [ 0. 1. 1. 2. 3. 5. 8. 13.]
但是,将 Fortran 子程序所做的更改传播到 Python 的推荐方法是使用
intent(out)属性.这种方法更有效且更干净.在 Python 中使用
fib1.fib与在 Fortran 中使用FIB非常相似.但是,在 Python 中使用 in situ 输出参数是一种糟糕的风格,因为 Python 中没有安全机制来防止错误的参数类型.使用 Fortran 或 C 时,编译器会在编译过程中发现任何类型不匹配,但在 Python 中,必须在运行时检查类型.因此,在 Python 中使用 in situ 输出参数可能会导致难以发现的错误,更不用说如果实现了所有必需的类型检查,代码的可读性会降低.
虽然到目前为止讨论的用于包装 Fortran 例程的方法非常简单,但它有几个缺点(请参阅上面的评论).这些缺点是由于 F2PY 无法确定参数的实际意图;也就是说,在区分输入和输出参数时存在歧义.因此,F2PY 默认情况下假定所有参数都是输入参数.
通过“教” F2PY 有关函数参数的真实意图,有一些方法(见下文)可以消除这种歧义,然后 F2PY 能够为 Fortran 函数生成更明确,更易于使用且更不易出错的包装器.
明智的方法#
如果我们想更好地控制 F2PY 如何处理 Fortran 代码的接口,我们可以逐步应用包装步骤.
首先,我们通过运行以下命令从
fib1.f创建一个签名文件:python -m numpy.f2py fib1.f -m fib2 -h fib1.pyf
签名文件保存到
fib1.pyf(参见-h标志),其内容如下所示.! -*- f90 -*- python module fib2 ! in interface ! in :fib2 subroutine fib(a,n) ! in :fib2:fib1.f real*8 dimension(n) :: a integer optional,check(len(a)>=n),depend(a) :: n=len(a) end subroutine fib end interface end python module fib2 ! This file was auto-generated with f2py (version:2.28.198-1366). ! See http://cens.ioc.ee/projects/f2py2e/
接下来,我们将教 F2PY 参数
n是一个输入参数(使用intent(in)属性),并且结果(即在调用 Fortran 函数FIB之后a的内容)应该返回给 Python(使用intent(out)属性).此外,应该使用输入参数n确定的大小来动态创建数组a(使用depend(n)属性来指示这种依赖关系).fib1.pyf经过适当修改后的版本(保存为fib2.pyf)的内容如下:! -*- f90 -*- python module fib2 interface subroutine fib(a,n) real*8 dimension(n),intent(out),depend(n) :: a integer intent(in) :: n end subroutine fib end interface end python module fib2
最后,我们通过运行以下命令,用
numpy.distutils构建扩展模块:python -m numpy.f2py -c fib2.pyf fib1.f
在 Python 中:
>>> import fib2
>>> print(fib2.fib.__doc__)
a = fib(n)
Wrapper for ``fib``.
Parameters
----------
n : input int
Returns
-------
a : rank-1 array('d') with bounds (n)
>>> print(fib2.fib(8))
[ 0. 1. 1. 2. 3. 5. 8. 13.]
备注
fib2.fib的签名现在更接近于 Fortran 子程序FIB的意图:给定数字n,fib2.fib返回前n个斐波那契数作为一个 NumPy 数组.新的 Python 签名fib2.fib也排除了fib1.fib中意外的行为.请注意,默认情况下,使用单个
intent(out)也意味着intent(hide).指定了intent(hide)属性的参数将不会在包装器函数的参数列表中列出.
更多详情,请参见 签名文件 .
快速而巧妙的方法#
如上所述,包装 Fortran 函数的“巧妙方法”适用于包装(例如,第三方)Fortran 代码,这些代码不希望甚至不能对其源代码进行修改.
但是,如果可以编辑 Fortran 代码,则在大多数情况下可以跳过生成中间签名文件.可以使用 F2PY 指令将 F2PY 特有的属性直接插入到 Fortran 源代码中.F2PY 指令由特殊的注释行(例如,以 Cf2py 或 !f2py 开头)组成,Fortran 编译器会忽略它们,但 F2PY 将其解释为普通行.
考虑一个带有 F2PY 指令的先前 Fortran 代码的修改版本,保存为 fib3.f :
C FILE: FIB3.F
SUBROUTINE FIB(A,N)
C
C CALCULATE FIRST N FIBONACCI NUMBERS
C
INTEGER N
REAL*8 A(N)
Cf2py intent(in) n
Cf2py intent(out) a
Cf2py depend(n) a
DO I=1,N
IF (I.EQ.1) THEN
A(I) = 0.0D0
ELSEIF (I.EQ.2) THEN
A(I) = 1.0D0
ELSE
A(I) = A(I-1) + A(I-2)
ENDIF
ENDDO
END
C END FILE FIB3.F
现在可以使用一个命令构建扩展模块:
python -m numpy.f2py -c -m fib3 fib3.f
请注意,生成的 FIB 包装器与前一种情况一样“巧妙”(明确):
>>> import fib3
>>> print(fib3.fib.__doc__)
a = fib(n)
Wrapper for ``fib``.
Parameters
----------
n : input int
Returns
-------
a : rank-1 array('d') with bounds (n)
>>> print(fib3.fib(8))
[ 0. 1. 1. 2. 3. 5. 8. 13.]