• Python基础笔记系列十四:python无缝调用c程序


      本系列教程供个人学习笔记使用,如果您要浏览可能需要其它编程语言基础(如C语言),why?因为我写得烂啊,只有我自己看得懂!!

    python语言可以对c程序代码进行调用,以弥补python语言低性能的缺点。当然,它也不是直接就可以调用,需要我们对c代码进行一些中间过程处理,其基本流程如下:

    1.创建c程序功能代码
    ------------1.1创建.c源程序文件(py_test1.c)
    ------------1.2创建.h头文件(py_test1.h)
    2.python类型适配,包装c代码(写包裹文件)(py_test1wrapper.c)
    ------------2.1.包含Python.h头文件(在python安装目录下的include目录下找到)
    ------------2.2.为每一个函数设置一个PyObject *Module_func()的包裹函数
    ------------2.3.为模块增加一个PyMethodDef ModuleMethods[]的数组
    ------------2.4.增加模块的初始化函数void initModule()
    3.编译和测试
    -------------3.1编译安装到python环境
    --------------------3.1.1)创建setup.py
    --------------------3.1.2)运行setup.py编译和链接c的扩展代码
    -------------3.2测试
    --------------------3.2.1)从Python中导入模块
    --------------------3.2.2)测试

    • 创建c程序功能代码
      一、创建.c源程序文件py_test1.c
      这是程序的具体功能代码,也就是python需要调用的c源程序。这里主要写了三个方法,最终我们就会实现python来调用这三个方法。
       1 #include<stdio.h>
       2 #include<stdlib.h>
       3 #include<string.h>
       4 
       5 
       6 //求阶乘
       7 int fac(int n) {
       8     if(n < 2)
       9         return 1;
      10 
      11     return n*fac(n-1);
      12 }
      13 
      14 
      15 
      16 //字符串逆序
      17 char *reverse(char *s) {
      18     //比如输入abcdefg,则返回gfedcba
      19     char t,*p = s ,*q = (s+strlen(s)-1);
      20 
      21     while(s && (p<q)) {
      22         t = *p;
      23         *p++ = *q;
      24         *q-- = t;
      25     }
      26 
      27     return s;
      28 }
      29 
      30 
      31 int test(void)  //测试main方法,改成普通的test方法
      32 //  int main(void)
      33 {
      34     char s[1024];
      35 
      36     printf("5! = %d
      ",fac(5));  //5的阶乘
      37 
      38     printf("10! = %d
      ",fac(10)); // 10的阶乘
      39 
      40 
      41     strcpy(s,"hello world");
      42     printf("reversing 'hello world',we get '%s'
      ",reverse(s));
      43 
      44     return 0;
      45 }
      c源程序

      二、创建.h头文件py_test1.h
      接下来写一个就像stdio.h这样的头文件,方面后面引用。这个文件里面主要就是声明了py_test1.c中的三个方法。

      1 #ifndef PYTEST1_H_
      2 #define PYTEST1_H_
      3 
      4 int fac (int n) ;
      5 char *reverse(char *s) ;
      6 int test(void) ;
      7 
      8 #endif 
    • 写包裹文件py_test1wrapper.c
      这个包裹文件其实也是一段c语言代码,只是这一段代码比较特殊,它需要除c语言外还有一定的规则来创建它,这些规则就是python调用c定义的,必须遵循。
      1.必须include包含Python.h这个头文件,可以说这里就慢慢地去靠向python了。这个头文件在python安装目录下的include目录下找到它,但我们并不需要去知道它在哪儿。
      2.必须include包含py_test1.h这个头文件,这个就是上一步我们创建的那个头文件。
      3.还记得我们的py_test1.c这个源程序文件吗?现在我们需要对它里面的每个方法(这里是三个)都要设置一个包裹函数,它必须以PyObject为返回值,并且每个函数都有两个必须的参数。
      4.还要在这个文件里添加一个模块数组,类型必须是PyMethodDef,它用来定义方法名以及方法名与包裹函数的对应关系。
      5.最后还要添加一个模块的初始化函数void initModule(),这里的函数名必须以init和模块名组成。
      下面就是这个包裹文件的完整代码:
       1 #include "Python.h"
       2 #include <stdlib.h>
       3 #include <string.h>
       4 #include "py_test1.h"
       5 /**
       6 
       7  **包裹文件
       8 
       9 **/
      10 
      11 //为fac函数设置包裹函数(函数名、参数都有一定的规则,要注意)
      12 static PyObject *py_test1_fac(PyObject *self,PyObject *args)
      13 {
      14 
      15     int num ;
      16     //将python的数据类型int args通过i的方式转换成能被c识别的类型int num
      17     //i:表示将python的整型转成c的整型 ,其它类型可百度
      18     if (!PyArg_ParseTuple(args,"i",&num)) 
      19         return NULL;
      20 
      21 
      22     //调用c的对应函数并得到返回值,
      23     //然后将返回值c的数据类型int通过i的方式转换成能被python识别的类型int
      24     //最后强转成PyObject类型
      25     return (PyObject *)Py_BuildValue("i",fac(num));
      26 
      27 }
      28 
      29 
      30 //为reverse函数设置包裹函数(由于python中有reverse函数,不能使用)
      31 static PyObject *py_test1_doppel(PyObject *self,PyObject *args)
      32 {
      33     char *src;
      34     char *mstr;
      35     PyObject *retval;
      36 
      37     //s:python中str ----->C中char * 
      38     if (!PyArg_ParseTuple(args,"s",&src))
      39         return NULL;
      40 
      41     //申请存储空间
      42     mstr = malloc(strlen(src) +1);
      43     //拷贝src到mstr
      44     strcpy(mstr,src);
      45     //调用reverse方法,逆序字符串
      46     reverse(mstr);
      47     //这里把原字符串和转换后的字符串返回
      48     retval = (PyObject *) Py_BuildValue("ss",src,mstr);
      49     //释放空间
      50     free(mstr);
      51 
      52     return retval;
      53 }
      54 
      55 
      56 //为test函数设置包裹函数
      57 static PyObject *py_test1_test(PyObject *self,PyObject *args)
      58 {
      59     //直接调用c函数
      60     test();
      61 
      62     return (PyObject *)Py_BuildValue("");
      63 }
      64 
      65 
      66 //添加模块数组(注意是PyMethodDef,不要错写成PyMethondDef)
      67 //定义对应的方法名,后面Python调用的时候就用这里面的方法名调用
      68 static PyMethodDef py_test1Methods[] = {
      69     {"fac",py_test1_fac,METH_VARARGS},
      70     {"doppel",py_test1_doppel,METH_VARARGS},
      71     {"test",py_test1_test,METH_VARARGS},
      72     {NULL,NULL},
      73 };
      74 
      75 
      76 //模块初始化函数
      77 void initpy_test1(void)
      78 {
      79     Py_InitModule("py_test1",py_test1Methods);
      80 }
      包裹文件


      到此为止,准备工作可以说已经完成了,接下来就需要编译、安装上面的那些文件了,怎么编译呢?

    • 编译、安装和测试
      一、编译安装
        1.创建setup.py文件,文件内容也很简单,主要功能就是使用python的自带模块,将包裹文件编译。
      1 #incoding:utf-8
      2 from distutils.core import setup,Extension
      3 #模块名
      4 MOD = 'py_test1'
      5 #资源(要编译和链接的代码文件)
      6 source = ['py_test1.c','py_test1wrapper.c']
      7 
      8 #调用setup函数,编译和链接
      9 setup(name=MOD,ext_modules=[Extension(MOD,sources=source)])

        那如果运行这个setup.py 程序呢?这里就不能直接build了,需要到命令行里操作,定位要当前目录后通过命令python setup.py build来编译setup.py文件(出现各种错误,如:error: Unable to find vcvarsall.bat请看文章最后)
        

        2.编译完成过后,可以在当前文件目录下找到一个build文件夹,里面就是编译过后的内容了,我们也无需知道里面到底是些什么文件。编译完成我们怎么使用?别急,我们还要安装它们到python的库中,同样通过命令python setup.py install安装。
        

      这里其实就可以发现其实是往我们本地python库中安装了文件,也就是一个模块。在python安装目录下Lib目录的site-packages文件夹下。现在我们就可以使用它了。

      二、测试
        现在就可以使用python来调用最开始写的那个c程序了,别忘了要导入模块哦~
      测试程序test.py:
      1 #incoding:utf-8
      2 import py_test1
      3 print help(py_test1) #查看里面都要哪些方法
      4 py_test1.test() #调用test函数
      5 print "-"*50
      6 print py_test1.fac(9) #调用fac()求阶乘的函数
      7 print py_test1.doppel("yycsetup") #调用逆序函数

      运行输出:

       1 Help on module py_test1:
       2 
       3 NAME
       4     py_test1
       5 
       6 FILE
       7     d:python27libsite-packagespy_test1.pyd
       8 
       9 FUNCTIONS
      10     doppel(...)
      11     
      12     fac(...)
      13     
      14     test(...)
      15 
      16 
      17 None
      18 5! = 120
      19 10! = 3628800
      20 reversing 'hello world',we get 'dlrow olleh'
      21 --------------------------------------------------
      22 362880
      23 ('yycsetup', 'putescyy')
      24 [Finished in 0.5s]
    • 错误解决

      错误提示error: Unable to find vcvarsall.bat
      解决地址 : 其实这个错就是缺少文件vcvarsall.bat,基本解决思路就是安装VCForPython27(安装过后可能提示找不到vc),修改python源代码(修改方法返回值)。下面这几个地址值得参考,标红的为重要解决思路。
      ------------https://www.cnblogs.com/yyds/p/7065637.html
      ------------安装VCForPython27:https://www.microsoft.com/en-us/download/details.aspx?id=44266
      ------------升级setuptools:https://pypi.org/project/setuptools/
      ------------设置setuptools环境变量:https://www.cnblogs.com/fbwfbi/p/4509622.html
      ------------修改方法返回值:https://www.cnblogs.com/lazyboy/p/4017567.html

  • 相关阅读:
    [推荐]大量 Blazor 学习资源(二)
    [翻译]欢迎使用C#9.0
    重磅消息:微软发布多平台应用UI框架 MAUI,网友直呼:牛x
    如何对Git的分支进行管理
    如何使用JPA的@Formula注解
    伤其十指,不如断其一指,谈谈我的学习计划
    如何使用Swagger-UI在线生成漂亮的接口文档
    如何在Linux服务器上部署jar包
    使用PageHelper插件分页时,如何对对象进行转换以及添加属性
    在Java中使用Collections.sort 依据多个字段排序
  • 原文地址:https://www.cnblogs.com/hyyq/p/8995372.html
Copyright © 2020-2023  润新知