python 子包调用 跨目录调用

1. python3中的模块和包

简单来讲,模块是一个包含Python定义和语句的文件,包是一种通过用“带点号的模块名”来构造 Python 模块命名空间的方法。

详细可以参考廖雪峰的:python教程-模块

本文以以下的项目结构,对python3的子包之间的调用进行探索:

python模块 (1)

使用的python版本:3.7.0

平台:win10


2. python的模块搜索路径

当一个名为 spam 的模块被导入的时候,解释器首先寻找具有该名称的内置模块。如果没有找到,然后解释器从 sys.path 变量给出的目录列表里寻找名为 spam.py 的文件。sys.path 初始有这些目录地址:

  • 包含输入脚本的目录(或者未指定文件时的当前目录);
  • PYTHONPATH (一个包含目录名称的列表,它和shell变量 PATH 有一样的语法);
  • 取决于安装的默认设置;


3. 调用子包与调用兄弟包

假如在main.py中调用pack.mod1.py中的函数func1,而func1又调用pack2.mod2.py中的func2函数,各个文件中的代码如下:

main.py:
import sys
print("file:{},sys.path:{}".format(__file__, sys.path))
from pack1 import mod1

if __name__ == "__main__":
    mod1.func1()


pack1/mod1.py:
from pack2 import mod2

def func1():
    print("this is pack1.mod1")
    mod2.func2()


pack2/mod2.py:
def func2():
    print("this is pack2.mod2")

进入test_mod目录,执行命令:

python main.py

输出:

file:main.py,sys.path:['D:\\1-Work\\python_src\\test_mod', 
'C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python37\\python37.zip', 'C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python37\\DLLs', 'C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python37\\lib', 
'C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python37', 'C:\\Users\\xxx\\AppData\\Roaming\\Python\\Python37\\site-packages', 'C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python37\\lib\\site-packages']
this is pack1.mod1
this is pack2.mod2

可以看出sys.path中的第一个元素,就是main.py所在的目录,因为包pack1和pack2都在这个目录下,所以它们能够互相发现。

假如这个时候,你想单独调试一下pack1.mod1.func1函数,将mod1.py修改如下:

import sys
print("file:{},sys.path:{}".format(__file__, sys.path))
from pack2 import mod2

def func1():
    print("this is pack1.mod1")
    mod2.func2()

if __name__ == "__main__":
    func1()

进入pack1目录运行:

python mod1.py

输出如下:

file:mod1.py,sys.path:['D:\\1-Work\\python_src\\test_mod\\pack1', 
'C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python37\\python37.zip', 'C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python37\\DLLs', 'C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python37\\lib', 'C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python37', 'C:\\Users\\xxx\\AppData\\Roaming\\Python\\Python37\\site-packages', 'C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python37\\lib\\site-packages']
Traceback (most recent call last):
  File "mod1.py", line 3, in <module>
    from pack2 import mod2
ModuleNotFoundError: No module named 'pack2'

发现sys.path的第一个路径已经是test_mod/pack1了,在这个目录下面解释器无法找到pack2这个包。

如果需要发现这个包,按照python搜索路径的规则,需要将test_mod这个目录加入到sys.path中,一般来说可以:

  • 通过环境变量PYTHONPATH进行设置;
  • 也可以将路径写入.pth文件,Python在遍历已知的库文件目录过程中,如果见到一个.pth 文件,就会将文件中所记录的路径加入到 sys.path 中;
  • 最简单的是使用sys.path.append函数将路径加入。

使用最后一种办法,将pack1.mod1.py修改为:

import sys
sys.path.append("D:\\1-Work\\python_src\\test_mod")
print("file:{},sys.path:{}".format(__file__, sys.path))
from pack2 import mod2

def func1():
    print("this is pack1.mod1")
    mod2.func2()

if __name__ == "__main__":
    func1()

结果输出:

file:mod1.py,sys.path:['D:\\1-Work\\python_src\\test_mod\\pack1', 'C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python37\\python37.zip', 'C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python37\\DLLs', 'C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python37\\lib', 'C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python37', 'C:\\Users\\xxx\\AppData\\Roaming\\Python\\Python37\\site-packages', 'C:\\Users\\xxx\\AppData\\Local\\Programs\\Python\\Python37\\lib\\site-packages', 'D:\\1-Work\\python_src\\test_mod']

为什么使用绝对路径呢,因为如果使用相对路径的话,当前工作路径一变就不行了,不信在test_mod目录执行

python pack1/mod1.py

或者用vscode等文本编辑器打开test_mod目录,然后直接F5运行,工作目录就是test_mod,而不是pack1,这个时候添加的”../”路径就会变成目录python_src。

这也是为什么很多文章使用sys.path.append(“../”),然后很多人在留言区说在他们本地运行的时候根本不行。

所以一切的关键还是模块搜索路径。

回到解决方式上面,或者将项目的路径加到PYTHONPATH环境变量,或者写入.pth并放在搜索路径中才是一劳永逸的方式。


4. 参考

(1) python3文档-模块

(2) 廖雪峰:python教程-模块

(完)

posted @ 2020-02-10 19:22  大师兄啊哈  阅读(...)  评论(...编辑  收藏