Python的package和relative import

这篇博客主要解决python项目中,各个模块相互import导致的各种奇怪问题,主要参考 stackoverflow

常见错误:

ValueError: attempted relative import beyond top-level package

SystemError: Parent module '' not loaded, cannot perform relative import

ImportError: No module named 'xxxx'

 

1. python什么情况下会把文件夹认为是package呢?

答: 只要在文件夹下建一个 __init__.py 即可

 

2. python的script(.py)什么情况下被当成top-level script,什么时候module?

答:当使用python xx.py时,xx.py被认为top-level script,当使用import xx或者python -m xx,此时xx.py被认为module

 

3. python和python -m的区别

答: 看下以下目录结构

program
|--  test
    |-- main.py
    |-- __init__.py      # 必须加,否则test就不是package

其中,main.py中的代码如下

import sys
print(sys.path)
print(__name__)
print(__package__)

我们在program目录下执行:

# python test/main.py
['E:program\\test', ...]
__main__
None

# python -m test.main
['', ...]
__main__
test

可以看到两种方式__name__都设置为了__main__,  但是__package__是有区别的,不加-m并不会认为main.py在任何package里,并且sys.path加入了main.py所在的目录,也就是解释器通过扫描sys.path,在目录E:program\\test下找到的main.py,所以叫top-level script,而加-m后,main.py被认为在package中,并且sys.path加入了当前目录(执行python -m的目录),也就是解释器通过扫描sys.path在当前目录E:program下找到test (package) 下的 main.py,所以叫module

 

4. 相对导入有以下形式

import . from xx             # 从当前package导入xx
import .. from xx            # 从父package导入xx
import ... from xx           # 从祖父package导入xx

# 多少个点就是向上多少层package

我们在test目录下新建hello.py,输入print('hello'), 然后将main.py改成from . import hello, 此时用上述两种方式运行:

# python test/main.py
SystemError: Parent module '' not loaded, cannot perform relative import

# python -m test.main
hello

不加-m报错,原因很简单,因为上面这种方式__package__为None, main.py根本没有在任何package下,而加-m就成功运行了,此时.指的就是test (package),而这个package下刚好有hello.py,因此成功导入了

如果我们把目录切到test下再运行python -m main, 显然也报错啊,因为此时__package__为'',sys.path被加入了E:program\\test

所以我们可以看到相对导入显然和在什么路径下执行python命令有很大关系,本质上是__package__在发生变化

来一个复杂的项目结构:

program
|-- test1
    |-- test1.py
    |-- __init__.py
|-- test2
    |-- test2.py
    |-- __init__.py
    |-- test3
        |-- test3.py
        |-- __init__.py
|--  test
    |-- main.py
    |-- __init__.py

各文件内容如下:

# main.py
print('p: main')
from .test2 import test2
print('p: main')

# test1.py
print('p: test1')
print(__name__)
print(__package__)
print('p: test1')

# test2.py
print('p: test2')
print(__name__)
print(__package__)
from .test3 import test3
print('p: test2')

# test3.py
print('p: test3')
print(__name__)
print(__package__)
from ...test1 import test1
print('p: test3')

进入program目录,执行python -m test.main得到如下运行结果

p: main
p: test2
test.test2.test2
test.test2
p: test3
test.test2.test3.test3
test.test2.test3
p: test1
test.test1.test1
test.test1
p: test1
p: test3
p: test2
p: main

这个结果很符合预期,main.py在test(package)下,所以main.py中使用from .进入test,而test2.py在test.test2(package),也使用from .进入test2, 而test3.py在test.test2.test3(package), 使用from ...向上三级package到test下,然后到test1下进入test1.py

对于上面的例子,当在test目录下执行命令python或者python -m显然报错,原因就是上面所提到的main.py的不在任何package里,不能使用相对导入。如果把main.py改成from test2 import test2,在test2下执行python -m main呢?此时仍然会报错,但是错在test3.py,因为此时sys.path里包含E:program\\test,test并不算package了,所以test3.py在test2.test3(package)里,所以此时...到超出了顶层package,那用..呢?也不行,因为..表示移到父package test2,而test2下并没有package test1,所以这种情况完全没法实现相对导入的,那怎么办?直接from test1 import test1,因为E:program\\test下直接就有test1 package

总结,原来执行python命令的位置一改变,代码需要发生巨变啊,相对导入这种方式好扯淡啊。那为什么很多优秀的源码还使用了呢? 原因很简单,因为作者认为你不会进入项目目录去执行命令,正常用户都是pip install package, 然后自动安装到site-package目录下,而sys.path默认就加了site-package目录,所以当你import的时候就从site-package下的package引入,好比在我的例子里,你只会从program目录去执行,真是妙哉啊! 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值