终于搞懂了Python模块之间的相互引用问题
摘要:详细讲解了相对路径和绝对路径的引用方法。
在某次运行过程中出现了如下两个报错:
报错1: ModuleNotFoundError: No module named '__main__.src_test1'; '__main__' is not a package
报错2: ImportError: attempted relative import with no known parent package
于是基于这两个报错探究了一下python3中的模块相互引用的问题,下面来逐个解析,请耐心看完。
好的,我们先来构造第一个错,测试代码结构如下:
|--- test_main.py
|--- src
|--- __init__.py
|--- src_test1.py
|--- src_test2.py
src_test2.py 代码
class Test2(object):
def foo(self):
print('I am foo')
src_test1.py 代码,引用Test2模块
from .src_test2 import Test2 def fun1():
t2 = Test2()
t2.foo()
if __name__ == "__main__":
fun1()
此时运行 src_test1.py 报错“No module named '__main__.src_test1'; '__main__' is not a package”
问题原因:
主要在于引用src_test2模块的时候,用的是相对路径".",在import语法中翻译成"./",也就是当前目录下,按这样理解也没有问题,那为什么报错呢?
从 PEP 328 中,我们找到了关于 the relative imports(相对引用)的介绍

通俗一点意思就是,你程序入口运行的那个模块,就默认为主模块,他的name就是‘main’,然后会将本模块import中的点(.)替换成‘__main__’,那么 .src_test2就变成了 __main__.src_test2,所以当然找不到这个模块了。
解决方法:
因此,建议的做法是在 src同层级目录创建 引用模块 test_main.py(为什么不在src目录下创建,待会下一个报错再讲),并引用src_test1模块,代码如下:
from src.src_test1 import fun1 if __name__ == "__main__":
fun1()
那为什么这样执行就可以了呢,其中原理是什么呢?我是这样理解的(欢迎纠正):test_main执行时,他被当做根目录,因此他引用的src.src_test1 是绝对路径,这样引用到哪都不会错,此时他的name=‘main’,当执行src_test1的时候,注意了此时test1的name是 src.src_test1,那么在test1中使用的是相对路径,查找逻辑是先找到父节点(src目录),再找父节点下面的src_test2,因此可以成功找到,Bingo!
辅证:
构造一个例子,就可以理解上面的 执行目录就是根目录 的说法了,修改test1,使引用test_main:
from .. import test_main 报错:ValueError: attempted relative import beyond top-level package
OK,那继续构造第二个报错:
上文中说过,解决main 的问题,就是创建一个模块,来调用使用相对路径的模块,那么为什么我不能在相同目录下创建这个文件来调用呢?让我们来测试下代码:
创建test_src.py文件,代码结构变更如下:
|--- test_main.py
|--- src
|--- __init__.py
|--- src_test1.py
|--- src_test2.pys
|--- test_src.py
test_src 代码:
from src_test1 import fun1 if __name__ == "__main__":
fun1()
执行报错:ImportError: attempted relative import with no known parent package
问题原因:
当执行test_src时,按上文理解,此时执行文件所在的目录为根目录,那么引用test1的时候,需要注意的是,此时test1的name属性不再是src.src_test1,因为程序感知不到src的存在,此时他的绝对路径是 src_test1,此时再次引用相对路径查找的test2,同样的步骤,需要先找到父节点,而此时他自己就是根节点了,已经没有父节点了,因此报错“no known parent package”。
解决方法:
此时为了避免父节点产生矛盾,因此将test1中的引入去掉相对引用即可
from .src_test2 import Test2 --> from src_test2 import Test2
继续深入:
那使用相对路径和绝对路径,编译器是怎么找到这个模块的呢?
执行import的时候,存在一个引入的顺序,即优先查找执行目录下有没有此文件,如没有,再查找lib库下,如还没有,再查找sys.path中的路径,如再没有,报错。
所以不管是当前目录,还是 sys.path中的目录,都可以查到 src_test2这个模块,就可以编译成功。
号外:
解决完上述问题后,不管我们用哪种方式,我们调试代码时,都是单个文件调试,但此时根目录就不对了,import方式又要改动,执行起来很麻烦,所以这里推荐另一种方式(有更好的方式欢迎留言),使用sys.path.append()的方法
import sys,os
sys.path.append(os.getcwd())
from src.src_test2 import Test2
使用append的方式,将程序文件根目录放进了sys.path中,然后再引用绝对路径,这样的方式,不管使用上文中的第一或第二执行方式都可以调用,也可以单独编译test1文件,不用修改import路径,也是相对安全的方式。但是缺点就是,如果你修改了某一个包名,需要将所有引用地方都修改一下,工作量大,所以因地制宜。
综上,详细讲解了相对路径和绝对路径的引用方法,现在你应该对import导入的问题有了清晰的理解吧
备注:本文基于Python3.7版本测试
终于搞懂了Python模块之间的相互引用问题的更多相关文章
- 我终于弄懂了Python的装饰器(四)
此系列文档: 1. 我终于弄懂了Python的装饰器(一) 2. 我终于弄懂了Python的装饰器(二) 3. 我终于弄懂了Python的装饰器(三) 4. 我终于弄懂了Python的装饰器(四) 四 ...
- 终于搞懂了vue 的 render 函数(一) -_-|||
终于搞懂了vue 的 render 函数(一) -_-|||:https://blog.csdn.net/sansan_7957/article/details/83014838 render: h ...
- 我终于弄懂了Python的装饰器(一)
此系列文档: 1. 我终于弄懂了Python的装饰器(一) 2. 我终于弄懂了Python的装饰器(二) 3. 我终于弄懂了Python的装饰器(三) 4. 我终于弄懂了Python的装饰器(四) 一 ...
- 我终于弄懂了Python的装饰器(二)
此系列文档: 1. 我终于弄懂了Python的装饰器(一) 2. 我终于弄懂了Python的装饰器(二) 3. 我终于弄懂了Python的装饰器(三) 4. 我终于弄懂了Python的装饰器(四) 二 ...
- css笔记14:css文件之间可以相互引用
css文件之间相互引用是通过@import指令完成的 格式: @import url("被引用的css文件"); 顺便说一句,如果希望在html或者php文件中引用某个xxx.c ...
- [转]我花了一个五一终于搞懂了OpenLDAP
轻型目录访问协议(英文:Lightweight Directory Access Protocol,缩写:LDAP)是一个开放的,中立的,工业标准的应用协议,通过IP协议提供访问控制和维护分布式信息的 ...
- 终于搞懂了shell bash cmd...
问题一:DOS与windows中cmd区别 在windows系统中,“开始-运行-cmd”可以打开“cmd.exe”,进行命令行操作. 操作系统可以分成核心(kernel)和Shell(外壳)两部分, ...
- 探索JAVA并发 - 终于搞懂了sleep/wait/notify/notifyAll
> sleep/wait/notify/notifyAll分别有什么作用?它们的区别是什么?wait时为什么要放在循环里而不能直接用if? ## 简介 首先对几个相关的方法做个简单解释,Obje ...
- python 模块之间相互引用
模块层级关系: ----: |->AA.py |->BB.py |->CC.py AA.py from BB import BB class AA: def sub(self, x) ...
- IntelliJ IDEA 部署 Web 项目,终于搞懂了!
这篇牛逼: IDEA 中最重要的各种设置项,就是这个 Project Structre 了,关乎你的项目运行,缺胳膊少腿都不行. 最近公司正好也是用之前自己比较熟悉的IDEA而不是Eclipse,为了 ...
随机推荐
- Kafka的零拷贝技术Zero-Copy
传统的拷贝过程 流程步骤: (1)操作系统将数据从磁盘文件中读取到内核空间的页面缓存: (2)应用程序将数据从内核空间读入用户空间缓冲区: (3)应用程序将读到数据写回内核空间并放入socket缓冲区 ...
- React项目中webpack的配置过程
初始化一个web项目 使用npm init -y 初始化一个项目 在项目目录下创建src, dist文件夹,创建webpack.config.js配置文件 然后在src文件夹下创建index.js, ...
- c# 引入同一个版本dll(比如包含opencv不同模块的dll,但是版本却是一致的)
1.使用dnSpy.exe工具,打开相关dll 在左侧"管理器"中选中并点击右键 2.在弹出菜单中点击编辑程序集 3.更改名称和版本号 4.工具栏-文件-全部保存 5.在VS中引入 ...
- 停止 Windows 11 更新的行之有效的办法,去掉 Windows Defender 实时监控(Win 11)
用设置的方法,几乎无法达成目的.即使禁用 Windows 11 服务里的 "Windows 更新" 服务,系统也会自己将之改成手动,然后再打开. 先找到控制面板 -> 服务, ...
- GIT协作流程规范
分支模型 集中式的分支模型 目前团队使用的模式属于老旧的集中式分支模型,简单的总结就是: 开发时: 团队的所有成员都在dev分支上开发(也支持少部分的特性分支feature-xxx). 测试时: 当功 ...
- mysql insert的特殊用法
1. 正常的insert语句:插入一条数据如下:INSERT INTO `testdb`.`tb_user` (`id`, `userName`) VALUES (3, '张飞'); 2. inser ...
- 关于区间DP的一点点心得(虽然还是很菜)
自己今天对于区间 DP 的一个总结 区间 DP 的数组一般是二维,其状态一般表示区间 \((l,r)\). 区间 DP 在思考的时候是有一定套路的,思考时可以按照如下方式进行思考: 这段区间要维护的信 ...
- Ubuntu 20.04 挂载 NTFS 硬盘 / 格式化并挂载 EXT4 硬盘
创建挂载目录 mkdir /mnt/hdd 此目录在最后一步中用得到. 确定要挂载的硬盘 fdisk -l 由于我们要挂载的是 NTFS 硬盘,根据上面的信息,可以确定 /dev/sda1 是我们要挂 ...
- Linux TTY/PTS
转载:https://segmentfault.com/a/1190000009082089 当我们在键盘上敲下一个字母的时候,到底是怎么发送到相应的进程的呢?我们通过ps.who等命令看到的类似tt ...
- 后台获取的map集合封装json
let list = [] let arr = {'a':'1','b':'2','c':'3','d':'4'} for(var key in arr){ console.log("key ...