由f2py生成的fortran/c例程、公共块或fortran 90模块数据的所有包装器都以 fortran 类型对象。常规包装可调用 fortran 当Fortran数据的包装器具有引用数据对象的属性时,键入对象。
fortran
所有 fortran 类型对象具有属性 _cpointer 它包含引用对应fortran/c函数或C级变量的C指针的cobject。当这些函数的计算部分在C或Fortran中实现并用f2py(或任何其他能够提供函数的cobject的工具)包装时,这些cobject可用作f2py生成函数的回调参数,以绕过从Fortran或C调用python函数的python c/api层。
_cpointer
考虑使用Fortran 77文件 ftype.f :
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) 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 .
f2py -c ftype.f -m ftype
在 Python 中:
>>> 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标量(整数、浮点、复数)以及标量的任意序列对象(列表、元组、数组、字符串)。在后一种情况下,sequence对象的第一个元素作为标量参数传递给Fortran例程。
请注意,当需要类型转换并且可能丢失信息时(例如,当类型转换浮点到整数或复杂到浮点),f2py不会引发任何异常。在复型到实型转换中,只使用复数的实部。
intent(inout) 标量参数假定为数组对象,以便 就地 改变是有效的。建议使用具有适当类型的数组,但也可以使用其他类型的数组。
intent(inout)
考虑以下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 .
f2py -c -m scalar scalar.f
>>> 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数组 'c' 或 '1' 用作字符串参数时。
str
'c'
'1'
将字符串用作F2PY生成的包装函数的字符串参数时,字符串可以具有任意长度。如果长度大于预期值,则字符串将被截断。如果长度小于预期,则分配额外的内存并用 \0 .
\0
因为python字符串是不可变的, intent(inout) 参数需要字符串的数组版本才能 就地 改变是有效的。
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 .
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) 数组参数必须始终正确连续且具有正确的类型,否则将引发异常。另一个例外是 intent(inplace) 将更改属性的数组参数 就地 如果参数的类型与预期的不同(请参阅 intent(inplace) 属性获取更多信息)。
intent(inplace)
通常,如果numpy数组是适当的连续数组,并且具有适当的类型,那么它将直接传递给包装好的fortran/c函数。否则,将生成输入数组的逐元素副本,并将该副本作为数组参数使用,该副本是正确的连续副本,具有正确的类型。
有两种类型的适当的连续numpy数组:
fortran连续数组,当数据按列存储时,即存储在内存中的数据的索引从最低维度开始;
C—数据按行存储时的连续或简单的连续数组,即存储在内存中的数据的索引从最高维度开始。
对于一维阵列,这些概念是一致的。
例如,2x2阵列 A 如果FORTRAN元素按以下顺序存储在内存中,则它是连续的:
A
A[0,0] A[1,0] A[0,1] A[1,1]
如果顺序如下,则为C-连续:
A[0,0] A[0,1] A[1,0] A[1,1]
要测试数组是否是C-连续的,请使用 .flags.c_contiguous NumPy数组的属性。要测试Fortran的连续性,请使用 .flags.f_contiguous 属性。
.flags.c_contiguous
.flags.f_contiguous
通常不需要担心数组是如何存储在内存中的,也不需要担心包装函数(fortran或c函数)是采用一种还是另一种存储顺序。f2py自动确保包装后的函数得到具有正确存储顺序的参数;相应的算法设计为仅在绝对必要时才复制数组。但是,当处理尺寸接近计算机物理内存大小的非常大的多维输入数组时,必须注意始终使用适当的连续和适当的类型参数。
要在将输入数组传递给Fortran例程之前将其转换为列主存储顺序,请使用函数 numpy.asfortranarray(<array>) .
numpy.asfortranarray(<array>)
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 .
f2py -c -m arr array.f -DF2PY_REPORT_ON_ARRAY_COPY=1
>>> 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函数。
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 .
f2py -c -m callback callback.f
>>> 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 声明如下所示。回调参数的相同签名可以在不同的例程签名中引用。
__user__
external
intent(callback)
use
我们使用与前一个示例中相同的fortran 77代码,但现在我们将假装f2py无法正确猜测回调参数的签名。首先,我们创建一个初始签名文件 callback2.pyf 使用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 .
f2py -c callback2.pyf callback.f
示例python会话与前一个示例相同,只是参数名不同。
有时,Fortran包可能要求用户提供包将使用的例程。f2py可以构造到这些例程的接口,这样就可以从fortran中调用python函数。
考虑下面的fortran 77子例程,它接受一个数组并应用一个函数 func 它的元素。
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
预期功能 func 已从外部定义。为了使用python函数 func ,它必须具有属性 intent(callback) (必须在 external 声明)。
最后,使用 f2py -c -m foo calculate.f
f2py -c -m foo calculate.f
>>> 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子例程的python函数调用的参数包含在内,即使它是 not 在Fortran子例程参数列表中。“外部”是指由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 .
f2py -c -m pfromf extcallback.f
>>> 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 >>>
f2py生成的接口在回调参数方面非常灵活。对于每个回调参数,都有一个附加的可选参数 <name>_extra_args 由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 :
gun
如果 p == 0 然后 gun(a_1, ..., a_q) 这里叫做 q = min(m, n) .
p == 0
gun(a_1, ..., a_q)
q = min(m, n)
如果 n + p <= m 然后 gun(a_1, ..., a_n, e_1, ..., e_p) 被称为。
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 < n + p
gun(a_1, ..., a_q, e_1, ..., e_p)
q=m-p
如果 p > m 然后 gun(e_1, ..., e_m) 被称为。
p > m
gun(e_1, ..., e_m)
如果 n + p 小于的必需参数个数 gun 然后引发异常。
n + p
函数 gun 可以作为元组返回任意数量的对象。然后应用以下规则:
如果 k < l 然后 y_{{k + 1}}, ..., y_l 被忽略。
k < l
y_{{k + 1}}, ..., y_l
如果 k > l 然后,只有 x_1, ..., x_l 被设置。
k > l
x_1, ..., x_l
f2py生成包装 common 在常规签名块中定义的块。所有与当前扩展模块链接的Fortran代码都可以看到公共块,但对其他扩展模块不可见(这一限制是由Python如何导入共享库造成的)。在python中,f2py包装器 common 块是 fortran 类型具有与公共块的数据成员相关的(动态)属性的对象。当访问时,这些属性作为numpy数组对象返回(多维数组是fortran连续的),直接链接到公共块中的数据成员。可以通过直接分配或对相应数组对象进行就地更改来更改数据成员。
common
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 .
f2py -c -m common common.f
>>> 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模块数据的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 .
f2py -c -m moddata moddata.f90
>>> 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模块可分配数组有基本支持。
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 .
f2py -c -m allocarr allocarr.f90
>>> 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