python的相对导入和绝对导入(小白教程)

热门标签

GentleCP

发表文章数:46

首页 » 技术杂谈 » Python » 正文
摘要:本文从实际案例出发,详细地讲述了python中相对导入和绝对导入可能出现的问题以及对于相对导入和绝对...

前言

在运行python项目的时候,你是否遇到过这样的问题(ModuleNotFoundError):
python的相对导入和绝对导入(小白教程)
又或者是这样的问题(attempted relative import beyond top-level package):
python的相对导入和绝对导入(小白教程)

其实这些问题都涉及到了python的导入机制,关于第一类问题,可以阅读我的这篇文章-python的import机制当然我建议你看完import机制之后再回过头将本篇文章完整看完,相信我,你会有不一样的收获。

实际案例

为了能更直观地理解,我们创建一个Import的python项目,并在其中创建目录结构如下:

Import
    |--A
        |--D
            |--d.py
        |--E
            |--e.py
        |--a.py
    |--B
        |--b.py
    |--C
        |--c.py
    |--main.py  # 项目入口

其中A,B,C,D均为目录,a.py, b.py, c.py, d.py 都是一个简单python文件,每个文件中有一个对应的hello函数,用于打印对应的hello信息,例如a.py 中:

python的相对导入和绝对导入(小白教程)

其他的就是将a换成对应字母。
我们从实际项目开发出发,一般项目开发中有多级目录,我们已经实现了,在项目本身根目录下会有一个入口文件(main.py),他用于调用其他模块的文件达到执行整个项目的目的。

项目根目录下运行

我们在main.py中写入如下代码,并运行查看结果(为了体现ide的特殊之处,后面的运行结果如果不一样,我会展示ide和终端运行两种结果,原因你在看过python的import机制就会明白):

"""
项目的入口,用于调用其他模块,运行整个项目
"""
from A import a
from B import b
from C import c
if __name__ == '__main__':
    a.hello_a()
    b.hello_b()
    c.hello_c()
  • pycharm运行
    python的相对导入和绝对导入(小白教程)

可以发现这样导入运行都是没问题的。

但是这是最简单的情况,实际开发中各个模块之间会相互调用,并不仅仅只是main去调用其他人,因此我们在b.py中修改代码如下:

from A import a
def hello_b():
    print("hello, I'm b!")
    a.hello_a()

在d.py中修改代码如下:

from C import c
def hello_d():
    print("hello, I'm d!")
    c.hello_c()

修改main.py如下:

from A import a
from B import b
from C import c
from A.D import d
if __name__ == '__main__':
    a.hello_a()
    b.hello_b()
    c.hello_c()
    d.hello_d()

再运行main.py查看结果:

  • pycharm运行

    python的相对导入和绝对导入(小白教程)

可以发现还是没有问题的。

下面我们将绝对导入改成相对导入的形式看看结果(为了简便,这里仅对d.py进行修改):

from ..E import e
def hello_d():
    print("hello, I'm d!")
    e.hello_e()
  • pycham运行
    python的相对导入和绝对导入(小白教程)

截至目前,我们可以得出一个结论,只要从main.py入口运行,其他模块的内部导入符合python导入规则,无论相对导入还是绝对导入都是不会发生ModuleNotFoundError & attempted relative import beyond top-level package这两种问题的

那么我们的问题又是怎么产生的呢?别急,之前我是在d.py中修改相对导入,下面我在b.py中做相同的操作,如下:
python的相对导入和绝对导入(小白教程)
注意我这里用图片而不是代码是想告诉你,pycharm对这行代码没有显示红色波浪线错误,证明语法上是没问题的。

  • pycharm运行
    python的相对导入和绝对导入(小白教程)

可以看到同样的操作在b上就出了问题, 提示attempted relative import beyond top-level package。

这里直接给出解释:python默认将运行文件作为顶级,其子目录中的文件仅能导入比执行文件低级或同级的目录及文件。例如以main.py运行,那么Import就是顶级包,A,B,C均为其同级包,..A首先想要访问Import,再从Import中导入A,这是不被允许的。所以就报了这个错误。而对d.py而言,..E首先想要访问A,再从A中导入E,这是可以的,因为A是main.py的同级目录,允许被访问。所以b.py想要导入A,必须直接用from A import ...的形式。

子目录下运行

前面一直采用main.py导入其他模块的包调用运行的形式,实际开发过程也是这样,那么我们如果遇到想要单独运行某个单一文件,或者仅执行某个子目录下内容进行部分测试怎么办呢?

一个简单的解决方法是在main.py的同级目录新建一个test.py用相同的方式导入运行对应模块。但有的人就是喜欢直接执行对应文件来查看输出呢?显然这种方式也更佳直白,简单。

还是以d.py为例,修改d.py内容让其进行单独运行测试:

python的相对导入和绝对导入(小白教程)

  • pycharm运行
    python的相对导入和绝对导入(小白教程)

咦?原来好好的,单独运行d怎么报错了?(注意pycharm仍然没有红波浪线提示)还记得之前说的python会把当前运行文件的目录作为顶端包吗?此时用d.py运行,那么顶端包就是D,..E试图访问A自然超出了范围。

那怎么办,我在d中该如何访问到E?自然是借助A,用相对访问的方式访问不到A,我们改成绝对导入

from A.E import e
def hello_d():
    print("hello, I'm d!")
    e.hello_e()
if __name__ == '__main__':
    hello_d()

python的相对导入和绝对导入(小白教程)
成功了?先别急着高兴,这里还有一个隐患(这个隐患跟python的import机制一文内容有关,你能猜到吗?),我们先扣着在后面继续说。

经过上面的测试我们总结出了以下两点:

    1. 用项目根目录入口文件执行,无论是相对导入,还是绝对导入都没有ModuleNotFoundError & attempted relative import beyond top-level package两种问题
    1. 相对导入会将执行文件对应目录设置为top-level package,所以在单独运行使用了相对导入的python文件时,会导致attempted relative import beyond top-level package问题。

那么是不是就用绝对导入就比相对导入要好呢?不一定,这个问题与前面说的隐患有联系。我们在终端直接运行d.py查看结果:
python的相对导入和绝对导入(小白教程)
你会发现程序找不到A了,这个原因在python的import机制里解释过。pycharm会将项目根目录自动添加到sys.path中,所以在pycharm中运行的时候,Import是被加到sys.path中的,自然可以通过Import找到A,但是在终端运行的时候就不会添加,导致A无法被找到。

所以你发现了吗?如果我们只采用绝对导入的方式,一旦有人将我们的项目作为子项目添加到他的项目中,就会发现模块找不到的问题,因为pycharm自动添加的是他的项目的目录,而不是我们项目的目录,原本直接A.E可以访问的,需要X.Import.A.E才能访问(X是他的项目目录名)。

总结

OK,说了那么多,你肯定会问我,那我到底什么时候用相对导入,什么时候用绝对导入的方式才好啊?我罗列了几种情况如下,你可以对照参考:

仅供参考,实际开发请依据需求设计

  • 1. 该项目是一个独立项目,不会合并到其他项目内容当中 ,且该项目体量较小
    我说的体量,指的是项目的分级结构深度。例如Import->A->D仅有3级结构,属于体谅较小。采用绝对导入的方式,即导入模块的时候从根目录算起,导入都是Demo.A.B 的形式,这种方式的好处是简洁明了,随便拿出单独一个文件你都知道它从哪里导入了什么,并且这样允许你直接对单个文件或子模块进行运行测试。不会出现attempted relative import beyond top-level package。

  • 2. 该项目是一个独立项目,不会合并到其他项目内容中,但是该项目体量较大
    体量较大的项目如果你仍全部用绝对导入的方式,也不是不行,但容易出现下面的情况,你要导入一个包from Demo.A.B.C.D.E import method,这种方式显得十分冗余且不美观。所以这时候我们采用绝对导入和相对导入结合的形式,如果要导入的包通过2层就能找到(Demo.A),那可以用绝对导入的形式,不然采用相对导入,例如Demo下的一个小demo目录下的我建议都可以采用相对导入的形式,就像例子中的d.py导入Efrom ..E import e。你可以通过在小demo目录下设置一个test.py对该demo下的内容进行测试,由于运行test.py会将该小demo的目录添加到sys.path中,所以test.py中可以采用绝对导入的形式导入要测试的包。

  • 3.该项目是一个子项目 ,未来要添加到其他项目中
    这种就没得说了,采用相对导入的形式,但是为了保证你自己单独测试项目的时候依然可以运行,你可以给你的项目多嵌套一层目录,假装它已经被包含到其他项目中了。

如果你还有任何疑惑,欢迎评论留言给我,我会及时给予反馈。

标签:

未经本人允许不得转载!作者:GentleCP, 转载或复制请以 超链接形式 并注明出处 求索
原文地址:《python的相对导入和绝对导入(小白教程)》 发布于2019-08-16

分享到:
赞(0)

评论 抢沙发

评论前必须登录!

  注册



Vieu4.5主题
专业打造轻量级个人企业风格博客主题!专注于前端开发,全站响应式布局自适应模板。
切换注册

登录

忘记密码 ?

您也可以使用第三方帐号快捷登录

Q Q 登 录
微 博 登 录
切换登录

注册