三种包裹方式 - 入门#

使用 F2PY 将 Fortran 或 C 函数封装到 Python 中包括以下步骤:

  • 创建所谓的 签名文件 ,其中包含对Fortran或C函数的包装描述,也称为函数的签名.对于Fortran例程,F2PY可以通过扫描Fortran源代码并跟踪创建包装函数所需的所有相关信息来创建初始签名文件.

    • 可选地,可以编辑 F2PY 创建的签名文件以优化包装函数,这可以使它们更”智能”和更”Pythonic”.

  • 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 测试的所有操作系统,因此它们的 系统依赖面板 是一个很好的参考.

根据情况,这些步骤可以在一个复合命令中一次性完成,或者一步一步地进行;在这种情况下,某些步骤可以省略或与其他步骤合并.

下面,我们描述了三种使用 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 一起使用的包装函数.然而,它不是一个编译器,并且不会检查源代码中的额外错误,也不会实现整个语言标准.一些错误可能会静默通过(或作为警告),需要用户验证.

快速方法#

在Python中使用Fortran子程序``FIB``的最快方法是运行

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 实现了基本的相关参数兼容性检查,以避免意外崩溃.

  • 当一个 Fortran 连续 的 NumPy 数组并且其 dtype 对应于一个假定的 Fortran 类型作为输入数组参数使用时,那么它的 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 例程包装为 Python 的方法非常直接,但它有几个缺点(见上文评论).这些缺点是由于 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 函数 FIBa 的内容,应该返回给 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.]