Python隐式相对导入机制及包目录定义的技术疑问
这个问题其实戳中了Python模块导入机制里一个很容易混淆的点——直接运行脚本和作为包模块运行的区别,咱们一步步拆解来看:
第一个场景的运行逻辑
先看你最初的文件结构:
pkg/ pkg/__init__.py pkg/main.py # import string pkg/string.py # print("Package's string module imported")
当你运行python pkg/main.py时,Python会把pkg/目录加入到sys.path的最前端(因为这是你运行的脚本所在的目录)。此时执行import string,Python会按照sys.path的顺序查找模块:
- 先在
pkg/目录下找string.py,刚好存在,所以直接加载这个本地模块,也就输出了你看到的内容。
第二个场景的核心误解
再看修改后的结构:
pkg/ pkg/__init__.py pkg/core/__init__.py pkg/core/main.py # import string pkg/string.py # print("Package's string module imported")
当你运行python pkg/core/main.py时,Python会把pkg/core/目录作为sys.path的第一个元素,而不是你以为的pkg/目录。这是因为你直接运行的是core/main.py这个脚本文件,Python会将该脚本所在的目录视为“顶级目录”,而非把pkg当作一个完整的包来处理。
此时执行import string,Python的查找顺序是:
- 先在
pkg/core/目录下找string.py——不存在; - 接着遍历
sys.path的其他路径(包括Python内置模块的路径),最终找到内置的string模块并加载。
你之前的理解有误:隐式相对导入的“包目录查找”规则,只在模块被当作包的一部分加载时生效,而不是直接运行脚本文件的时候。直接运行脚本时,它是作为“独立的顶级脚本”存在的,而非pkg包的一部分,自然不会去父目录pkg/下查找模块。
如何让第二个场景加载本地的string.py
如果想让core/main.py加载pkg/string.py,你有几种可行的方式:
1. 用模块方式运行脚本
在pkg的父目录下,执行:
python -m pkg.core.main
此时Python会把pkg的父目录加入sys.path,并将pkg视为一个包,core/main.py作为包内的模块,此时import string会先在pkg目录下查找,就能加载到你的本地模块。
2. 使用显式相对导入
修改pkg/core/main.py的代码为:
from .. import string
不过这种方式同样要求你用python -m pkg.core.main来运行,直接运行脚本会报错(因为相对导入不能在顶级脚本中使用)。
3. 手动修改sys.path
在core/main.py的开头添加代码,把pkg目录加入sys.path:
import sys from pathlib import Path sys.path.append(str(Path(__file__).parent.parent)) import string
这样不管你怎么运行core/main.py,都会先去pkg目录下查找模块。
内容的提问来源于stack exchange,提问作者Nishant

