python-fortran集成:f2py和ctypes的回调比较

python-fortran integration: callback comparison between f2py and ctypes

我在 https://numpy.org/devdocs/f2py/python-usage.html#call-back-arguments 中找到了一个有启发性的示例。 这里是 fortran 例程:

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__)
def f(i):
    return i * i
print(callback.foo(f))

一切正常。现在,我想使用 ctypes 重复测试。 我可以使用以下命令轻松编译 Fortran 源代码:gfortran -shared callback.f -o callback.dll 我可以加载库:

import ctypes as ct
import numpy as np
# import the dll
fortlib = ct.CDLL('callback.dll')

问题:

提前致谢。詹马可

平台:Anaconda python 3.7.6,Mingw-64 Windows 10

良好的编程风格要求我们永远不要使用单字符变量名。 Fortran 子例程的现代 Fortran-2008 实现类似于以下内容:

module foo_mod

    use iso_c_binding, only: RK => c_double, IK => c_int32_t
    implicit none

    abstract interface
        function getFunVal_proc(inputInteger) result(funVal) bind(C)
            import :: RK, IK
            implicit none
            integer(IK), intent(in), value :: inputInteger
            real(RK) :: funVal
        end function getFunVal_proc
    end interface

contains

    subroutine getFoo(getFunValFromC,outputReal) bind(C,name="getFoo")
        !DEC$ ATTRIBUTES DLLEXPORT :: getFoo
        use, intrinsic :: iso_c_binding, only: c_funptr, c_f_procpointer
        implicit none
        type(c_funptr), intent(in), value   :: getFunValFromC
        procedure(getFunVal_proc), pointer  :: getFunVal
        real(RK), intent(out)               :: outputReal
        integer(IK)                         :: indx

        ! associate the input C procedure pointer to a Fortran procedure pointer
        call c_f_procpointer(cptr=getFunValFromC, fptr=getFunVal)

        outputReal = 0._RK
        do indx = -5,5
            write(*,"(*(g0,:,' '))") "value of indx from inside Fortran: ", indx
            outputReal = outputReal + getFunVal(indx)
        end do

        write(*,"(*(g0,:,' '))") "value of outputReal from inside Fortran: ", outputReal

        ! nullify the Fortran pointer
        nullify(getFunVal)

    end subroutine getFoo

end module foo_mod

这看起来有点冗长,但比 F77 好得多。毕竟,我们生活在 21 世纪。然后你会通过 Intel ifort 编译这个 Fortran 代码,例如,

ifort /dll /threads /libs:static foo_mod.f90 /exe:foo.dll

然后,您将从生成的 DLL foo.dll 中调用 getFoo(),如以下 Python 脚本,

import ctypes as ct
import numpy as np

# This is the Python callback function to be passed to Fortran
def getSquare(inputInteger):
    print("value of indx received by getSquare() inside Python: ",inputInteger)
    return np.double(inputInteger**2)

# define ctypes wrapper function, with the proper result and argument types
getFunVal_proc =    ct.CFUNCTYPE( ct.c_double                  # callback (python) function result
                                , ct.c_int32                   # callback (python) function input integer argument
                                )
getSquare_pntr = getFunVal_proc(getSquare)

libpath = "foo.dll"
try:

    # open DLL
    foolib = ct.CDLL(libpath)

except Exception as e:

    import logging
    logger = logging.Logger("catch_all")
    logger.error(e, exc_info=True)

# define getFoo's interface from Fortran dll

foolib.getFoo.restype = None # return type of the Fortran subroutine/function
foolib.getFoo.argtypes =    [ getFunVal_proc            # procedure
                            , ct.POINTER(ct.c_double)   # real64 return value
                            , ]

outputReal = ct.c_double(0.)
foolib.getFoo   ( getSquare_pntr
                , ct.byref(outputReal)
                )
print("value of outputReal received in Python: ", np.double(outputReal))

运行 这个脚本会产生如下内容,

In [1]: run main.py
value of indx from inside Fortran:  -5
value of indx received by getSquare() inside Python:  -5
value of indx from inside Fortran:  -4
value of indx received by getSquare() inside Python:  -4
value of indx from inside Fortran:  -3
value of indx received by getSquare() inside Python:  -3
value of indx from inside Fortran:  -2
value of indx received by getSquare() inside Python:  -2
value of indx from inside Fortran:  -1
value of indx received by getSquare() inside Python:  -1
value of indx from inside Fortran:  0
value of indx received by getSquare() inside Python:  0
value of indx from inside Fortran:  1
value of indx received by getSquare() inside Python:  1
value of indx from inside Fortran:  2
value of indx received by getSquare() inside Python:  2
value of indx from inside Fortran:  3
value of indx received by getSquare() inside Python:  3
value of indx from inside Fortran:  4
value of indx received by getSquare() inside Python:  4
value of indx from inside Fortran:  5
value of indx received by getSquare() inside Python:  5
value of outputReal from inside Fortran:  110.0000000000000
value of outputReal received in Python:  110.0

与您的 F2PY 代码相比,上面的 Python 脚本可能再次显得相当冗长。但它比您的实施更专业、更现代、更符合标准,符合 Python 和 Fortran 标准。

脚注:Intel ifort 对 Windows、Linux 和 Mac 平台上的所有学生、教师和开源开发人员免费提供。这并不意味着 gfortran 不好。但在我看来,在 Windows OS 上使用 gcc 通常不比永无止境的噩梦好(我与英特尔没有任何关系,只是一个用户)。