一 前言

        前一段时间使用python实现了一个多车道线检测的功能,代码400余行,但是运行速度非常低,需要200ms/帧。为了优化其运行速度,准备将一些Python函数使用C语言实现(据说C程序的运行速度是Python的5倍)。

        于是乎,我搜索了一些文章,Python中调用C程序的方法主流上分为4种,具体的可以参考博客在Python中调用C程序的4种方法。其中,我最想使用的方法是“Python/C API”的方法,因为它对整个接口的结构描述的更清晰,想要做一些大的项目实现起来会更准确可靠。但是,在真正使用的过程中,引用Python.h库文件出现了很多问题,该库文件中所引用的.h文件经常不被检测,造成其中的方法不能够被连接到.o或者.so文件中,研究了很长时间也没有解决这个问题(我的系统是ubuntu20.04,出现的问题是Python.h中的一些类没有被指定)。

        因此,我使用了更为简单的ctype的方式,这种方法的优点是:

表1 在Python中调用C程序-ctypes方法的优点

序号

优点

1

没有复杂的.h文件引用

2

C代码完全不用考虑Python数据类型,可以“随心所欲”。

3

在Python中兼容C模块的方式也很清晰,易于上手。

        在学习的过程中,网上的教程鱼龙混杂,有很多教程自身都无法使用。而且大多数的教程不具有一般性,他们使用python原本就兼容的c_int数据类型进行举例,当我们想要使用像float或者double时就会报错。而且,极少数教程能够说明白,如何使用ctype将python中的numpy数组转为C指针,或者如何在python中为所调用的C模块传参和正确获得C模块返回值。基于此类现象,特此写一个系列的博客来记录ctype的学习过程。

        操作系统ubuntu20.04。

二 一个简单且普遍的“int”示例

1 CPython_Int.c

#include<stdio.h>

int addInt(int yourInt1,int yourInt2);

int addInt(int yourInt1,int yourInt2){
    int sumInt = 0;

    sumInt = yourInt1 + yourInt2;
    
    return sumInt;
}

2 将CPython_Int.c编译为.so文件

gcc -shared -Wl,-soname,CPython_Int -o CPython_Int.so -fPIC CPython_Int.c

3 CPython_Int.py

import ctypes

#刚刚获得的.so文件的绝对路径
file_so_dir = '/home/torch/桌面/python c test/CLib/CPython_Int.so'
#引用该文件
CPython_Int = ctypes.cdll.LoadLibrary(file_so_dir)
#设定文件中函数的输入数据类型和输出数据类型
##对于本int示例,这一步不用做,因为python和c的int类型是互通的。
##但是对于其他数据类型,比如float或者double,这一步是必须的。
CPython_Int.addInt.restype = ctypes.c_int #指定函数输出数据类型
CPython_Int.addInt.argtypes = (ctypes.c_int,ctypes.c_int)
#调用函数并获得结果
result = CPython_Int.addInt(1,2)

print("结果的数据类型为:",type(result))
print(f"结果为:{result}")

4 运行结果

结果的数据类型为: <class 'int'>
结果为:3

重要了。如果不加指定,会输出错误,此处不予演示。

        更换为float类型后的输出结果:

结果的数据类型为: <class 'float'>
结果为:3.0

三 向C程序中传入指针

        我们以浮点型指针为例。

1 CPython_Float_Ptr_In.c

#include<stdio.h>

float addFloatList(float *yourList,int length);

float addFloatList(float *yourList,int length){
    float sum = 0.00;
    for(int i = 0; i < length;i++){
        sum += *(yourList+i);
    }
    return sum;
}

2 编译.c文件

gcc -shared -Wl,-soname,CPython_Float_Ptr_In -o CPython_Float_Ptr_In.so -fPIC CPython_Float_Ptr_In.c

3 CPython_Float_Ptr_In.py

import ctypes
import numpy as np

file_so_dir = '/home/torch/桌面/python c test/CLib/CPython_Float_Ptr_In.so'

CPython_Float_Ptr_In = ctypes.cdll.LoadLibrary(file_so_dir)
#指定函数的输入类型和输出类型
CPython_Float_Ptr_In.addFloatList.restype = ctypes.c_float
CPython_Float_Ptr_In.addFloatList.argtypes = (ctypes.POINTER(ctypes.c_float),ctypes.c_int)
#将numpy数组转换为C需要的指针
c_input_array = np.array([1,2,3,4,5,6,7,8,9],dtype=np.float32)

if not c_input_array.flags['C_CONTIGUOUS']:c_input_array = np.ascontiguousarray(c_input_array,dtype=c_input_array.dtype) #转换为连续内存
c_input_array_ptr = ctypes.cast(c_input_array.ctypes.data,ctypes.POINTER(ctypes.c_float))    #获得c指针

result = CPython_Float_Ptr_In.addFloatList(c_input_array_ptr,len(c_input_array))

print(type(result))
print("总和:",result)

4 运行结果

<class 'float'>
总和: 45.0

5 一些容易遇到的“小麻烦”

        这里主要记录一下,关于数据类型上的问题。

        我们注意到,在上面的例程中,我们使用的numpy数据类型是float32,那么,使用其他float格式,例如float,float64是否也可以完成这项任务呢。

        我们以float为例。我们将CPython_Float_Ptr_In.py中的代码做如下修改:

- c_input_array = np.array([1,2,3,4,5,6,7,8,9],dtype=np.float32)
+ c_input_array = np.array([1,2,3,4,5,6,7,8,9],dtype=float)

        将numpy数组的float32类型改为float。得到的结果如下:

<class 'float'>
总和: 8.25

        出现了错误。为什么会这样呢?

        我们需要知道,在Python中float的默认为float16,占用两个字节。而在C中float默认为float32,占用4个字节。我们在示例程序中使用numpy.float32匹配了数据类型长度,所以获得了正确结果。而如果将数据类型修改为float或者numpy.float64,则会出现求和错误的现象。

6 一些ctype转换关系和C语言中数据类型长度整理

6.1 ctype对应的Python数据与C数据的转换关系

Python 怎么调方法 python 调c_python

6.2 C语言不同数据类型的内存占用情况

 

Python 怎么调方法 python 调c_c++_02

四 从C程序中获得指针并转换为numpy数组

1.CPython_Float_Ptr_In_Out.c

#include<stdio.h>
//数组排序
float* sortFloatList(float *yourList,int length);

float* sortFloatList(float *yourList,int length){
    float tempStore;

    for(int j=0;j<length -1;j++){
        for(int i=1;i<length -j;i++){
            if (*(yourList +i -1) < *(yourList +i)){
                tempStore = *(yourList +i -1);
                *(yourList +i -1) = *(yourList +i);
                *(yourList +i) = tempStore;
            }
        }
    }

    return yourList;
}

2.编译C文件

gcc -shared -Wl,-soname,CPython_Float_Ptr_In_Out -o CPython_Float_Ptr_In_Out.so -fPIC CPython_Float_Ptr_In_Out.c

3.CPython_Float_Ptr_In_Out.py

import ctypes
import numpy as np

file_so_dir = '/home/torch/桌面/python c test/CLib/CPython_Float_Ptr_In_Out.so'

CPython_Float_Ptr_In_Out = ctypes.cdll.LoadLibrary(file_so_dir)
#指定函数的输入类型和输出类型
CPython_Float_Ptr_In_Out.sortFloatList.restype = ctypes.POINTER(ctypes.c_float)
CPython_Float_Ptr_In_Out.sortFloatList.argtypes = (ctypes.POINTER(ctypes.c_float),ctypes.c_int)
#将numpy数组转换为C需要的指针
c_input_array = np.array([1,2,3,4,5,6,7,8,9],np.float32)

if not c_input_array.flags['C_CONTIGUOUS']:c_input_array = np.ascontiguousarray(c_input_array,dtype=c_input_array.dtype) #转换为连续内存
c_input_array_ptr = ctypes.cast(c_input_array.ctypes.data,ctypes.POINTER(ctypes.c_float))    #获得c指针
#获得C模块结果c_float指针
c_float_ptr = CPython_Float_Ptr_In_Out.sortFloatList(c_input_array_ptr,len(c_input_array))
#将c指针转为numpy数组
result = np.ctypeslib.as_array(c_float_ptr,c_input_array.shape)

print(type(result))
print("排序结果:",result)

4 运行结果

<class 'numpy.ndarray'>
排序结果: [9. 8. 7. 6. 5. 4. 3. 2. 1.]

----------------------

五 结束语

        希望通过此类方法提升运行速度的兄弟们,慎用。我测试了一下,C的速度没有想象中那么快,特别是在数组处理上。可能C在某一些方面表现比python好吧。但是对于我来讲已经达不到我的目的了,所以,这个文章就更新到这儿了,勿怪。

2022年3月26日20:41