Python成为胶水语言有一个发展过程,并不是一步实现的。Python设计初期就面向科研人员,降低编程难度,提高编程效率。在现代也有其他按照这个思路发展的语言,比如R和Matlab。也有一些思路有些许相似的玩法,比如面向特定领域的语言,像Lua、JavaScript。在与C库的互操作性方面也是个重要的问题。
任何编程语言发展初期都会遇到标准库如何构建的问题。而每种语言的发展思路却有所不同。
C和C++的思路类似,就是提供一个较小的标准库,这使得标准库在各个平台的移植过程工作量较小。但也这个思路也带来一个问题,就是标准库一直保持较小的状态,就会导致一些必要的功能需要第三方库甚至用户自己去实现,这些实现之间又不兼容,就导致了很多悲剧。比如一些常见的数据结构,buffer、stack、queue。在C/C++的各种第三方库里几乎都有各自的buffer实现,stack和queue也有多种实现。C++为部分解决此问题,提供了STL库来实现更多的功能,以及Boost库提供更大范围但支持程度更低的实现。C/C++得益于几十年的发展,各种库还是非常丰富的,而且每当有新的领域兴起时,C/C++也几乎总是最早一批提供支持的。比如GPU编程等等。
R和Matlab一类的面向科研人员的语言,其设计目标就是降低编程难度,但在编程的通用型方面不足,难以出圈成为通用编程语言。另外就是在性能方面弃疗的也有些难堪。
Lua则是一开始就面向嵌入其他系统里的应用,比如早期的网络游戏,近些年的运营规则引擎,交换机策略等等。JavaScript从早期在浏览器里运行也发展到现在可以在一些环境里脱离浏览器,嵌入其他系统应用。Lua和JS的特征是很像的,就是面向很短的脚本应用,为了在这些短脚本里用的方便,语言上缺少大规模应用的良好支持。比如变量的作用域、模块、面向对象等,在Lua和JS里都缺少通用的实现,一些库和和应用需要自己去选择某种实现策略,这就导致了应用之间的兼容性困难。同时缺少这些功能也使得编写较大的Lua/JS程序时存在一些痛苦,项目越大就越需要大量trick来控制复杂度。
Python早期在面对缺少第三方库问题时,用的是那个时代的常见作法,即实现与C的高度互操作性。Python可以方便的编写C模块来调用各类C函数库,把一些结构体等封装成Python对象来进行更好的组织。在80-90年代这种思路是很常见的作法,那个时代的语言基本都会确保自己编译出来的obj文件可以与C的obj文件能链接(link)成一个可执行文件。Python没有编译过程,但至少是非常重视与C的互操作性问题的。
但进入90年代以后,Java的出现对与C的互操作性是非常不屑的。因为Java最初设计目标是用于嵌入式编程的,而引入与C的调用后,可能影响Java运行时的稳定性,因此Java在很长时期都不允许与C互相操作。在Java出现近10年后,才出现了JNI用于操作C库。自Java的思路以后,新出现的部分语言就不再开发与C库对接的API了。这导致了一段时期这些语言只能用其自身开发各类第三方库,进而影响了发展速度。
Python的C API本身设计的是不错的,相比其他多种脚本语言,我见过唯一比Python C API设计的更好的只有Lua了。这就使得一些新的领域出现以后,C/C++第一时间支持,之后在提高可编程性方面的第一选择了就是用Python C API来封装出给Python的接口。最近十几年的CUDA、OpenCL、OpenCV等,都是官方同时提供C++和Python的接口,使得应用质量上有很好的支持。
这里不得不提及的一个关键结点是numpy库。numpy库本身定义的数组(np.array)实际上成了多种Python高性能计算库的通用数据结构。numpy建立的多维数组,可以不经过转换就交给OpenCV做下一步计算,以及再交给CUDA做算法加速。加之numpy本身底层的BLAS经过了SIMD指令集加速,提供了非常高的性能,在常见密集计算里能提供比纯C/C++(没使用SIMD优化)还高几倍的计算性能。进而使得Python在最近几年的大数据、深度学习领域,能够同时获得易于编程和高性能的优势,自然也就成了更好的胶水。
相关阅读