相对导入的官方解释(中文):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.pytest/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就没了。而我们的相对导入在解析相对路径的时候,是根据所在脚本的包路径来解析的。

仔细分析一下这个路径:

dir2moudle2b
..(父目录).(当前目录)包名
从这个表就可以看出来,三级目录 ... (三个点) 已经超出了最顶层的目录结构。因此才会报错。

再看下一个命令:

python run.py 
这个命令打印的 b 的包路径是:

包路径:test.dir2.moudle2.b


分析下来就是:

testdir2moudle2b
...(三级目录)..(父目录).(当前目录)当前目录
通过上面的比较,可以知道,包路径跟运行脚本所在的目录有关系,而更加本质的原因是,跟包的查找路径有关系(python会在运行脚本所在的路径查找包)。对此有疑惑的同学,也可以试试,比如:把不同的包路径添加到 sys.path 中,然后在其他地方执行run脚本,是可以得到同样的结果的。


最后,总结一下,在使用相对导入的时候一定要注意包路径和包的查找路径。要在最顶层的目录添加到 sys.path 中,或者 在最顶层运行脚本。


Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐