3.3 模块的搜索路径

在设计好模块之后,它应该放在何处呢?或许你会说,这很容易啊,把这个模块和想使用这个模块的Python文件放到同一个文件夹下不就行了吗?就如同前面的案例,parameters(模块)和calcalute.py(引用模块方)就放在同一个目录下。

的确,上述方法的确是一种解决方案,但并不是必需的。有时候,当项目很大时,我们希望模块能分门别类地处于不同地方,这时模块和引用方就难以共处于同一个目录下。如果还是按照原先介绍的方式,把一个第三方模块(如parameters)导入当前Python文件,就会产生没有找到模块的错误(ModuleNotFoundError)。

这种情况该如何处理呢?要解决这个问题,就需要了解一个重要的概念—模块搜索路径(Module Search Path)。这个路径存储在系统模块sys里,该模块中有一个全局变量path,可以用如下方法查看该变量值。

从输出可以看出,path本身是以一个列表的形式存在的,它列出的这些路径都是Python在导入模块时搜索的路径。这有点类似于操作系统的环境变量PATH。

为了加深理解,我们通过一个形象的例子让读者对环境变量有一个感性的认识。比如说,我们喊一句:“张三,你妈妈喊你回家吃饭!”可是“张三”在哪里呢?对于人们来说,认不认识“张三”都能给出一定的回应。如果你认识他,可能就会给他带个话;如果不认识他,也可能帮忙吆喝一声“张三,快点回家吧!”

然而,对于操作系统来说,假设“张三”代表的是一条命令,它若不认识“张三”是谁,也不知道他来自何处,便会“毫无情趣”地说:“不认识张三。”即返回not recognized as an internal or external command(错误的内部或外部命令),然后拒绝继续服务。

为了让操作系统“认识”张三,必须给操作系统有关张三的精确信息,如“XX省YY县ZZ乡QQ村张三”。这就好比某个命令的绝对路径。这种添加绝对路径的方式,无疑是正确的。

但其他问题又来了,如果“张三”代表的命令是用户经常用到的,每次呼叫这个“张三”,用户都在终端敲入“XX省YY县ZZ乡QQ村张三”,这无疑是非常烦琐的,有没有更加简单的办法呢?

答案是,当然有!聪明的设计人员想出了一个简单的策略,就是使用环境变量。把“XX省YY县ZZ乡QQ村”设置为常见的“环境”,当用户在终端敲入“张三”时,系统自动检测环境变量集合里有没有“张三”这个人,如果在“XX省YY县ZZ乡QQ村”中找到了,就自动将“张三”替换为这个精确的描述信息“XX省YYY县ZZ乡QQ村张三”,然后继续为用户服务。如果整个环境变量集合里都没有“张三”,再拒绝服务也不迟,如图3-2所示。

图3-2 环境变量的比喻

操作系统里没有上/下行政级别的概念,但却有父/子文件夹的概念,二者有异曲同工之处。对“XX省YY县ZZ乡QQ村”这条定位路径,操作系统可以用“/”来区分不同级别的文件夹,即XX省/YY县/ZZ乡/QQ村,而“张三”就像这个文件夹下的可执行命令。

下面我们给出环境变量的正式定义。环境变量是指在操作系统指定的运行环境中的一组参数,它包含一个或多个应用程序使用的信息。环境变量一般是多值的,即一个环境变量可以有多个值。

对于Windows、Linux等操作系统来说,它们都有一个系统级的环境变量PATH(路径)。当用户要求操作系统运行一个应用程序,却没有指定应用程序的完整路径时,操作系统首先会在当前路径下寻找该应用程序,如果找不到,便会到环境变量PATH指定的路径集合中寻找。若找到了,就执行它,否则,就给出错误提示。用户可以通过设置环境变量来指定程序运行的位置。

回到Python模块搜索路径的讨论上。类似地,按照这个逻辑,如果我们有办法把自己模块的路径告知sys.path,那么Python在导入模块时不就能找到这个模块了吗?

的确是这样。假设我们开发的模块在家目录的package下,即/home/yhily/package(这里的yhily为用户名,对于不同的用户名,路径也会有所不同),则可通过列表的append()方法把这个路径添加到sys.path。延续前面的案例(In [1]处已把sys模块导入内存),代码如下所示。

从最后一行的输出可以看到,我们自己模块的路径已经添加到Python的系统路径sys.path中了。然后,我们故意把parameters(模块)移动至其他路径下,即/home/yhily/package文件夹下,此时calcalute.py(引用模块方)和parameters.py程序已经不在同一目录下,然后我们在IPython中输入如下命令。

在上面的命令中,%run是IPython的魔法函数(第1章已介绍过),可以用它直接运行Python文件。从结果可以看出,程序运行无误。