233. Python 模块间互相引用
目录如下
1 | src |
字符串的相对路径
在 python 文件中所有出现的字符串 "./"
路径均为运行 python 命令所在的目录路径
- 在运行文件的所在文件夹下运行:
python ./main.py
中的"./"
路径为./
- 跨文件夹运行:
python ./subpath/subsubpath/main.py
, 所有"./"
表示的路径仍然为"./"
, 而不是./subpath/subsubpath
相对路径导入模块的原则
需要在
sys.path
中能够找到需要 import 的模块sys.path
的路径为默认包括主模块 (这里是 main.py) 父目录路径.不管是在
./
下运行python ./main.py
还是在../
路径下运行python ./experiments/main.py
,sys.path
的路径永远包含主模块所在目录的绝对路径.使用相对导入时, 使用
..
或...
等等指向的目录不能超过 top-level 包, 即与起始 import (在哪个模块开始 import 的就是起始 import) 的第一层的路径同级.import 是个迭代模式的.
假设如下模块导入代码在主模块 (其他模块相同) 中为例:
from src.subdir_2 import file_2_1
的 import 的 top-level 就是src
from subdir_1 import file_1_1
的 import 的 top-level 就是subdir_1
主模块只能使用绝对路径导入
如:
from subdir_1 import file_1_1
, 而不能使用from ..subdir_1 import file_1_1
模块导入方式
模块路径以混合方式导入 (推荐)
假设以 src
为根目录, 并在根目录下运行 python 命令, 那么模块导入方式如下
凡是涉及到项目根目录的模块使用绝对路径导入
如这里的:
subdir_1
,subdir_2
中只有彼此引用到了, 都使用绝对路径导入没有涉及到项目根目录的模块使用相对路径导入
如这里的:
subdir_1
中的模块之间的导入, 即subdir_*/
(正则表达式) 中的模块之间的导入
实例
目录见这里, 在 src
目录运行命令: python ./experiments/main.py
, 点击这里下载 源码.
[!IMPORTANT]
如果使用的是 neovim 的 pyright, 那么需要指定项目根目录才可.
如果是当前目录./
, 可以直接创建空文件./pyrightconfig.json
, 并使用nvim ./
更多细节 (见 234. neovim pyright 环境配置)
如果还需要包含其他的搜索目录, 那么 pyrightconfig.json 中添加如下内容
具体代码如下所示:
__init__.py
experiments
__init__.py
main.py
1
2
3
4import sys
sys.path.append("./")
print(sys.path)
from subdir_1 import file_1_1这里有由于
main.py
的路径为./experiments/main.py
, 所以sys.path
中只有路径./experiments
的绝对路径, 而没有./
的绝对路径.
从而通过sys.path.append("./")
来添加./
路径, 其可通过print(sys.path)
查看
subdir_1
__init__.py
file_1_1.py
1
2
3
4
5
6
7
8# 同模块调用
from .subsubdir_1_1 import file_1_1_1
# 跨模块调用
from subdir_2 import file_2_1
def main():
print(__file__)subsubdir_1_1
__init__.py
file_1_1_1.py
1
2
3
4from subdir_2.subsubdir_2_1 import file_2_1_2
def main():
print(__file__)
subdir_2
__init__.py
file_2_1.py
1
2
3
4
5from . import file_2_2
from .subsubdir_2_1 import file_2_1_1, file_2_1_2
def main():
print(__file__)file_2_2.py
1
2def main():
print(__file__)subsubdir_2_1
__init__.py
file_2_1_1.py
1
2def main():
print(__file__)file_2_1_2.py
1
2def main():
print(__file__)
纯相对路径引用
不管怎么样, 只要是 python path/to/your/main.py
, 主模块(main.py) 都要使用绝对路径导入模块.
只需要满足 相对路径导入模块核心点 即可.
如果找不到模块, 可通过如下代码添加模块搜索路径:
1 | import sys |
上述代码将运行路径添加进 sys.path
中, 那么 sys.path
中的 "./"
路径指的是运行命令的目录 (这里是 src
), 而不是 python 主文件所在目录, 可通过 print(os.path.abspath("./"))
查看.
实例
目录见这里, 在 src
目录运行命令: python ./experiments/main.py
, 点击这里下载 源码.
__init__.py
experiments
main.py
1
2
3
4
5import os
import sys
sys.path.append("..") # add path: os.path.abspath("../")
print(sys.path)
from suboptimum_way_src.subdir_1 import file_1_1
subdir_1
__init__.py
file_1_1.py
1
2
3
4
5
6
7
8# 同模块调用
from .subsubdir_1_1 import file_1_1_1
# 跨模块调用
from ..subdir_2 import file_2_1
def main():
print(__file__)subsubdir_1_1
__init__.py
file_1_1_1.py
1
2
3
4from ...subdir_2.subsubdir_2_1 import file_2_1_2
def main():
print(__file__)
subdir_2
__init__.py
file_2_1.py
1
2
3
4
5from . import file_2_2
from .subsubdir_2_1 import file_2_1_1, file_2_1_2
def main():
print(__file__)file_2_2.py
1
2def main():
print(__file__)subsubdir_2_1
__init__.py
file_2_1_1.py
1
2def main():
print(__file__)file_2_1_2.py
1
2def main():
print(__file__)
常见错误
attempted relative import beyond top-level package
src 目录文件如下所示:
__init__.py
main.py
1
2
3
4
5import os
import sys
sys.path.append(os.path.abspath("../"))
print(sys.path)
from subdir_1 import file_1_1subdir_1
__init__.py
file_1_1.py
1
2
3
4
5from suboptimum_way_src.subdir_1.subsubdir_1_1 import file_1_1_1
from ..subdir_2 import file_2_1
def main():
print(__file__)subsubdir_1_1
__init__.py
file_1_1_1.py
1
2
3
4from ...subdir_2.subsubdir_2_1 import file_2_1_1
def main():
print(__file__)
subdir_2
__init__.py
file_2_1.py
1
2
3
4
5from . import file_2_2
from .subsubdir_2_1 import file_2_1_1, file_2_1_2
def main():
print(__file__)file_2_2.py
1
2def main():
print(__file__)subsubdir_2_1
__init__.py
file_2_1_1.py
1
2def main():
print(__file__)file_2_1_2.py
1
2def main():
print(__file__)
由于 main.py
中使用了使用 from subdir_1 import file_1_1
导入模块, 那么 top-level 就是 suboptimum_way_src
.
而这时即使 file_1_1.py
文件使用 from suboptimum_way_src.subdir_1.subsubdir_1_1 import file_1_1_1
能够明确出来 suboptimum_way_src
与 subdir_1
的关系.
且 from ...subdir_2.subsubdir_2_1 import file_2_1_1
明确了 suboptimum_way_src
与 subdir_2
的关系
但是对于 from ..subdir_2 import file_2_1
中的 subdir_2
是否是 suboptimum_way_src/subdir_2
是一个呢?
也可能是与 suboptimum_way_src
同级的 subdir_2
呢
验证方法很简单, 只需要将 file_1_1.py
修改成如下:
1 | # 同模块调用 |
仍然是报错:
1 | ['/Users/qeuroal/abspath_relatpath/suboptimum_way_src', '/Users/qeuroal/anaconda3/envs/torch/lib/python312.zip', '/Users/qeuroal/anaconda3/envs/torch/lib/python3.12', '/Users/qeuroal/anaconda3/envs/torch/lib/python3.12/lib-dynload', '/Users/qeuroal/anaconda3/envs/torch/lib/python3.12/site-packages', '/Users/qeuroal/abspath_relatpath'] |
修正方法
file_1_1.py
改为如下模块导入方法
1 | # 同模块调用 |
下面是猜想:
使用相对导入时, 使用 ..
目录不能于主模块中第一层 import 的路径
from suboptimum_way_src.subdir_2 import file_2_1
的 import 的 top-level 就是suboptimum_way_src
from subdir_1 import file_1_1
的 import 的 top-level 就是subdir_1
这是个迭代模式的、树状展开的