在 Python 中使用 F2PY 绑定#
在本页中,您可以找到 F2PY 与 Python 及不同参数类型的常见用法模式的完整描述和一些示例。更多示例和用例,请参见 F2PY 示例。
Fortran 类型对象#
F2PY 生成的所有 Fortran/C 过程、公共块或 Fortran 90 模块数据的包装器在 Python 中都显示为 fortran
类型对象。过程包装器是可调用的 fortran
类型对象,而 Fortran 数据的包装器具有引用数据对象的属性。
所有 fortran
类型对象都具有一个属性 _cpointer
,该属性包含一个 PyCapsule
,它在 C 级指向相应的 Fortran/C 函数或变量的 C 指针。此类 PyCapsule
对象可以用作 F2PY 生成的函数的回调参数,以绕过 Python C/API 层,从而从 Fortran 或 C 调用 Python 函数。当此类函数的计算方面在 C 或 Fortran 中实现并使用 F2PY(或任何其他能够提供包含函数的 PyCapsule
的工具)包装时,这将非常有用。
考虑一个 Fortran 77 文件 `ftype.f
C FILE: FTYPE.F
SUBROUTINE FOO(N)
INTEGER N
Cf2py integer optional,intent(in) :: n = 13
REAL A,X
COMMON /DATA/ A,X(3)
C PRINT*, "IN FOO: N=",N," A=",A," X=[",X(1),X(2),X(3),"]"
END
C END OF FTYPE.F
并使用 f2py -c ftype.f -m ftype
构建包装器。
在 Python 中,您可以观察 foo
和 data
的类型,以及如何访问包装的 Fortran 代码的各个对象。
>>> import ftype
>>> print(ftype.__doc__)
This module 'ftype' is auto-generated with f2py (version:2).
Functions:
foo(n=13)
COMMON blocks:
/data/ a,x(3)
.
>>> type(ftype.foo), type(ftype.data)
(<class 'fortran'>, <class 'fortran'>)
>>> ftype.foo()
IN FOO: N= 13 A= 0. X=[ 0. 0. 0.]
>>> ftype.data.a = 3
>>> ftype.data.x = [1,2,3]
>>> ftype.foo()
IN FOO: N= 13 A= 3. X=[ 1. 2. 3.]
>>> ftype.data.x[1] = 45
>>> ftype.foo(24)
IN FOO: N= 24 A= 3. X=[ 1. 45. 3.]
>>> ftype.data.x
array([ 1., 45., 3.], dtype=float32)
标量参数#
一般来说,F2PY 生成的包装器函数的标量参数可以是普通的 Python 标量(整数、浮点数、复数),也可以是标量的任意序列对象(列表、元组、数组、字符串)。在后一种情况下,序列对象的第一个元素将作为标量参数传递给 Fortran 过程。
注意
当需要类型转换并且可能通过缩小(例如,将浮点数转换为整数或复数转换为浮点数)导致信息丢失时,F2PY *不会*引发异常。
对于复数到实数的类型转换,仅使用复数的实部。
intent(inout)
标量参数假定为数组对象,以便 *就地* 更改有效。建议使用具有正确类型的数组,但其他类型也可以工作。阅读有关 intent 属性的更多信息。
考虑以下 Fortran 77 代码
C FILE: SCALAR.F
SUBROUTINE FOO(A,B)
REAL*8 A, B
Cf2py intent(in) a
Cf2py intent(inout) b
PRINT*, " A=",A," B=",B
PRINT*, "INCREMENT A AND B"
A = A + 1D0
B = B + 1D0
PRINT*, "NEW A=",A," B=",B
END
C END OF FILE SCALAR.F
并使用 f2py -c -m scalar scalar.f
包装它。
在 Python 中
>>> import scalar
>>> print(scalar.foo.__doc__)
foo(a,b)
Wrapper for ``foo``.
Parameters
----------
a : input float
b : in/output rank-0 array(float,'d')
>>> scalar.foo(2, 3)
A= 2. B= 3.
INCREMENT A AND B
NEW A= 3. B= 4.
>>> import numpy
>>> a = numpy.array(2) # these are integer rank-0 arrays
>>> b = numpy.array(3)
>>> scalar.foo(a, b)
A= 2. B= 3.
INCREMENT A AND B
NEW A= 3. B= 4.
>>> print(a, b) # note that only b is changed in situ
2 4
字符串参数#
F2PY 生成的包装器函数接受几乎任何 Python 对象作为字符串参数,因为对于非字符串对象会应用 str
。例外情况是 NumPy 数组,当用作字符串参数时,必须具有类型代码 'S1'
或 'b'
(分别对应于过时的 'c'
或 '1'
类型代码)。有关这些类型代码的更多信息,请参见 标量。
用作 F2PY 生成的包装器函数的字符串参数的字符串可以具有任意长度。如果长度大于预期,则字符串将被静默截断。如果长度小于预期,则会分配额外的内存并填充 \0
。
因为 Python 字符串是不可变的,所以 intent(inout)
参数需要字符串的数组版本才能使 *就地* 更改有效。
考虑以下 Fortran 77 代码
C FILE: STRING.F
SUBROUTINE FOO(A,B,C,D)
CHARACTER*5 A, B
CHARACTER*(*) C,D
Cf2py intent(in) a,c
Cf2py intent(inout) b,d
PRINT*, "A=",A
PRINT*, "B=",B
PRINT*, "C=",C
PRINT*, "D=",D
PRINT*, "CHANGE A,B,C,D"
A(1:1) = 'A'
B(1:1) = 'B'
C(1:1) = 'C'
D(1:1) = 'D'
PRINT*, "A=",A
PRINT*, "B=",B
PRINT*, "C=",C
PRINT*, "D=",D
END
C END OF FILE STRING.F
并使用 f2py -c -m mystring string.f
包装它。
Python 会话
>>> import mystring
>>> print(mystring.foo.__doc__)
foo(a,b,c,d)
Wrapper for ``foo``.
Parameters
----------
a : input string(len=5)
b : in/output rank-0 array(string(len=5),'c')
c : input string(len=-1)
d : in/output rank-0 array(string(len=-1),'c')
>>> from numpy import array
>>> a = array(b'123\0\0')
>>> b = array(b'123\0\0')
>>> c = array(b'123')
>>> d = array(b'123')
>>> mystring.foo(a, b, c, d)
A=123
B=123
C=123
D=123
CHANGE A,B,C,D
A=A23
B=B23
C=C23
D=D23
>>> a[()], b[()], c[()], d[()]
(b'123', b'B23', b'123', b'D2')
数组参数#
一般来说,F2PY 生成的包装器函数的数组参数接受可以转换为 NumPy 数组对象的任意序列。有两个值得注意的例外情况
intent(inout)
数组参数必须始终是 正确的连续的 并具有兼容的dtype
,否则将引发异常。intent(inplace)
数组参数如果参数的类型与预期类型不同,则将 *就地* 更改(有关更多信息,请参见intent(inplace)
属性)。
一般来说,如果 NumPy 数组是 正确的连续的 并具有正确的类型,则它将直接传递给包装的 Fortran/C 函数。否则,将创建输入数组的逐元素副本,并将该副本(为正确的连续的且具有正确的类型)用作数组参数。
通常无需担心数组在内存中的存储方式以及包装的函数(无论是 Fortran 函数还是 C 函数)是否假定一种或另一种存储顺序。F2PY 自动确保包装的函数获得具有正确存储顺序的参数;底层算法旨在仅在绝对必要时才复制数组。但是,当处理非常大的多维输入数组时,其大小接近计算机物理内存的大小,则必须注意确保使用正确的连续的和正确的类型参数。
要在将输入数组传递给 Fortran 过程之前将其转换为列主序存储顺序,请使用函数 numpy.asfortranarray
。
考虑以下 Fortran 77 代码
C FILE: ARRAY.F
SUBROUTINE FOO(A,N,M)
C
C INCREMENT THE FIRST ROW AND DECREMENT THE FIRST COLUMN OF A
C
INTEGER N,M,I,J
REAL*8 A(N,M)
Cf2py intent(in,out,copy) a
Cf2py integer intent(hide),depend(a) :: n=shape(a,0), m=shape(a,1)
DO J=1,M
A(1,J) = A(1,J) + 1D0
ENDDO
DO I=1,N
A(I,1) = A(I,1) - 1D0
ENDDO
END
C END OF FILE ARRAY.F
并使用 f2py -c -m arr array.f -DF2PY_REPORT_ON_ARRAY_COPY=1
包装它。
在 Python 中
>>> import arr
>>> from numpy import asfortranarray
>>> print(arr.foo.__doc__)
a = foo(a,[overwrite_a])
Wrapper for ``foo``.
Parameters
----------
a : input rank-2 array('d') with bounds (n,m)
Other Parameters
----------------
overwrite_a : input int, optional
Default: 0
Returns
-------
a : rank-2 array('d') with bounds (n,m)
>>> a = arr.foo([[1, 2, 3],
... [4, 5, 6]])
created an array from object
>>> print(a)
[[ 1. 3. 4.]
[ 3. 5. 6.]]
>>> a.flags.c_contiguous
False
>>> a.flags.f_contiguous
True
# even if a is proper-contiguous and has proper type,
# a copy is made forced by intent(copy) attribute
# to preserve its original contents
>>> b = arr.foo(a)
copied an array: size=6, elsize=8
>>> print(a)
[[ 1. 3. 4.]
[ 3. 5. 6.]]
>>> print(b)
[[ 1. 4. 5.]
[ 2. 5. 6.]]
>>> b = arr.foo(a, overwrite_a = 1) # a is passed directly to Fortran
... # routine and its contents is discarded
...
>>> print(a)
[[ 1. 4. 5.]
[ 2. 5. 6.]]
>>> print(b)
[[ 1. 4. 5.]
[ 2. 5. 6.]]
>>> a is b # a and b are actually the same objects
True
>>> print(arr.foo([1, 2, 3])) # different rank arrays are allowed
created an array from object
[ 1. 1. 2.]
>>> print(arr.foo([[[1], [2], [3]]]))
created an array from object
[[[ 1.]
[ 1.]
[ 2.]]]
>>>
>>> # Creating arrays with column major data storage order:
...
>>> s = asfortranarray([[1, 2, 3], [4, 5, 6]])
>>> s.flags.f_contiguous
True
>>> print(s)
[[1 2 3]
[4 5 6]]
>>> print(arr.foo(s))
>>> s2 = asfortranarray(s)
>>> s2 is s # an array with column major storage order
# is returned immediately
True
>>> # Note that arr.foo returns a column major data storage order array:
...
>>> s3 = ascontiguousarray(s)
>>> s3.flags.f_contiguous
False
>>> s3.flags.c_contiguous
True
>>> s3 = arr.foo(s3)
copied an array: size=6, elsize=8
>>> s3.flags.f_contiguous
True
>>> s3.flags.c_contiguous
False
回调参数#
F2PY 支持从 Fortran 或 C 代码调用 Python 函数。
考虑以下 Fortran 77 代码
C FILE: CALLBACK.F
SUBROUTINE FOO(FUN,R)
EXTERNAL FUN
INTEGER I
REAL*8 R, FUN
Cf2py intent(out) r
R = 0D0
DO I=-5,5
R = R + FUN(I)
ENDDO
END
C END OF FILE CALLBACK.F
并使用 f2py -c -m callback callback.f
包装它。
在 Python 中
>>> import callback
>>> print(callback.foo.__doc__)
r = foo(fun,[fun_extra_args])
Wrapper for ``foo``.
Parameters
----------
fun : call-back function
Other Parameters
----------------
fun_extra_args : input tuple, optional
Default: ()
Returns
-------
r : float
Notes
-----
Call-back functions::
def fun(i): return r
Required arguments:
i : input int
Return objects:
r : float
>>> def f(i): return i*i
...
>>> print(callback.foo(f))
110.0
>>> print(callback.foo(lambda i:1))
11.0
在上面的示例中,F2PY 能够准确地猜测回调函数的签名。但是,有时 F2PY 无法确定合适的签名;在这些情况下,必须在签名文件中明确定义回调函数的签名。
为方便起见,签名文件可能包含特殊的模块(这些模块的名称包含特殊的 __user__
子字符串),这些模块定义了回调函数的各种签名。过程签名中的回调参数具有 external
属性(另请参见 intent(callback)
属性)。为了将回调参数与其在 __user__
模块块中的签名关联起来,可以使用 use
语句,如下所示。同一个回调参数的相同签名可以在不同的过程签名中引用。
我们使用与前面示例中相同的 Fortran 77 代码,但现在我们将假装 F2PY 无法正确猜测回调参数的签名。首先,我们使用 F2PY 创建一个初始签名文件 callback2.pyf
f2py -m callback2 -h callback2.pyf callback.f
然后对其进行如下修改
! -*- f90 -*-
python module __user__routines
interface
function fun(i) result (r)
integer :: i
real*8 :: r
end function fun
end interface
end python module __user__routines
python module callback2
interface
subroutine foo(f,r)
use __user__routines, f=>fun
external f
real*8 intent(out) :: r
end subroutine foo
end interface
end python module callback2
最后,我们使用 f2py -c callback2.pyf callback.f
构建扩展模块。
此代码段的示例 Python 会话将与前面的示例相同,只是参数名称会有所不同。
有时,Fortran 包可能要求用户提供该包将使用的例程。F2PY 可以构建此类例程的接口,以便可以从 Fortran 调用 Python 函数。
考虑以下 Fortran 77 子例程,它将数组作为输入并将其函数 func
应用于其元素。
subroutine calculate(x,n)
cf2py intent(callback) func
external func
c The following lines define the signature of func for F2PY:
cf2py real*8 y
cf2py y = func(y)
c
cf2py intent(in,out,copy) x
integer n,i
real*8 x(n), func
do i=1,n
x(i) = func(x(i))
end do
end
Fortran 代码期望函数 func
已在外部定义。为了将 Python 函数用于 func
,它必须具有属性 intent(callback)
,并且必须在 external
语句之前指定。
最后,使用 f2py -c -m foo calculate.f
构建扩展模块
在 Python 中
>>> import foo
>>> foo.calculate(range(5), lambda x: x*x)
array([ 0., 1., 4., 9., 16.])
>>> import math
>>> foo.calculate(range(5), math.exp)
array([ 1. , 2.71828183, 7.3890561, 20.08553692, 54.59815003])
即使Fortran子程序的参数列表中*没有*该函数,它也作为参数包含在对Fortran子程序的Python函数调用中。“external”关键字指的是f2py生成的C函数,而不是Python函数本身。Python函数实际上是提供给C函数的。
回调函数也可以在模块中显式设置。然后,没有必要在Fortran函数的参数列表中传递该函数。如果调用Python回调函数的Fortran函数本身由另一个Fortran函数调用,则可能需要这样做。
考虑以下Fortran 77子程序
subroutine f1()
print *, "in f1, calling f2 twice.."
call f2()
call f2()
return
end
subroutine f2()
cf2py intent(callback, hide) fpy
external fpy
print *, "in f2, calling f2py.."
call fpy()
return
end
并使用f2py -c -m pfromf extcallback.f
进行封装。
在 Python 中
>>> import pfromf
>>> pfromf.f2()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
pfromf.error: Callback fpy not defined (as an argument or module pfromf attribute).
>>> def f(): print("python f")
...
>>> pfromf.fpy = f
>>> pfromf.f2()
in f2, calling f2py..
python f
>>> pfromf.f1()
in f1, calling f2 twice..
in f2, calling f2py..
python f
in f2, calling f2py..
python f
>>>
注意
当通过callstatement
或其他指令使用修改后的Fortran代码时,必须将封装的Python函数作为回调函数调用,否则只会使用原始的Fortran例程。更多详情,请参见numpy/numpy#26681
解决回调函数的参数#
F2PY生成的接口在回调参数方面非常灵活。对于每个回调参数,F2PY都会引入一个额外的可选参数<name>_extra_args
。此参数可用于向用户提供的回调函数传递额外参数。
如果F2PY生成的包装器函数期望以下回调参数
def fun(a_1,...,a_n):
...
return x_1,...,x_k
但用户提供了以下Python函数
def gun(b_1,...,b_m):
...
return y_1,...,y_l
并且此外,
fun_extra_args = (e_1,...,e_p)
被使用,那么当Fortran或C函数计算回调参数gun
时,将应用以下规则:
如果
p == 0
,则调用gun(a_1, ..., a_q)
,这里q = min(m, n)
。如果
n + p <= m
,则调用gun(a_1, ..., a_n, e_1, ..., e_p)
。如果
p <= m < n + p
,则调用gun(a_1, ..., a_q, e_1, ..., e_p)
,这里q=m-p
。如果
p > m
,则调用gun(e_1, ..., e_m)
。如果
n + p
小于gun
所需参数的数量,则会引发异常。
如果函数gun
可以返回任意数量的对象作为元组;则应用以下规则:
如果
k < l
,则忽略y_{k + 1}, ..., y_l
。如果
k > l
,则只设置x_1, ..., x_l
。
公共块#
F2PY 生成对在例程签名块中定义的common
块的包装器。公共块对链接到当前扩展模块的所有Fortran代码可见,但不适用于其他扩展模块(此限制是由于Python导入共享库的方式造成的)。在Python中,common
块的F2PY包装器是fortran
类型的对象,它们具有与公共块的数据成员相关的(动态)属性。访问时,这些属性作为NumPy数组对象返回(多维数组是Fortran连续的),这些对象直接链接到公共块中的数据成员。可以通过直接赋值或对相应数组对象的原地更改来更改数据成员。
考虑以下 Fortran 77 代码
C FILE: COMMON.F
SUBROUTINE FOO
INTEGER I,X
REAL A
COMMON /DATA/ I,X(4),A(2,3)
PRINT*, "I=",I
PRINT*, "X=[",X,"]"
PRINT*, "A=["
PRINT*, "[",A(1,1),",",A(1,2),",",A(1,3),"]"
PRINT*, "[",A(2,1),",",A(2,2),",",A(2,3),"]"
PRINT*, "]"
END
C END OF COMMON.F
并使用f2py -c -m common common.f
进行封装。
在 Python 中
>>> import common
>>> print(common.data.__doc__)
i : 'i'-scalar
x : 'i'-array(4)
a : 'f'-array(2,3)
>>> common.data.i = 5
>>> common.data.x[1] = 2
>>> common.data.a = [[1,2,3],[4,5,6]]
>>> common.foo()
>>> common.foo()
I= 5
X=[ 0 2 0 0 ]
A=[
[ 1.00000000 , 2.00000000 , 3.00000000 ]
[ 4.00000000 , 5.00000000 , 6.00000000 ]
]
>>> common.data.a[1] = 45
>>> common.foo()
I= 5
X=[ 0 2 0 0 ]
A=[
[ 1.00000000 , 2.00000000 , 3.00000000 ]
[ 45.0000000 , 45.0000000 , 45.0000000 ]
]
>>> common.data.a # a is Fortran-contiguous
array([[ 1., 2., 3.],
[ 45., 45., 45.]], dtype=float32)
>>> common.data.a.flags.f_contiguous
True
Fortran 90模块数据#
Fortran 90模块数据的F2PY接口类似于Fortran 77公共块的处理方式。
考虑以下Fortran 90代码
module mod
integer i
integer :: x(4)
real, dimension(2,3) :: a
real, allocatable, dimension(:,:) :: b
contains
subroutine foo
integer k
print*, "i=",i
print*, "x=[",x,"]"
print*, "a=["
print*, "[",a(1,1),",",a(1,2),",",a(1,3),"]"
print*, "[",a(2,1),",",a(2,2),",",a(2,3),"]"
print*, "]"
print*, "Setting a(1,2)=a(1,2)+3"
a(1,2) = a(1,2)+3
end subroutine foo
end module mod
并使用f2py -c -m moddata moddata.f90
进行封装。
在 Python 中
>>> import moddata
>>> print(moddata.mod.__doc__)
i : 'i'-scalar
x : 'i'-array(4)
a : 'f'-array(2,3)
b : 'f'-array(-1,-1), not allocated
foo()
Wrapper for ``foo``.
>>> moddata.mod.i = 5
>>> moddata.mod.x[:2] = [1,2]
>>> moddata.mod.a = [[1,2,3],[4,5,6]]
>>> moddata.mod.foo()
i= 5
x=[ 1 2 0 0 ]
a=[
[ 1.000000 , 2.000000 , 3.000000 ]
[ 4.000000 , 5.000000 , 6.000000 ]
]
Setting a(1,2)=a(1,2)+3
>>> moddata.mod.a # a is Fortran-contiguous
array([[ 1., 5., 3.],
[ 4., 5., 6.]], dtype=float32)
>>> moddata.mod.a.flags.f_contiguous
True
可分配数组#
F2PY基本支持Fortran 90模块可分配数组。
考虑以下Fortran 90代码
module mod
real, allocatable, dimension(:,:) :: b
contains
subroutine foo
integer k
if (allocated(b)) then
print*, "b=["
do k = 1,size(b,1)
print*, b(k,1:size(b,2))
enddo
print*, "]"
else
print*, "b is not allocated"
endif
end subroutine foo
end module mod
并使用f2py -c -m allocarr allocarr.f90
进行封装。
在 Python 中
>>> import allocarr
>>> print(allocarr.mod.__doc__)
b : 'f'-array(-1,-1), not allocated
foo()
Wrapper for ``foo``.
>>> allocarr.mod.foo()
b is not allocated
>>> allocarr.mod.b = [[1, 2, 3], [4, 5, 6]] # allocate/initialize b
>>> allocarr.mod.foo()
b=[
1.000000 2.000000 3.000000
4.000000 5.000000 6.000000
]
>>> allocarr.mod.b # b is Fortran-contiguous
array([[ 1., 2., 3.],
[ 4., 5., 6.]], dtype=float32)
>>> allocarr.mod.b.flags.f_contiguous
True
>>> allocarr.mod.b = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] # reallocate/initialize b
>>> allocarr.mod.foo()
b=[
1.000000 2.000000 3.000000
4.000000 5.000000 6.000000
7.000000 8.000000 9.000000
]
>>> allocarr.mod.b = None # deallocate array
>>> allocarr.mod.foo()
b is not allocated