zoukankan      html  css  js  c++  java
  • 写python的c扩展简介

    转自:http://www.isnowfy.com/introduction-to-python-c-extension/#ctypes

            python是一门非常方便的动态语言,很多你用c或者java要很多行的代码,可能python几行就搞定了,所以python社区一直有个口号“人生苦短,我用python”,但是方便至于,也带来速度上的问题。python最被人诟病的就是程序的运行速度了,所以结合c的快速和python的方便,就诞生了很多解决方案。首先注意到python就是c写成的,所以最根本的解决方案就是利用原生的python c api来写c程序,然后编译成链接库文件(linux下就是so文件),然后在python中直接调用,而且其他的解决方案也基本是围绕这个思路,只不过替你做了很多重复的工作。这次主要是简要介绍下python c api,swig,sip,ctypes,cython,cffi的使用。

            python c api

             首先来看最原始的就是使用python c api了。

    </pre><pre code_snippet_id="137979" snippet_file_name="blog_20140101_1_1985980" name="code" class="cpp">#include <Python.h>
     
    static PyObject* add(PyObject* self, PyObject* args){
        int a = 0;
        int b = 0;
        if(!PyArg_ParseTuple(args, "i|i", &a, &b))
            return NULL;
        return Py_BuildValue("i", a+b);
    }
     
    static PyObject* sub(PyObject* self, PyObject* args){
        int a = 0;
        int b = 0;
        if(!PyArg_ParseTuple(args, "i|i", &a, &b))
            return NULL;
        return Py_BuildValue("i", a-b);
    }
     
    static PyMethodDef addMethods[]={
        {"add", add, METH_VARARGS},
        {"sub", sub, METH_VARARGS},
        {NULL, NULL, 0, NULL}
    };
     
    void initmytest(){
        Py_InitModule("mytest", addMethods);
    }
             首先是引入Python.h这个头文件,所以编译的时候要注意引入python的库,python中的对象在c中都是用PyObject来表示的,程序中定义了add和sub两个方法,然后编写init函数,名字注意是init加上你的module的名字,然后调用Py_InitModule函数来告诉python你定义的函数有哪些。然后就是把他编译成so文件。

    gcc mytest.c -shared -lpython2.7 -L /usr/lib/python2.7/ -I /usr/include/python2.7/ -o mytest.so
            这样你就可以在python中import mytest这样引用,用法就和用其他python的模块一样了。

    swig
             首先要说明的是swig可以进行很多语言的调用转换,不止是可以让python调用c。swig和sip都被称作wrapper,就是说他对你的原有函数进行了包装。看到之前用python c api的方式里,我们必须严格按照python c api的方式来写代码,破坏了原有c程序的可读性,于是wrapper的思想就是把原生c程序包装成python c api那种方式的代码,再去生成so文件。因此我们要做的是首先写c文件。

    int add(int a, int b){
        return a+b;
    }
    int sub(int a, int b){
        return a-b;
    }
            然后再去写一个swig格式的接口文件。

    %module mytest
    %{
    extern int add(int a, int b);
    extern int sub(int a, int b);
    %}
     
    extern int add(int a, int b);
    extern int sub(int a, int b);
            然后就可以运行swig,他会自动生成python c api写的代码,并且会自动编译出so文件来调用。

    sip

            来看sip,sip是swig发展而来是方便python调用c的,所以基本使用方式都是差不多,只不过接口文件略有差异。

    %Module(name=mytest, language="C")
    int add(int a, int b);
    int sub(int a, int b);
    ctypes

             ctypes提供了另外的思路来调用c程序。首先ctypes是python的标准库,所以如果用ctypes你不需要额外的其他的东西。ctypes让你可以在python直接写代码加载c的链接库so文件来调用,就是说如果你用so文件而没有源文件的话,你仍然可以用ctypes去调用。

    from ctypes import *
     
    f = 'mytest.so'
    cdll.LoadLibrary(f)
    api = CDLL(f)
    api.add.argtypes = [c_int, c_int]
    api.add.restype = c_int
    api.sub.argtypes = [c_int, c_int]
    api.sub.restype = c_int
     
    print api.add(3, 2)
    print api.sub(3, 2)
            有点像在python中去写接口文件,由于是python的标准库,所以这种方式用的还是蛮多的。

    cython

            cython的方法呢是利用类似python的语法来写调用c程序的接口,并且可以同时方便的地用c函数和python函数。看代码理解。

    int sub(int a, int b){
        return a-b;
    }
    我们可以有一些c写成的代码。
    cdef extern from 'test.c':
        int sub(int a, int b)
     
    def add(int a, int b):
        return a+b
     
    def mysub(a, b):
        return sub(a, b)
             然后在cython中我们既可以引入c文件调用c文件中的函数,也可以去调用python中的函数,调用cython程序会把他变成纯正的c文件,然后编译成so文件就可以使用了。
    cffi

            最后是cffi,cffi类似于ctypes直接在python程序中调用c程序,但是比ctypes更方便不要求编译成so再调用,注意到上面的所有方式都是需要去编译成so文件后再在python中调用,而cffi允许你直接调用c文件来使用里面的函数了,为什么这么神奇呢,其实是cffi在解释过程中才帮你把c编译为so文件的。。。

    from cffi import FFI
    ffi = FFI()
    ffi.cdef("""
    int add(int a, int b);
    int sub(int a, int b);
    """)
    lib = ffi.verify('#include "mytest.c"')
    print lib.add(1,2)
             然后基本就是这样了,最后给我的感觉就是:基本上原生的python c api的写法最麻烦了,但是一些需要高级用法的话还是这个更容易控制;方便一点的就是用wrapper;ctypes好处是,他是python的标准模块并且不需要另外写其他的额外程序(接口程序之类的);cython好处就是可以方便的同时调用c函数和python函数,并且是类python语法,用起来很方便;CFFI好处是调用c更加方便,不用编译 so。最后本文只是对各种用法简单的介绍,并没有深入的对各种用法的优缺点进行比较,因此如果想了解更多内容还是去看官方文档吧。。。

    自己的体会:

    1、使用python c api写C代码看着确实挺别扭的,使用起来相当繁复,但既然是原生支持,想必肯定支持的更彻底,可靠性更高些吧。

    2、ctypes模块是python的内置模块,不需要另外安装和写专门的包装文件,也挺方便的。ctypes也可以支持对C++代码调用,需要对代码进行一点封装。根据官方文档,ctypes支持的数据类型都是跟C对应的,不支持C++类对象。但是可以把所有的类处理过程写进函数内,然后在python里调用这个函数达到使用类的目的。如下:

    //test.cpp
    #include<iostream>                               
    class A{   
          public:
               A(){
                 std::cout<<"class A created"<<std::endl;
               }
              ~A(){}
               void printA(int arg){
                      std::cout<<"printA arg: "<<arg<<std::endl;
               }
      };
    class B:public A{
          public:
              B(){
                  std::cout<<"class B created"<<std::endl;
              }
              ~B(){} 
     };
    extern "C"{
          void foo(){
              B* b = new B();
              b->printA(3);
              delete b;
          }
    }   
    g++ -o libtest.so -shared -fPIC test.cpp
    # python 调用脚本 call.py
    #!/usr/bin/env python
    #-*- coding:utf-8 -*-
    
    import ctypes
    
    lib = ctypes.CDLL("./libtest4.so")
    lib.foo()
    执行结果:
    King@machine:~/code/python$ python call.py
    class A created
    class B created
    printA arg: 3

            可以看到,在foo()函数内部引用的类对象,使用了类的一些特性,经过ctypes调用之后还是正确执行了。所以我推测,只要对python暴露的调用接口(如foo())符合C语言的标准,就可以被正常调用,至于接口内部使用了多少类、模板等C++特性,都不影响。当然,这只是我的一种推测,还没有用复杂的代码进行验证。

    另外,细节上需要注意的是,python开放的接口要用extern “C”修饰,否则python脚本里会找不到函数,比如test.cpp代码里的foo()函数。还有,一定要用g++编译C++代码,不然会报类似“OSError: ./libtest4.so: undefined symbol: _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_”这样的错误,因为gcc不会链接C++的库文件。

    ctypes的官方文档链接:https://docs.python.org/2/library/ctypes.html#ctypes-tutorial

    3、cffi模块是第三方模块,不是python的标准库,目前才发展到0.8.6版,貌似有点小众,用的人还比较少,不过有人说比ctypes更方便。官方地址:https://pypi.python.org/pypi/cffi

    4、分享一个牛人写的python调用C++代码方案的分析文章,http://hgoldfish.com/blogs/article/87/。









  • 相关阅读:
    【JS基础】数组
    【JS基础】循环
    【JS基础】DOM操作
    移动端字体
    【JQ基础】
    【JS基础】
    WebBrowser.DocumentText引发FileNotFound异常
    抽取网络信息进行数据挖掘 建立语料库
    文本分类和聚类有什么区别?
    C# 读取网页源代码
  • 原文地址:https://www.cnblogs.com/Harry-Lord/p/4002858.html
Copyright © 2011-2022 走看看