使用Ctypes从Python调用Fortran时返回数组

blb*_*rns 4 python arrays fortran ctypes

如何使用ctypes将数组从Fortran返回到Python?

作为一个例子,我将一个数组(长度为5)从Python传递给Fortran.使用相同的值创建输出数组.然后,它被传递回Python.在Fortran中,值是正确的,但在被发送回Python后,它们不是.我的设置怎么样不允许阵列正确传递?

我的Fortran代码(例如,test.f)包含以下内容:

SUBROUTINE mySub(inArray, lenInOut, outArray) BIND(C)

USE ISO_C_BINDING
IMPLICIT NONE

INTEGER(C_INT), INTENT(IN), VALUE :: lenInOut
REAL(C_DOUBLE), DIMENSION(lenInOut), INTENT(IN) :: inArray
REAL(C_DOUBLE), DIMENSION(lenInOut), INTENT(OUT) :: outArray

print *, "outArray from within Fortran"
do i = 1, lenInOut
  outArray(i) = inArray(i)
  print *, outArray(i)
end do
return
end subroutine mySub
Run Code Online (Sandbox Code Playgroud)

这被编译为.so:

ifort -g -O0 -fpic -traceback -c -o "test.o" "../test.f"
ifort -shared -o "mylib.so"  ./test.o
Run Code Online (Sandbox Code Playgroud)

Python代码如下:

from ctypes import *

mylib = CDLL('mylib.so')

ArrayType = c_double*5
IntType = c_int
input1 = ArrayType(1.1,2.2,3.3,4.4,5.5)
input2 = IntType(5)
inputoutput = ArrayType(0,0,0,0,0)              

mylib.mySub.argtypes = [ArrayType,IntType,ArrayType]
mylib.mySub.restype =  ArrayType

output = mylib.mySub(input1,input2,inputoutput)

print '------------------------------------------------------'  
print 'output within Python'
print output
a = [0,1,2,3,4]
for ii in a: print output[ii]
Run Code Online (Sandbox Code Playgroud)

输出结果如下:

#outArray from within Fortran
#1.10000000000000     
#2.20000000000000     
#3.30000000000000     
#4.40000000000000     
#5.50000000000000     
#------------------------------------------------------
#output within Python
#<__main__.c_double_Array_5 object at 0x10aa830>
#2.96439387505e-323
#6.91177308417e-310
#1.48219693752e-323
#6.91177308238e-310
#6.91177319086e-310
Run Code Online (Sandbox Code Playgroud)

Ale*_*ogt 6

有几件事:

  • Fortran使用引用调用,即默认情况下传递指针.但是,通过指定VALUE,您可以切换到按值调用.这只是你做的lenInOut,所以正确argtypes
mylib.mySub.argtypes = [ POINTER(c_double), c_int, POINTER(c_double) ]
Run Code Online (Sandbox Code Playgroud)
  • 你有一个子程序,而不是一个函数.所以你没有得到输出.相反,您的代码填充了数组outArray.output永远不会触及Python代码中的数组,并将打印未定义的值.正如@eryksun指出的那样,您可能希望显式声明这一点以避免从堆栈/返回值寄存器返回垃圾:
mylib.mySub.restype = None
Run Code Online (Sandbox Code Playgroud)
  • 此外,您虽然指定了bind(c),但未完全指定函数的名称.如果属性中未name指定,bind则强制使用小写,参见 Fortran 2008第15.5.2条第2款(感谢@francescalus).要避免此问题,请提供bind(c, name='mySub').

  • 虽然您指定IMPLICIT NONE,i但未声明.

进一步改进:

  • return子程序结束时不需要a .

  • 由于ctypes转换器可以处理Python int值,您可以input2 = 5直接使用(感谢@eryksun提示)

  • 所有ctypes缓冲区最初都归零,因此您可以简化inputoutputto 的初始化inputoutput = ArrayType()(感谢@eryksun提示)


完整的代码如下所示:

test.f90:

SUBROUTINE mySub(inArray, lenInOut, outArray) BIND(C, NAME='mySub')

USE ISO_C_BINDING
IMPLICIT NONE

INTEGER(C_INT), INTENT(IN), VALUE :: lenInOut
REAL(C_DOUBLE), DIMENSION(lenInOut), INTENT(IN) :: inArray
REAL(C_DOUBLE), DIMENSION(lenInOut), INTENT(OUT) :: outArray
integer :: i 

print *, "outArray from within Fortran"
do i = 1, lenInOut
  outArray(i) = inArray(i)
  print *, outArray(i)
end do

end subroutine mySub
Run Code Online (Sandbox Code Playgroud)

test.py:

from ctypes import *

mylib = CDLL('./mylib.so')
mylib.mySub.argtypes = [ POINTER(c_double), c_int, POINTER(c_double) ]
mylib.mySub.restype = None

ArrayType = c_double*5
IntType = c_int
input1 = ArrayType(1.1,2.2,3.3,4.4,5.5)
input2 = 5
inputoutput = ArrayType()              

mylib.mySub( input1, input2, inputoutput )

print '------------------------------------------------------'  
print 'output within Python'
a = [0,1,2,3,4]
for ii in a: print inputoutput[ii]
Run Code Online (Sandbox Code Playgroud)