【问题标题】:Calling Fortran shared library in Python with ctypes使用 ctypes 在 Python 中调用 Fortran 共享库
【发布时间】:2016-02-26 13:01:35
【问题描述】:

我正在尝试在 Python 中使用 Fortran 模块。我在 Python 中有几个数组——数字和字符串。我在 Fortran 和 Python 中定义了数组,但我认为我为数值数组使用了错误的类型。我收到一个错误,即转换第一个参数(数字数组)失败。我应该使用哪些类型?

错误:

Traceback (most recent call last):
  File "py_try.py", line 66, in <module>
    writelittler.write_obs(p,z,t,td,spd,wdir,slp,ter,xlat,xlon,date_char,num_met,num_lev,kx,dd_strvar,station_strvar,synop,string4, bogus, iseq_num, iunit)
ctypes.ArgumentError: argument 1: <type 'exceptions.TypeError'>: Don't know how to convert parameter 1

Fortran:

subroutine write_obs(p, z, t, td, spd, wdir, xlon, kx, slp, ter, xlat,  date_char, dd, station, num_met, num_lev, synop, string4, bogus, iseq_num, iunit) bind(C, name='write_obs')
use iso_c_binding
implicit none
integer(c_int) k, kx, num_met, num_lev, iseq_num, iunit, ierr
real(kind=c_float) p(kx), slp(kx), z(kx), t(kx), td(kx)
real(kind=c_float) spd(kx), ter(kx), xlat(kx), xlon(kx), wdir(kx)
character(len=1,kind=c_char) date_char
character(len=1,kind=c_char) synop, string4
character(len=1,kind=c_char), intent(in) :: dd(kx)
character(len=1,kind=c_char), intent(in) :: station(kx)
logical(c_bool) bogus
character(len=84,kind=c_char) rpt_format
character(len=22,kind=c_char) meas_format
character(len=14,kind=c_char) end_format

rpt_format = ' ( 2f20.5 , 2a40 , '&
&' 2a40 , 1f20.5 , 5i10 , 3L10 , '&
&' 2i10 , a20 , 13( f13.5 , i7 ) )'

meas_format = ' ( 10( f13.5 , i7 ) ) '
end_format = ' ( 3 ( i7 ) )'

do 100 k=1 , kx

write ( UNIT = iunit , iostat = ierr , FMT = rpt_format ) &
& xlat(k), xlon(k), dd(k), station(k), &
& synop , string4, ter(k), num_met, 0, 0, iseq_num, 0, &
& .true., bogus, .false., &
& -888888, -888888, date_char, slp(k), 0, &
& -888888., 0, -888888., 0, -888888., 0, &
& -888888., 0, -888888., 0, -888888., 0, -888888., 0, &
& -888888., 0, -888888., 0, -888888., 0, -888888., 0, &
& -888888., 0

write ( UNIT = iunit , iostat = ierr , FMT = meas_format ) &
& p(k), 0, z(k), 0, t(k), 0, td(k), 0, &
& spd(k), 0, wdir(k), 0, &
& -888888., 0, -888888., 0, -888888., 0, -888888., 0


write ( UNIT = iunit , iostat = ierr, FMT = meas_format ) &
& -777777., 0, -777777., 0, float(num_lev), 0, &
& -888888., 0, -888888., 0, -888888., 0, &
& -888888., 0, -888888., 0, -888888., 0, &
& -888888., 0

write ( UNIT = iunit, iostat = ierr, FMT = end_format ) &
& num_lev, 0, 0

if (ierr .NE. 0 ) then
    print '(A,I5,A)','Troubles writing a sounding.Error #', ierr
    stop 'writing_error'
endif
100 continue
return
end subroutine write_obs

Python:

import numpy as np
import ctypes
from ctypes import c_int, c_char
writelittler=ctypes.CDLL("/writelittler.so")
p = np.asarray([ 982.6, 999.7 ], dtype="float64")
p_strvar=ctypes.c_void_p(p.ctypes.data)

# [other numerical arrays omitted]

bogus = 0
kx=2
iseq_num = 0
iunit=2
date_char = '      20160128060000'
dt=np.dtype('a40')
dd = np.asarray([ '1111111111111111111111111111111111111111', '6666666666666666666666666666666666666666', '9999999999999999999999999999999999999999'  ], dtype=dt)
station = np.asarray([ 'V111111111111111111111111111111111111111','M111111111111111111111111111111111111111' ], dtype=dt)

dd_strvar=ctypes.c_void_p(dd.ctypes.data)
station_strvar=ctypes.c_void_p(station.ctypes.data)
num_met=6
num_lev=1
synop='FM-12 SYNOP                             '
string4='                                        '

writelittler.write_obs(p,z,t,td,spd,wdir,slp,ter,xlat, xlon,date_char,num_met,num_lev,kx,dd_strvar, station_strvar,synop,string4, bogus, iseq_num, iunit)

【问题讨论】:

  • 当传递一个numpy数组时,使用它的ctypes属性,例如x.ctypes 而不是 c_void_p(x.ctypes.data)。对于第一个参数,您错误地传递了 numpy 数组 p 而不是 p.ctypes。确保数字类型匹配(例如c_float float32c_double float64)。
  • 对于按值传递,Fortran 的 C 绑定不要求您像 value 那样显式声明一个参数吗?否则参数通过引用传递。例如,integer(c_int), intent(in), value :: k, kx
  • @eryksun 确实如此。但我建议将 argtypes 明确声明为 POINTER(c_float) 等。
  • @Palina 您在哪里指定zt 以及 Python 代码中的所有其他变量?您正在 Fortran 中访问它们!

标签: python fortran ctypes


【解决方案1】:

您没有在 Python 中指定 Fortran 子例程的参数类型。此外,您也不声明/初始化传递给 Fortran 的变量。因此,Python 不知道如何处理它。这就是它在错误消息中告诉您的内容。

由于 Fortran 默认使用引用调用,argtypes 是:

writelittler.write_obs.argtypes = [ POINTER(c_float), # p
                                    POINTER(c_float), # z
                                    POINTER(c_float), # t
                                    POINTER(c_float), # td
                                    POINTER(c_float), # spd
                                    POINTER(c_float), # wdir
                                    POINTER(c_int),   # kx
                                    POINTER(c_float), # slp
                                    POINTER(c_float), # ter
                                    POINTER(c_float), # xlat
                                    POINTER(c_char),  # date_char
                                    POINTER(c_char),  # dd
                                    POINTER(c_char),  # station
                                    POINTER(c_int),   # num_met
                                    POINTER(c_int),   # num_lev
                                    POINTER(c_char),  # synop
                                    POINTER(c_int),   # string4
                                    POINTER(c_bool),  # bogus
                                    POINTER(c_bool),  # string4
                                    POINTER(c_int),   # iseq_num
                                    POINTER(c_int)    # iunit
                                  ]

[这大概可以简化...]

另外,Fortran 子例程没有返回值:

writelittler.write_obs.restype = None

此外,您需要调整函数调用本身(在 Python 中):

writelittler.write_obs( p.ctypes.data_as(POINTER(c_float)), # Numpy data type
                        ctypes.byref(z), # ctypes data type
                        ctypes.byref(t),
                        ctypes.byref(td),
                        ctypes.byref(spd),
                        ctypes.byref(wdir),
                        ctypes.byref(slp),
                        ctypes.byref(ter),
                        ctypes.byref(xlat), 
                        ctypes.byref(xlon),
                        ctypes.byref(date_char),
                        ctypes.byref(num_met),
                        ctypes.byref(num_lev),
                        ctypes.byref(kx),
                        ctypes.byref(dd_strvar), 
                        ctypes.byref(station_strvar),
                        ctypes.byref(synop),
                        ctypes.byref(string4), 
                        ctypes.byref(bogus), 
                        ctypes.byref(iseq_num), 
                        ctypes.byref(iunit) )

[这未经验证...]

既然 Python 知道会发生什么,您仍然需要声明缺失的变量。正如您的代码所知,它将导致错误:

Traceback (most recent call last):
  File "test.py", line 51, in <module>
    writelittler.write_obs(p,z,t,td,spd,wdir,slp,ter,xlat, 
xlon,date_char,num_met,num_lev,kx,dd_strvar,
station_strvar,synop,string4, bogus, iseq_num, iunit)
NameError: name 'z' is not defined

【讨论】:

  • 如果我要声明argtypes,那么我会使用一个numpy 指针类型,例如npf8arr_t = np.ctypeslib.ndpointer(dtype='float64')。这样OP可以直接传递一个numpy数组x而不是传递x.ctypes
  • 另外,如果 OP 控制 Fortran API,则整数参数应更改为通过value 传递。否则这将是一个乏味的界面,例如num_met = ctypes.c_int(6)write_obs(..., ctypes.byref(num_met), ...)。而不是简单的num_met = 6;write_obs(..., num_met, ...)
  • @eryksun 实际上,我更喜欢更繁琐的编写方式。通常,会传递大型 numpy 数组,我会尽量避免复制内容。但你是对的,我也应该将更改添加到函数调用中。
  • 谁说过复制数组参数? ndpointer 类型不复制参数。它的from_param 方法验证 numpy 数组类型在类型、维度、形状和标志方面是否匹配所需的约束,然后它简单地传递ctypes 属性,该属性定义了具有基地址的_as_parameter_ 属性数组作为c_void_p 实例。没有创建副本。
  • @eryksun 在 Fortran 中,如果指定 value,则切换为按值调用。
猜你喜欢
  • 1970-01-01
  • 2023-03-11
  • 2012-12-25
  • 2012-05-06
  • 1970-01-01
  • 1970-01-01
  • 2019-02-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多