Python的路径引用问题

本来,我以为这是一个不太需要关注的问题,之前也遇到过几次了,但是都是搜索引擎一搜然后解决了就没管了.

然后今天在写一个包的过程中又遇到了,又坑了我半天时间,而且搜索引擎找到的解释也大都模凌两可(包括Google),这让我深深的觉得自己离理解这个bug还有点距离,故要记录一下.感觉不搞清楚一点以后还会被坑.

状况

我觉这里的状况还有几个层级,但是导致的结果都是[你的包不能用啦!你走错路啦!]
(因为原来会把这个问题想得很简单,找到一个方案之后,感觉自己似乎已经解决了,但还是报错,就会一直盯着看直到怀疑人生)

目前主要有3点,及解决方式.

遇到的3种情况

  • main‘ is not a package
  • Unresolved reference
  • ModuleNotFoundError: No module named ‘dir1’

接下来我要比较详细的记录一下几个问题的过程及处理步骤,主要是深化自己的理解,也希望会对遇到同样问题的同学能有帮助.
当然我的理解可能也不是基础理解,可能会有错漏,所以也请谨慎参考,欢迎拍砖交流.

项目结构

1
2
3
4
5
6
7
testmodule
├── dir1
│   ├── dir1_1_f.py
│   └── dir1_2_f.py
├── dir2
│   └── dir2_f.py
└── test_main.py

整个目录放在testmodule的根目录下,有一个主测试文件test_main.py和两个子文件夹,内容分别如下:
testmodule/test_main.py

1
2
3
4
5
6
7
8
9
from dir1.dir1_1_f import dir1_1_fun
from dir2.dir2_f import dir2_fun

def test_main():
dir1_1_fun()
dir2_fun()

if __name__ == '__main__':
test_main()

testmodule/dir1/dir1_1_f.py

1
2
def dir1_1_fun():
print('dir1_1_fun')

testmodule/dir1/dir1_2_f.py

1
2
3
4
5
6
7
from dir1_1_f import dir1_1_fun

def dir1_2_fun():
print('dir1_2_fun')

if __name__ == '__main__':
dir1_1_fun()

testmodule/dir2/dir2_f.py

1
2
def dir2_fun():
print('dir2_fun')

Debug

Markdown
Markdown
可以看出其实不加__init__.py文件也是可以跑的,使用的是相对路径.
所以不打包时,有无__init__.py都不重要,因为相关的执行文件都在,是都可以执行的.

但是如果打包的时候没有__init__.py的话,那么相关的子模块是不打包的,所以肯定会报错.

main‘ is not a package

使用相对路径导入且通过 python xxx/xxx.py调用时会出现
例如:将test_main.py中

1
2
from dir1.dir1_1_f import dir1_1_fun
from dir2.dir2_f import dir2_fun

改成:
1
2
from .dir1.dir1_1_f import dir1_1_fun
from .dir2.dir2_f import dir2_fun

通过
1
python testmodule/test_main.py

调用便会报上面错误.
需要通过
1
python -m testmodule.test_main

调用

Unresolved reference

“项目结构”中的代码在pycharm中应该会提示如上信息,因为示例代码中的引用还是绝对引用,但是却没有根目录信息,虽然可以执行,但是会有如上提示.
一是改成相对路径,二是在前面再加上根目录名称,三是在Django中将根文件夹设置成source(但是这一步只是消除提示,并不解决代码结构问题)

ModuleNotFoundError: No module named ‘dir1’

这个就是在代码目录中使用了相对路径,但是在包调用的时候只将包名import了,使用的时候希望将其它函数索引出来便会遇到这个问题,因为是相对引用,所以仅导入包名时并不能索引到其下层函数.
这个代码结构不用改,需要在import的时候使用全路径import.
例如:
使用

1
from testmodule.test_main import test_main

而不是
1
2
import testmodule
testmodule.test_main.test_main # 这个便会报错: ModuleNotFoundError: No module named 'test_main'

总结

参考:使用相对路径导入包中子模块

在本地执行(不通过setup.py打包)的时候,有无__init__.py是没关系的,但是如果要打包且要使用子模块的话就必须含有__init__.py才能被视为子模块

建议都是用相对路径引用,在子模块之间使用“.”,“..”
使用相对路径后:
在本地测试的时候不能用python xxx/xxx.py来运行单元测试.需要改成python -m xxx.xxx
在打包后使用时,需要在import时使用全路径,不能是只导入顶层包名,然后通过包名索引