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
通过放置意图指令和检查代码,可以大大清理接口,使 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(out)` 用于告知 f2py `c` 是一个输出变量,应该由接口在传递给底层代码之前创建。`intent(hide)` 指令告诉 f2py 不允许用户指定变量 `n`,而是从 `a` 的大小中获取它。`depend(a)` 指令是必需的,用于告知 f2py `n` 的值依赖于输入 `a`(因此它不会尝试在 `a` 变量创建之前创建 `n` 变量)。
修改 add.pyf 后,可以通过编译 add.f 和 add.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(*)`,并且对 `B` 和 `C` 类似,那么通过在源代码中放置 `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`,我们会看到一个错误;请注意,签名文件包含一个 `depend(m,n)` 语句,对于 `x`,这是不必要的。事实上,编辑上面的文件使其读取:
! -*- 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` 会产生正确的结果。