三种包装方式-入门

使用f2py将fortran或c函数包装到python中包括以下步骤:

  • 创建所谓的签名文件,该文件包含Fortran或C函数包装器的描述,也称为函数的签名。对于Fortran例程,f2py可以通过扫描Fortran源代码并捕获创建包装函数所需的所有相关信息来创建初始签名文件。

  • 或者,可以编辑f2py创建的签名文件,以优化包装器功能,使其“更智能”和更多“pythonic”。

  • f2py读取签名文件并写入包含fortran/c/python绑定的python c/api模块。

  • f2py编译所有源并构建包含包装器的扩展模块。在建筑扩展模块中,f2py使用 numpy_distutils 它支持许多Fortran 77/90/95编译器,包括GNU、Intel、Sun Fortre、SGI MipsPro、AbSoft、NAG、Compaq等编译器。

根据特定情况,这些步骤可以通过一个命令或一步一步地执行,某些步骤可以省略或与其他步骤组合。

下面我将描述使用F2PY的三种典型方法。下面的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

捷径

包装Fortran子程序的最快方法 FIB 对 Python 来说就是奔跑

python -m numpy.f2py -c fib1.f -m fib1

此命令生成(请参见 -c 执行标志 python -m numpy.f2py 没有参数来查看命令行选项的解释)扩展模块 fib1.so (见 -m 标记)到当前目录。现在,在python中,fortran子例程 FIB 可通过访问 fib1.fib ::

>>> import numpy
>>> 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 = numpy.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 = numpy.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连续数组,其数据类型与假定的fortran类型相对应,用作输入数组参数时,则其C指针直接传递给fortran。

    否则,f2py将生成输入数组的连续副本(具有适当的数据类型),并将副本的C指针传递给fortran子例程。因此,对输入数组(副本)的任何可能更改都不会影响原始参数,如下所示:

    >>> a = numpy.ones(8, 'i')
    >>> fib1.fib(a)
    >>> print(a)
    [1 1 1 1 1 1 1 1]
    

    显然,这不是预期的行为。上面的例子适用于 dtype=float 被认为是偶然的。

    F2PY提供 intent(inplace) 一种属性,它可以修改输入数组的属性,从而使Fortran例程所做的任何更改在输入参数中也有效。例如,如果其中一个指定 intent(inplace) a (见下文,如何),那么上面的例子是:

    >>> a = numpy.ones(8, 'i')
    >>> fib1.fib(a)
    >>> print(a)
    [  0.   1.   1.   2.   3.   5.   8.  13.]
    

    但是,建议使用 intent(out) 属性。这是一种更高效、更清洁的解决方案。

  • 用法 fib1.fib 在python中非常类似于使用 FIB 在FORTRAN中。然而,使用 就地 python中的输出参数表示样式不好,因为python中对于错误的参数类型没有安全机制。在使用Fortran或C时,编译器会在编译期间自然地发现任何类型不匹配,但在Python中,必须在运行时检查类型。所以,使用 就地 python中的输出参数可能会导致难以找到错误,更不用说在实现所有必需的类型检查时,代码的可读性会降低。

虽然演示的将Fortran例程包装到Python中的方法非常简单,但它有几个缺点(请参见上面的注释)。这些缺点是由于这样一个事实,即f2py无法确定一个或另一个参数的实际意图是什么,它是输入或输出参数,还是两者兼而有之,或者其他什么。因此,f2py保守地假设所有参数默认都是输入参数。

但是,有一些方法(见下文)可以“教”f2py关于函数参数的真实意图(以及其他事情);然后f2py可以生成更多的pythonic(更明确、更容易使用,并且更不容易出错)包装器到fortran函数。

聪明的方法

让我们将Fortran函数逐个包装到Python中。

  • 首先,我们从 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) 属性)以及结果,即 a 调用Fortran函数后 FIB ,应返回到python(使用 intent(out) 属性)。另外,一个数组 a 应该使用输入参数给定的大小动态创建 n (使用) 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
    
  • 最后,我们通过运行

    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 更接近:给定数字 nfib2.fib 返回第一个 n 斐波那契数作为一个numpy数组。另外,新的python签名 fib2.fib 排除我们遇到的任何意外 fib1.fib .

  • 请注意,默认情况下使用single intent(out) 也意味着 intent(hide) . 具有 intent(hide) 指定的属性不会列在包装函数的参数列表中。

快速而聪明的方法

如上所述,包装Fortran函数的“智能方法”适用于包装(如第三方)Fortran代码,因为对其源代码的修改不可取,甚至不可能。

但是,如果编辑Fortran代码是可以接受的,那么在大多数情况下可以跳过中间签名文件的生成。也就是说,可以使用所谓的f2py指令将特定于f2py的属性直接插入到Fortran源代码中。f2py指令定义特殊注释行(从 Cf2py 例如)被Fortran编译器忽略,但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.]