2014-10-06-python-module-import-comparison

Posted on October 6, 2014

一个语言成功与否,一个重要的标志就是它的社区是否成熟。 成熟的语言拥有成熟的社区,成熟的社区会反哺语言,给语言提供大量的程序库支持。 考虑语言对新增模块的支持,很大程度也影响着语言的流行程度。 可以想象,如果一门语言的扩展模块非常难于管理, 这一点对于整个语言社区都是一个极大的负反馈。 在这一点上,做得最成功的非Java莫属。Java的包管理方式极大的促进了语言内组件的应用,这种成熟便利的包管理方法也被后来的语言所借鉴。

用过C语言的外部程序库的人都知道,在C语言里面引入一个外部的依赖是多么复杂的一件事情, 当然这跟C语言本身的特点也分不开。

先来说静态依赖,也就是相对固定,在整个程序的生命周期内会被稳定引用到的外部程序库。首先需要收集这个外部程序的二进制文件和头文件。 在这点上C语言社区并没有一个便利的方式来获取这些内容, 经常人们需要自己辨别二进制头文件的体系结构以免出现二进制兼容问题。 然后,需要告知编译器这些外部程序所在的头文件位置和程序库位置。如果这些外部程序之间存在复杂的相互依赖并且有没有妥善的整理好得话,单单理清这些依赖文件就是不少的工作量了。 然后双手合十开始编译。C语言编译器会把程序引用的外部程序依赖链接到目标二进制中,这样运行的时候执行器就能够找到这些外部依赖符号并且正确的使用这些二进制目标。

再说说动态依赖,如果这种依赖只是在特定的事件下产生,那么完全可以在运行的时候将这些动态链接库导入到程序空间来。所谓的动态时导入,也就是将程序所引用的外部函数和动态链接库里的符号进行绑定的过程。绑定完成,那么动态链接库就会被映射到执行进程的内存空间, 如果多个进程对同一个外部依赖进行了冲突写操作, 那么操作系统会使用COPY-ON-WRITE的方式来解决这种冲突写问题。

就C语言而言,虽然管理外部依赖的方式相对复杂,但是可以看到,这些方式引入的库在运行时是没有太大的额外开销的。 对于静态依赖这种绑定在编译的时候就一次性发生了。 对于动态的依赖,如果该依赖已经被其他进程加载,那么几乎没有什么额外开销,如果尚未被加载,那么就存在将依赖加载的额外开销。 因此就C语言而言, 语言没有提供太大的便利来使用外部依赖, 并且外部依赖的使用有一定的门槛, 这些特点都对C语言的广泛流行造成了一定影响。 但是C语言的外部程序引入过程是十分高效的, 一贯如此。

Java语言的外部依赖管理实际上已经远远超脱了简单的依赖管理范畴(虽然可能也并不是它的本意)。 在Java里面使用外部依赖相对简单,只要保证你所引用的依赖项位于CLASSPATH环境变量所描述的路径即可, Java的外部依赖项组织的也非常方便:分割的名字空间,打成包得程序库, 要使用Java程序库只要能获取这个库即可。 Java使用了类加载器来加载所有的字节码类, 通过双亲加载模型来保证加载类型的安全特性。 类加载器的引入远远超脱了当初的问题, 通过类加载器可以动态的获取类, 可以通过网络获取类, 可以获取加密的类等等。类加载器的存在使得划分类体系成为可能, 最著名的例子: tomcat 通过类加载器对类内存进行划分,使得不同的应用能够运行在同一个容器内而不产生冲突, 同时能够共享一些共有类资源。 甚至相比COW的DDL这也是可圈可点的一笔。 Java的依赖管理模式在效率上也没有明显的弊病,相比于C语言,虽然没有编译时就绑定的外部符号,但是也仅仅只有真正被用到的依赖才会被类加载器加载到JVM。

实际上,Java是如此的流行,以至于出现了很多包依赖管理器程序专门负责管理Java程序的外部依赖。 IVY,MAVEN这些工具的出现不仅使得Java外部依赖包得使用更为简单,也使得软件对依赖项的管理进入了更高层次的抽象:不仅可以指定程序库的依赖,还可以指定依赖库版本的依赖。 这些工作似乎已经将Java中的依赖问题变得不再像那么难的问题。人们可以放心的在这样的抽象之上工作。

Python的依赖获取也十分方便,我一度认为软件世界里的Python外部程序库要远远多于Java程序库。Python依赖库的获取使用也很简单,下载,安装。位于PythonPath环境变量上得程序模块都能被Python程序加载使用, 而且Python程序库还有一个很大的特点,它几乎总是伴随着源代码一起出现的。Python外部模块的加载和Java C略有不同。 首先Python会在系统已加载模块sys.module中寻找目标模块,如果未发现,会继续在sys.meta_path继续寻找。 如果模块已经被加载过了,那么可以直接将模块中得依赖项绑定入当前作用域, 如果该模块尙未被加载,那么加载器就会尝试初始化该模块。 初始化模块时,即使模块中未被使用的部分也会被一同初始化,哪怕显式的指出只导入模块的一部分 from module import object, 这样的显式声明会影响加载的第二步,只有显式声明的部分会被绑定进当前作用域。 整个加载的过程也许并没有那么慢, 但是这样的额外开销不可避免。

Python预言也开始出现集中的程序库仓库,并且Python的依赖管理工具pip也极大的降低了人们获取Python模块的门槛, Python语言的流行和这些方面的正确努力工作时分不开的。

==