Three ways to wrap - getting started

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

  • 创建所谓的签名文件,其中包含对Fortran或C函数的包装器的描述,也称为函数的签名。在Fortran例程的情况下,F2PY可以通过扫描Fortran源代码并捕获创建包装函数所需的所有相关信息来创建初始签名文件。
  • 可选地,可以编辑F2PY创建的签名文件以优化包装函数,使它们“更智能”和更多“Pythonic”。
  • F2PY读取签名文件并写入包含Fortran / C / Python绑定的Python C / API模块。
  • F2PY编译所有源并构建包含包装器的扩展模块。在构建扩展模块时,F2PY使用支持多个Fortran 77/90/95编译器的numpy_distutils,包括Gnu,Intel,Sun Fortre,SGI MIPSpro,Absoft,NAG,Compaq等。编译器。

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

下面我将描述使用F2PY的三种典型方法。以下示例Fortran 77代码将用于说明:

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

The quick way

将Fortran子例程FIB包装到Python的最快方法是运行

f2py -c fib1.f -m fib1

此命令构建(参见-c标志,无参数地执行f2py以查看命令行选项的解释)扩展模块fib1.so-m标志)到当前目录。现在,在Python中,Fortran子程序FIB可通过fib1.fib访问:

>>> import numpy
>>> import fib1
>>> print fib1.fib.__doc__
fib - Function signature:
  fib(a,[n])
Required arguments:
  a : input rank-1 array('d') with bounds (n)
Optional arguments:
  n := len(a) input int

>>> 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)
    fib:n=10
    Traceback (most recent call last):
      File "<stdin>", line 1, in ?
    fib.error: (len(a)>=n) failed for 1st keyword n
    >>>
    

    这演示了F2PY中的一个有用的功能,它,F2PY实现相关参数之间的基本兼容性检查,以避免任何意外的崩溃。

  • 当NumPy数组(即Fortran连续并且具有对应于假定的Fortran类型的dtype)被用作输入数组参数时,则其C指针直接传递给Fortran。

    否则,F2PY产生输入数组的连续拷贝(具有适当的dtype),并将拷贝的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.]
    

    但是,Fortran子程序返回到python的建议方法是使用intent(out)属性。它是更有效和更清洁的解决方案。

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

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

然而,有一些方法(见下文)如何“教”F2PY关于函数参数的真实意图(除其他事项外);然后F2PY能够生成更多的Pythonic(更明确,更易于使用,并且更少容易出错)包装器到Fortran函数。

The smart way

让我们应用将Fortran函数逐个包装到Python的步骤。

  • 首先,我们通过运行从fib1.f创建一个签名文件

    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)属性)。此外,应使用输入参数n(使用depend(n)属性指定的大小动态创建数组a关系)。

    修改版本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
    
  • 最后,我们通过运行构建扩展模块

    f2py -c fib2.pyf fib1.f
    

在Python中:

>>> import fib2
>>> print fib2.fib.__doc__
fib - Function signature:
  a = fib(n)
Required arguments:
  n : input int
Return objects:
  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中遇到的任何惊喜。
  • 注意,默认情况下使用单个intent(out)也意味着intent(hide)具有指定的intent(hide)属性的参数将不会列在包装器函数的参数列表中。

The quick and smart way

如上所述,包装Fortran函数的“智能方式”适合于包装(例如第三方)Fortran代码,对于它们的源代码的修改是不期望的,甚至是不可能的。

然而,如果编辑Fortran代码是可接受的,则在大多数情况下可以跳过中间签名文件的生成。也就是说,可以使用所谓的F2PY指令将F2PY特定属性直接插入到Fortran源代码中。F2PY指令定义了特殊的注释行(例如,从Cf2py开始),它们被Fortran编译器忽略,但是F2PY将它们解释为正常行。

这里显示了保存为fib3.f的示例Fortran代码的修改版本:

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

现在可以在一个命令中构建扩展模块:

f2py -c -m fib3 fib3.f

请注意,FIB的结果包装器与前一种情况一样是“智能”:

>>> import fib3
>>> print fib3.fib.__doc__
fib - Function signature:
  a = fib(n)
Required arguments:
  n : input int
Return objects:
  a : rank-1 array('d') with bounds (n)

>>> print fib3.fib(8)
[  0.   1.   1.   2.   3.   5.   8.  13.]