⚠ 转载请注明出处:作者:ZobinHuang,更新日期:Mar.28 2021
本作品由 ZobinHuang 采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可,在进行使用或分享前请查看权限要求。若发现侵权行为,会采取法律手段维护作者正当合法权益,谢谢配合。
1. 包引用的规范和原理说明
(0). 背景
首先我们约定一下术语。在 python 中,一个 .py 文件就是一个 模块 (module)。我们使用一个文件夹,装了很多的 .py文件 (模块),然后在这个文件夹下创建一个 __init__.py 文件,这个文件夹就变成了 软件包 (packedge),此时在其它模块中就能将这个文件夹识别成一个 Python 软件包,进而 import 里面的模块。
(1) 规范1:使用绝对路径而不是相对路径
以下面的工程结构为例,所有的源文件都被放置在工程根目录下的 (Project Root)/commer/packedge/module.py 中,比如 (Project Root)/commer/msg/content_msg.py
我强烈建议在工程中的各个源文件内做包引用的时候,使用绝对路径来引用各个包内的模块,比如,在 (Project Root)/commer/test/queue_set_test.py 中,若要使用 (Project Root)/commer/msg/content_msg.py 这个模块,则采用以下绝对路径的方式引入。这么做有几个好处,一个是其他用户能在他们的环境中直接使用我们的工程代码,第二个是保证了工程中各个源文件中模块引用语句的统一性 (i.e. 各个文件要引用 (Project Root)/commer/msg/content_msg.py 模块的语句都是相同的),不会造成混乱。
1 | # (Project Root)/commer/test/queue_set_test.py |
注意到,Python在检索导入的包的范围是这样的:
(1) 源文件所在目录
(2) PYTHONPATH环境变量设置的目录
(3) 标准库的目录
(4) 任何能够找到的.pth文件的内容
(5) 第三方扩展的site-package目录
最重要的一点来了,用以上绝对路径导入模块/包的前提是,你已经把工程的根目录 (i.e. 上面例子中的 .../Commer/) 加入了检索的范围,否则任何尝试使用绝对路径导入包的尝试都将是失败的。我们可以使用以下代码查看当前系统检索包的路径范围:
1 | # 打印检索包的路径 |
如果打印出的东西里没有我们的工程的根目录,那么所有的绝对路径寻包都会报错。这里给出一种解决的办法:使用 shell 命令在 PYTHONPATH 变量中添加我们工程的目录。可以把 shell 命令封装成为脚本文件,如下所示
1 | please source this shell file |
注意,运行这个脚本文件必须使用 source 命令而不能是 bash 命令。这里就得扯到 进程全局变量 和 进程局部变量 的事情,故事是这样的:
进程可以创建局部变量,局部变量只有进程自己内部能够使用,对其他进程包括它自己创建的子进程都是不可见的。进程可以使用 export 命令把局部变量转化为全局变量,全局变量的可见范围就比较广了,当父进程创建子进程后,子进程会继承父进程的全局变量,也就是说全局变量对子进程是可见的,只不过子进程继承的是父进程刚开始全局变量的值。注意,反过来,父进程是看不见子进程自己创建的局部 & 全局变量的。
回到我们的场景,当用户开启一个终端时,实际上操作系统是给我们创建了一个 shell 进程 A。当我们在这个 shell 进程 A 中使用 bash 来运行我们的脚本时,实际上背后的机制是是我们的 shell 进程 A 又创建了一个子 shell 进程 B 来执行这段脚本,而我们发现这段脚本做的事情是:创建了一个局部变量 PYTHONPATH 给它赋值,并且把它 export 成为全局变量,使得这个变量对 B 的子进程可见。但是我们注意到此时的 PYTHONPATH 变量完完全全是在这个子 shell 进程 B 里执行的,所以我们的 shell 进程 A 完全看不见,而且当这段脚本执行完之后,子 shell 进程 B就结束了,所以这个 PYTHONPATH 变量压根就不会影响到我们真正想操作的 shell 进程 A 的变量域,因此使用 bash 来运行这个脚本是无法成功创建全局变量 PYTHONPATH 的。
正确的做法是使用 source 命令,source 和 bash 的区别就在于 source 是直接当我们的 shell 进程 A 执行这段脚本,而不是创建子进程。因此能够成功的在 shell 进程 A 创建全局变量 PYTHONPATH 并且赋上我们想要的值。