F2PY 示例#

以下是一些 F2PY 用法的示例。此列表并不全面,但可以作为包装您自己的代码时的起点。

注意

查找示例的最佳位置是 NumPy 问题跟踪器,或 f2py 的测试用例。在 样板代码减少和模板化 中,您可以找到更多用例。

F2PY 演练:一个基本的扩展模块#

为一个基本的扩展模块创建源代码#

考虑以下子例程,它包含在一个名为 add.f 的文件中

C
      SUBROUTINE ZADD(A,B,C,N)
C
      DOUBLE COMPLEX A(*)
      DOUBLE COMPLEX B(*)
      DOUBLE COMPLEX C(*)
      INTEGER N
      DO 20 J = 1, N
         C(J) = A(J)+B(J)
 20   CONTINUE
      END

此例程只是将两个连续数组中的元素相加,并将结果放置到第三个数组中。所有三个数组的内存必须由调用例程提供。f2py 可以自动为此例程生成一个非常基本的接口

python -m numpy.f2py -m add add.f

此命令将在当前目录中生成一个名为 addmodule.c 的扩展模块。现在,这个扩展模块可以像其他任何扩展模块一样被编译和从 Python 中使用。

创建已编译的扩展模块#

您还可以让 f2py 同时编译 add.f 和生成的扩展模块,只留下一个可以在 Python 中导入的共享库扩展文件

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

此命令生成一个与您的平台兼容的 Python 扩展模块。此模块随后可以从 Python 中导入。它将为 add 中的每个子例程包含一个方法。每个方法的文档字符串包含有关如何调用模块方法的信息

>>> import add
>>> print(add.zadd.__doc__)
zadd(a,b,c,n)

Wrapper for ``zadd``.

Parameters
----------
a : input rank-1 array('D') with bounds (*)
b : input rank-1 array('D') with bounds (*)
c : input rank-1 array('D') with bounds (*)
n : input int

改进基本接口#

默认接口是对 Fortran 代码到 Python 的非常直接的翻译。Fortran 数组参数将转换为 NumPy 数组,整数参数应映射到 C 整数。该接口将尝试将所有参数转换为它们所需的类型(和形状),并在不成功时发出错误。但是,由于 f2py 不了解参数的语义(例如,C 是输出,而 n 应该真正与数组大小匹配),因此有可能以可能导致 Python 崩溃的方式滥用此函数。例如

>>> add.zadd([1, 2, 3], [1, 2], [3, 4], 1000)

将在大多数系统上导致程序崩溃。在幕后,列表被转换为数组,但随后底层的 add 函数被告知循环远远超出分配内存的边界。

为了改进接口,f2py 支持指令。这是通过构建一个签名文件来实现的。通常最好从 f2py 在该文件中生成的接口开始,这些接口对应于默认行为。要让 f2py 生成接口文件,请使用 -h 选项

python -m numpy.f2py -h add.pyf -m add add.f

此命令在当前目录中创建 add.pyf 文件。与 zadd 相对应的此文件部分是

subroutine zadd(a,b,c,n) ! in :add:add.f
   double complex dimension(*) :: a
   double complex dimension(*) :: b
   double complex dimension(*) :: c
   integer :: n
end subroutine zadd

通过放置 intent 指令和检查代码,接口可以被清理不少,因此 Python 模块方法既易于使用,又对格式错误的输入更加健壮。

subroutine zadd(a,b,c,n) ! in :add:add.f
   double complex dimension(n) :: a
   double complex dimension(n) :: b
   double complex intent(out),dimension(n) :: c
   integer intent(hide),depend(a) :: n=len(a)
end subroutine zadd

intent 指令,intent(out) 用于告诉 f2py c 是一个输出变量,应该由接口在传递到底层代码之前创建。intent(hide) 指令告诉 f2py 不要允许用户指定变量 n,而是从 a 的大小获取它。depend( a ) 指令是必要的,告诉 f2py n 的值依赖于输入 a(以便它不会在创建变量 a 之前尝试创建变量 n)。

修改完 add.pyf 后,可以通过编译 add.fadd.pyf 来生成新的 Python 模块文件

python -m numpy.f2py -c add.pyf add.f

新接口的文档字符串是

>>> import add
>>> print(add.zadd.__doc__)
c = zadd(a,b)

Wrapper for ``zadd``.

Parameters
----------
a : input rank-1 array('D') with bounds (n)
b : input rank-1 array('D') with bounds (n)

Returns
-------
c : rank-1 array('D') with bounds (n)

现在,函数可以用更加健壮的方式调用

>>> add.zadd([1, 2, 3], [4, 5, 6])
array([5.+0.j, 7.+0.j, 9.+0.j])

注意发生了自动转换为正确格式。

在 Fortran 源代码中插入指令#

上一节中健壮的接口也可以通过将变量指令作为特殊注释放置在原始 Fortran 代码中来自动生成。

注意

对于正在积极开发 Fortran 代码的项目,这可能是首选方法。

因此,如果源代码被修改为包含

C
      SUBROUTINE ZADD(A,B,C,N)
C
CF2PY INTENT(OUT) :: C
CF2PY INTENT(HIDE) :: N
CF2PY DOUBLE COMPLEX :: A(N)
CF2PY DOUBLE COMPLEX :: B(N)
CF2PY DOUBLE COMPLEX :: C(N)
      DOUBLE COMPLEX A(*)
      DOUBLE COMPLEX B(*)
      DOUBLE COMPLEX C(*)
      INTEGER N
      DO 20 J = 1, N
         C(J) = A(J) + B(J)
 20   CONTINUE
      END

那么,可以使用以下方法编译扩展模块

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

函数 add.zadd 的最终签名与之前创建的签名完全相同。如果原始源代码包含 A(N) 而不是 A(*),依此类推,对于 BC,那么通过在源代码中放置 INTENT(OUT) :: C 注释行,几乎可以获得相同的接口。唯一的区别是 N 将是一个可选的输入,它将默认为 A 的长度。

一个过滤示例#

此示例展示了一个函数,它使用固定平均过滤器过滤一个二维双精度浮点数数组。从这个例子中,使用 Fortran 对多维数组进行索引的优势应该很清楚。

C
      SUBROUTINE DFILTER2D(A,B,M,N)
C
      DOUBLE PRECISION A(M,N)
      DOUBLE PRECISION B(M,N)
      INTEGER N, M
CF2PY INTENT(OUT) :: B
CF2PY INTENT(HIDE) :: N
CF2PY INTENT(HIDE) :: M
      DO 20 I = 2,M-1
         DO 40 J = 2,N-1
            B(I,J) = A(I,J) +
     &           (A(I-1,J)+A(I+1,J) +
     &           A(I,J-1)+A(I,J+1) )*0.5D0 +
     &           (A(I-1,J-1) + A(I-1,J+1) +
     &           A(I+1,J-1) + A(I+1,J+1))*0.25D0
 40      CONTINUE
 20   CONTINUE
      END

此代码可以使用以下方法编译并链接到一个名为 filter 的扩展模块中

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

这将在当前目录中生成一个扩展模块,其中包含一个名为 dfilter2d 的方法,该方法将返回输入的过滤版本。

depends 关键字示例#

考虑以下代码,保存在文件 myroutine.f90

subroutine s(n, m, c, x)
	implicit none
  	integer, intent(in) :: n, m
  	real(kind=8), intent(out), dimension(n,m) :: x
  	real(kind=8), intent(in) :: c(:)

	x = 0.0d0
	x(1, 1) = c(1)

end subroutine s

使用 python -m numpy.f2py -c myroutine.f90 -m myroutine 将其包装起来,我们可以在 Python 中执行以下操作

>>> import numpy as np
>>> import myroutine
>>> x = myroutine.s(2, 3, np.array([5, 6, 7]))
>>> x
array([[5., 0., 0.],
   [0., 0., 0.]])

现在,我们将创建此子例程的签名文件,而不是直接生成扩展模块。这是多步扩展模块生成的常见模式。在这种情况下,运行以下命令后

python -m numpy.f2py myroutine.f90 -m myroutine -h myroutine.pyf

将生成以下签名文件

!    -*- f90 -*-
! Note: the context of this file is case sensitive.

python module myroutine ! in 
    interface  ! in :myroutine
        subroutine s(n,m,c,x) ! in :myroutine:myroutine.f90
            integer intent(in) :: n
            integer intent(in) :: m
            real(kind=8) dimension(:),intent(in) :: c
            real(kind=8) dimension(n,m),intent(out),depend(m,n) :: x
        end subroutine s
    end interface 
end python module myroutine

! This file was auto-generated with f2py (version:1.23.0.dev0+120.g4da01f42d).
! See:
! https://web.archive.org/web/20140822061353/http://cens.ioc.ee/projects/f2py2e

现在,如果我们运行 python -m numpy.f2py -c myroutine.pyf myroutine.f90,我们会看到一个错误;注意,签名文件包含一个针对 xdepend(m,n) 语句,这是不必要的。事实上,将上面的文件编辑为

!    -*- f90 -*-
! Note: the context of this file is case sensitive.

python module myroutine ! in 
    interface  ! in :myroutine
        subroutine s(n,m,c,x) ! in :myroutine:myroutine.f90
            integer intent(in) :: n
            integer intent(in) :: m
            real(kind=8) dimension(:),intent(in) :: c
            real(kind=8) dimension(n,m),intent(out) :: x
        end subroutine s
    end interface 
end python module myroutine

! This file was auto-generated with f2py (version:1.23.0.dev0+120.g4da01f42d).
! See:
! https://web.archive.org/web/20140822061353/http://cens.ioc.ee/projects/f2py2e

并运行 f2py -c myroutine.pyf myroutine.f90 将产生正确的结果。

了解更多#