相对导入的官方解释(中文):http://python3-cookbook.readthedocs.io/zh_CN/latest/c10/p03_import_submodules_by_relative_names.html
相对导入解决的问题就是消除绝对路径带来的硬编码问题,具体请看文档。
但是在使用相对导入的时候会出来各种错误,其中最让人费解的可能就是:Attempted relative import beyond toplevel package
其实这个原因是因为包的层次引起的,跟包的路径查找有很大的关系。
先来聊聊python的包的查找方式:
简单的来说,Python 有个PYTHONPATH环境变量和安装路径,这个环境变量和安装路径决定了sys.path(是一个list类型)的值,而python查找包时,python会查找当前路径,然后会按顺序查找sys.path中的路径。
import sys
print sys.path
通过上面代码,你可以看到默认的查找路径。
在这里需要注意的是,python2中,是按上述方式进行的(先查找当前路径,再到sys.path),但是在python3中,是先查找sys.path,找不到再查找当前路径的。
具体可以看官方文档(中文):http://www.pythondoc.com/pythontutorial3/modules.html
如果你看了上面的这边官方文档,你一定看到了python找打印包路径的方法:
print __name__
现在让我们通过这命令来解释,在使用相对导入的时候,为什么会出现:Attempted relative import beyond toplevel package
先来看一下测试目录结构:
.
├── run.py
└── test
├── dir1
│ ├── __init__.py
│ └── moudle1
│ ├── a.py
│ └── __init__.py
├── dir2
│ ├── __init__.py
│ ├── __init__.pyc
│ └── moudle2
│ ├── b.py
│ ├── b.pyc
│ ├── __init__.py
│ └── __init__.pyc
├── run.py
└── test.py
a.py中有一个函数:
def test():
pass
现在,要实现的是,在 b. py 中引用 a.py 的内容:
print __name__
from ...dir1.moudle1.a import test
注意到,有两个
run.py 和
test/run.py 这两个文件的代码都很简单,只有一行代码:
from test.dir2.moudle2.b import test
然后分别运行上面的run脚本:
python test/run.py
这个命令将会打印 b 的包路径并且报错
包路径:dir2.moudle2.b
Traceback (most recent call last):
File "test/run.py", line 1, in <module>
from dir2.moudle2.b import test
File "~/Code/python/mytest/test/dir2/moudle2/b.py", line 3, in <module>
from ...dir1.moudle1.a import test
ValueError: Attempted relative import beyond toplevel package
从打印的包路径来看,这个包的完整路径只是到dir2就没了。而我们的相对导入在解析相对路径的时候,是根据所在脚本的包路径来解析的。
仔细分析一下这个路径:
dir2 | moudle2 | b |
..(父目录) | .(当前目录) | 包名 |
再看下一个命令:
python run.py
这个命令打印的 b 的包路径是:
分析下来就是:包路径:test.dir2.moudle2.b
test | dir2 | moudle2 | b |
...(三级目录) | ..(父目录) | .(当前目录) | 当前目录 |
最后,总结一下,在使用相对导入的时候一定要注意包路径和包的查找路径。要在最顶层的目录添加到 sys.path 中,或者 在最顶层运行脚本。