在设计好模块之后,它应该放在何处呢?或许你会说,这很容易啊,把这个模块和想使用这个模块的 Python 文件放到同一个文件夹下不就行了吗?就如同前面的案例,parameters(模块)和 calcalute.py(引用模块方)就放在同一个目录下。
的确,上述方法的确是一种解决方案,但并不是必需的。有时候,当项目很大时,我们希望模块能分门别类地处于不同地方,这时模块和引用方就难以共处于同一个目录下。如果还是按照原先介绍的方式,把一个第三方模块(如 parameters )导入当前 Python 文件,就会产生没有找到模块的错误(ModuleNotFoundError)。
这种情况该如何处理呢?要解决这个问题,就需要了解一个重要的概念—模块搜索路径(Module Search Path)。这个路径存储在系统模块 sys 里,该模块中有一个全局变量 path,可以用如下方法查看该变量值。
In [1]: import sys #导入系统模块 sys
In [2]: sys.path #输出 sys 的属性值 path
Out[2]:
['',
'/home/yhily/anaconda3/bin',
'/home/yhily/anaconda3/lib/python37.zip',
'/home/yhily/anaconda3/lib/python3.7',
'/home/yhily/anaconda3/lib/python3.7/lib-dynload',
'/home/yhily/anaconda3/lib/python3.7/site-packages',
'/home/yhily/anaconda3/lib/python3.7/site-packages/工Python/extensions',
'/home/yhily/.ipython']
从输出可以看出,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 村张三”,然后继续为用户服务。
如果整个环境变量集合里都没有“张三”,再拒绝服务也不迟,如图 1 所示。
图 1:环境变量的比喻
操作系统里没有上/下行政级别的概念,但却有父/子文件夹的概念,二者有异曲同工之处。对“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 模块导入内存),代码如下所示。
In [3]: home_dir= '/home/yhily/package' #定义自己模块所在的路径
In [4]: sys.path.append(home_dir) #添加到 sys.path 列表中
In [5]: sys.path #输出验证
Out [5]:
['',
'/home/yhily/anaconda3/bin',
'/home/yhily/anaconda3/lib/python37.zip',
'/home/yhily/anaconda3/lib/python3.7',
'/home/yhily/anaconda3/lib/python3.7/lib-dynload',
'/home/yhily/anaconda3/lib/python3.7/site-packages',
'/home/yhily/anaconda3/lib/python3.7/site-packages/IPython/extensions',
'/home/yhily/.ipython',
'/home/yhily/package']
从最后一行的输出可以看到,我们自己模块的路径已经添加到 Python 的系统路径 sys.path 中了。 然后,我们故意把 parameters(模块)移动至其他路径下,即 /home/yhily/package 文件夹下,此时 calcalute.py(引用模块方)和 parameters.py 程序已经不在同一目录下,然后我们在 IPython 中输入如下命令。
In [6]: %run calculate.py
圆形面积为:78.539815
在上面的命令中,%run 是 IPython 的魔法函数(之前已介绍过),可以用它直接运行 Python 文件。从结果可以看出,程序运行无误。