浅谈Python程序与C++程序的联合使用

WBOY
Release: 2016-06-06 11:24:21
Original
1347 people have browsed it

作为Python程序员,应该能够正视Python的优点与缺点。众所周之,Python的运行速度是很慢的,特别是大数据量的运算时,Python会慢得让人难以忍受。对于这种情况,“专业”的解决方案是用上numpy或者opencl。不过有时候为了一点小功能用上这种重型的解决方案很不划算,或者有时候想要实现的操作在numpy里面没有,需要我们自己用C语言来编写。总之,我们使用Python与C++的混合编程能够加快程序热点的运算速度。

首先要提醒大家注意的是,在考虑联合编程之前一定要找到程序运行的热点。简单一点地,使用标准库的profile或者cProfile模块找到最消耗CPU的位置,如果这个位置只简单的消耗IO时间,通常换成C++程序的意义也不会很大,此时做联合编程可能是事倍功半,起不到多大的效果。

还有些情况,Python程序员们想要使用操作系统或者外部模块提供的函数。这些模块一般是为C/C++程序员提供的。这时候也是Python与C++联合编程的用武之地。

Python语言可以说是最好的胶水语言。仅就与C++联合编程这个问题来讲,依使用难度与功能来排列,Python社区提供了以下几种解决方案:

1.使用标准库ctypes直接调用C/C++编写的动态链接库。这是最简单易用的方案。C/C++程序员使用自己的丰富的经验,把预定的功能实现为动态链接库。而Python程序员只要知道这些动态链接库函数的名称、参数类型与返回值类型就能简单地调用它。当你传入参数时,ctypes模块会自动地把Python的对象成为C/C++所对应的参数类型。比如以下调用Windows的API:

  #定义参数类型与函数名称
  from ctypes.wintypes import UINT, DWORD
  GetLastInputInfo = ctypes.windll.user32.GetLastInputInfo
  class LASTINPUTINFO(ctypes.Structure):
    _fields_ = [("cbSize", UINT),
         ("dwTime", DWORD)]

  #开始调用DLL导出的函数
  def getLastInputTime_nt():
    info = LASTINPUTINFO()
    info.cbSize = ctypes.sizeof(info)
    info.dwTime = 0
    if not GetLastInputInfo(ctypes.byref(info)):
      raise WindowsError("")
    return info.dwTime

Copy after login

在这里展示了如何构造Windows的API所需要的结构体,如何填充结构体并分析返回值。

ctypes还能将Python函数提供给C/C++代码作为回调函数。

与其它解决方案相比。ctypes不需要程序员熟悉C/C++语言,不需要安装一个C/C++的编译器,它通过操作系统的接口直接操作C/C++代码。而且ctypes是标准库的一部分,只要安装了Python就可以直接使用。这几个原因使得它深受Python程序员的喜爱。

而它的劣势呢。首先,ctypes不能简单调用C++程序,因为C++在编译的时候使用了name mangling这个技术来实现函数的重载。C++会自动地为类的成员函数加上类名前缀。所以,C++程序员需要以C语言的调用约定来提供接口,没有类,没有重载函数,没有模板,没有C++异常。不能直接调用现有的C++代码可能是这个方案最大的缺点。

另外,对于list, set之类的数据类型,ctypes不能识别并自动地在Python与C/C++数据类型之间转换。C/C++部分不能识别Python数据类型,这时候只能用Python语言来编写转换代码。如果数据量较大,或者调用很频繁,转换代码反而会浪费很多的资源。这或许是ctypes的另一个劣势之一了。

2.如果你使用的是Jython或者IronPython的话,它们也提供了类似于ctypes之类的模块,能够直接访问Java或者.Net语言编写的模块。其优势与劣势大致与ctypes相似。因为其使用范围有限,这里不再详述。

3.使用Cython语言,一种类似于Python语言的一种新型语言编写预定功能的代码,然后将这些代码转换成为C语言编译成为Python语言可以直接调用的二进制模块。Cython语言是融合Python语言与C语言的一种新型语言。它本身能够理解Python语言的语法,然后在其基础上增加了某些C语言的语法,以便更精细地控制数据类型与指针。基本兼容Python语法是这个解决方案最大的特点。很多时候,Python程序员只要在旧的代码中简单地声明一下代码中所使用的参数、变量的类型,就能把立即为旧的Python程序提速。

Cython提供了一个名为pyximporter的工具,能够在安装了C/C++编译器的计算机上面为简单的Cython程序直接生成相应的Python模块。这使得Cython的使用与普通的Python程序一样简单。比如下面这段代码,直接保存为myhello.pyx即可被调用。

  #myhello.pyx
  def sayHelloTenTimes():
    cdef int i #只要简单地为变量标识类型即可加速循环。
    for i in range(0, 10):
      print("hello, world!")

  $ python
  >>> import pyximport; pyximport.install()
  >>> import myhello
  >>> myhello.sayHelloTenTimes()

Copy after login

    由此可见,Cython非常容易使用。而且不仅能够处理C语言的模块,还能处理C++的模块——虽然没有直接支持虚函数之类的完整C++特性。因为它不直接使用C/C++语法,而是另外设计比C/C++更简洁优雅的新型语法,因此,对于不熟悉C/C++的程序员来说有很大的吸引力。相比ctypes来说,因为参数类型转换更加智能与高效,所以通常能够提升更多的效率。

    劣势呢,所谓用Python程序员所熟练的语法来编写高速的运算代码,乍一听相当地有吸引力。但是如果想要更深入地控制内存与数据结构时,程序员可能会发现,现在他不得不熟练地掌握C/C++语言,然后用Cython的语法写出来。以程序员们懒惰的性格,这反而是件难以忍受的事件。这或许是Cython本身并不大流行的主要原因吧。

4.使用boost.python。有意思的是,与ctypes/Cython形成鲜明的对比,boost.python倾向于让C++程序员拥有更熟悉的编程环境。它让C++程序员使用他所熟悉的C++语法直接控制Python的数据结构,调用Python的解释器。它没有像Cython那样发明新的语法,而是直接使用C++的语法,编写供Python使用的接口。与Cython同样的道理,它的效率优胜于ctypes。

    与Cython/SWIG/SIP等方案相比,程序员只需要学习C/C++与Python两种语言。另外,与本文提到的几种解决方案相比,它非常适合在主要由C++编写的程序中控制Python代码。不仅功能更强大、效率还更高。如此神奇的解决方案会有什么劣势呢?某些人可能不同意吧,老鱼一听说它依赖于boost就蔫了,感觉编译与学习庞大又奇怪的boost非常浪费生命。

5.使用SWIG或者SIP,通过编写一个接口文件,使用类似于C/C++语法——声明函数、类型的信息,然后使用特殊的工具为C/C++的代码生成Python的接口代码。这些接口代码能够在Python与C/C++之间的数据结构转换。最终编译这些接口代码,成为Python的二进制模块。SWIG与SIP的接口文件与C/C++的头文件非常相似。

    这两种工具差不多,因为。本质上,他们都与Cython类似,都使用了中间语言来生成转换代码。但SWIG/SIP能够在他们的接口文件中嵌入C/C++,能够让程序员仔细地调节数据类型的转换过程。在使用上,它比Cython的层次更低,更接近于Python本身提供的API。

    SWIG能够为多种脚本语言生成转换代码。而SIP则专门针对Python与C++。此外,SIP本身是作为PyQt的专门工具来开发的,因此它能够理解Qt的signal/slot。从应用项目上来看,SWIG似乎会更广泛一点。而SIP,目前所见的项目基本都与PyQt相关。据说SWIG对于C++的支持不好,不知道有没有人来说一下呢。相比之下,SIP对于C++的支持非常完善,诸如虚函数、protected member function、模版、析构函数、异常等特性都得到良好的支持。而且SIP支持Python的GIL,还拥有一个使用Python编写的编译系统。可能会更方便一点。

    然而这种方案毕竟要学习一种新的语言,所以从表面上来看不如Cython和boost.python讨喜。当程序员想要仔细地调节类型转换代码的时候,需要学习SWIG/SIP的内部机制,被限定使用特殊的变量名。这使得这种方案的学习曲线相对较高。

6.直接使用Python的API,可以称之为最终解决方案。Cython, SWIG, SIP的接口文件转换后所生成的C/C++代码实际上都使用Python的API。与其它方案相比,这种方案相当地繁复,必须为每次函数调用编写数据转换代码,还要操心Python对象的引用计数。我觉得这种方案一无是处,这时就不再多讲了。其它的工具pybindgen不知道什么情况。有兴趣的话可以看看。

好了。题外话一句吧,我一直觉得ctypes与xmlrpc并列Python语言的两大神器,最能体现Python的生产效率。

希望本文在大家选择一种技术路线时能提供一点点帮助。

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template