python相对导入

python的相对导入就是:from .[module/package] import xxx的形式。

即加入了以.或者..等表示当前目录和上一级目录的符号,不直接指定目录

(即package,有__init__.py)名,来从对应目录导入module或从对应目录下的module导入模块内名字的方式。

具体细节

python文件中使用相对导入是和当前文件的name属性有关的。即如下的一个文件结构

:pack
|--a.py
|--b.py
|--inner
|--e.py
|--f.py
back
|--c.py
|--d.py

假如在a.py中我们有:from .b import func

print(__name__)

运行a.py的结果是在import语句处产生了错误,当然print(__name__)那一步不会执行,不过它的结果就是__main__。这是因为我们把a.py作为入口脚本直接执行,它的__name__为__main__不存在任何层次结构,对于相对导入而言,它的.符合就无法从当前__name__解析出有效结果。这是语法解析角度的问题,所以使用相对导入的python脚本是不可以被直接作为入口脚本执行。

那么我们如果在a.py中这样导入呢?from back import c

print(__name__)

而c.py中如下:from . import d

print(__name__)

如此我们没有直接运行使用相对导入的python脚本文件,并且在a.py中我们以from back import c的形式导入c,则c的__name__属性应该是back.c,进一步在c.py中使用from . import d,应该也是可行的,名字可以被解析成功。

如此尝试运行a.py,得到的结果仍然是出错,并且在a.py中就出错了,错误显示no module named back。这就牵扯到python中import模块时的寻找问题了。

sys.path

在python的sys模块中的一个方法:sys.path。它返回一个列表,该列表中的每一项都是python文件import时的查找路径。

值得注意的是:

1.sys.path在程序启动时初始化,sys.path[0]表示启用python解释器的脚本所在的目录。如果该目录不可用(比如解释器是通过交互式调用的,或脚本读取自标准输入),则sys.path[0]为空字符串,表示python优先在当前工作目录搜索模块。【这里也解释了上文为何第二次尝试失败的原因,因为back目录不在sys.path的列表中】

2.除了sys.path[0],其余的sys.path的列表项一般都是python的自带标准库与一些第三方包的路径。并且我们还可以在import其它模块前先import sys,对sys.path进行修改,增加,或删除。

3.sys.path是以入口python脚本为准的,即如有一个被import的模块tmp,假如在该模块中也import了其它模块的话,则此时是以入口脚本的sys.path为准进行寻找的,而不是该tmp模块作为入口脚本的时的sys.path。这主要是牵扯到第一点中的sys.path[0]的问题。

那么我们再做一次尝试,这里回想一下目录结构:pack

|--a.py
|--b.py
|--inner
|--e.py
|--f.py
back
|--c.py
|--d.py

我们在a.py中如下:from inner import e

print(__name__)

在inner下的e.py中为:from . import f

print(__name__)

这样我们再运行a.py,其结果是:inner.e

__main__

其中inner.e是e.py的__name__,而__main__为a.py的__name__,成功运行。即sys.path[0]为a.py所在的目录路径,而在该路径下是可以找到inner目录的,则可以顺利的从inner中导入e.py,同时e.py的__name__也是具有层次性的,语法解析没有问题。

Tip: 在e.py中我们有from . import f,但是可不可以写为from .. import b呢?答案是不可以,我们来e.py中的__name__是inner.e,所以在语法解析的时候from .就是对应的__name__中的inner,而..则在inner.e中找不到了,即__name__不是pack.inner.e,所以..对应的语法解析是会fail。

python -m

除了在python文件中直接运行脚本,也可以在命令行中以python xxx.py的形式直接运行该脚本。

那么python -m是做什么的呢?命令行下的help中写着:run library module as a script,网络上博客中的大多的说法是把python脚本作为一个模块运行,两个解释貌似还有点矛盾,其实无须理会这些说法。

python -m运行与python直接运行有两大区别:

1.python直接运行脚本是:python tmp.py的形式,如果有目录结构则是python parent/tmp.py的形式。

而python -m运行则是:python -m tmp的形式,如果有目录结构,则对应为python -m parent.tmp的形式。

2.python tmp.py对应的sys.path[0]为tmp.py所在的目录路径,就算是python parent/tmp.py也是一样。而python -m的sys.path[0]则为执行该命令所在的路径,如python -m tmp,该路径也是tmp.py文件所在路径,并无差。但是python -m parent.tmp的话,对应的路径是parent目录所在路径,而非tmp.py所在的路径。

对于以上的第二点我们再进行一下实例说明,这里再回顾一下我们的目录结构,并且给出上一级目录:direct

|---pack
|--a.py
|--b.py
|--inner
|--e.py
|--f.py
|---back
|--c.py
|--d.py

再回顾我们上文所做的第二次尝试,即在a.py中:from back import c

print(__name__)

在c.py中:from . import d

print(__name__)

我们直接运行a.py或是python a.py都是出错的,因为back不在sys.path中。则我们可以退回到上一级目录direct中,并使用python -m pack.a,那么就可以顺利运行了。原因就是因为python -m的sys.path[0]并不是入口脚本所在的路径,而是命令所在目录路径。