python 的 import 机制

引言

对于初学 python,或多或少在 import 一个 module 时遇到过 ImportError: attempted relative import with no known parent package 这样的错误信息。对于初学 python,遇到这样的问题是因为在执行 python xxx.py 程序时,xxx.py 程序中 import 了其他 package 下的 module,导致了 xxx.py 程序中的 import 不能正确查找到所需要 module 路径,这背后的原理是什么呢?本文通过资料查询和实际测试,对 python 中的 import 的机制进行梳理总结。

基本概念

当使用 python 进行一些工程化的工作时,对代码的组织就非常重要了。在组织 python 的程序文件时,最重要的两个概念就是 package 和 module 了。下面对这两个概念进行解释说明。

module

An object that serves as an organizational unit of Python code. Modules have a namespace containing arbitrary Python objects. Modules are loaded into Python by the process of importing.

上述摘自 python 官文文档中对 module 的解释。可以从两个方面来理解,一方面,module 是组织 python 程序文件的最小单位,通常一个 .py 文件就是一个 module;当然不是只有 .py 文件能作为 module,其他程序文件提供给 python 程序 import 也能作为 module。另一方面,一个 module 是一个命名空间,在该命名空间下可以包含许多任意的 python 对象。

package

A Python module which can contain submodules or recursively, subpackages. Technically, a package is a Python module with a __path__ attribute.

上述摘自 python 官文文档中对 package 的解释,我们也可以从两个方面来理解。一方面,package 也是一个 module,可以用来被 import,此外 package 具有 __path__ 熟悉,而 module 没有,下文会稍作解释。另一方面,package 是管理组织 .py 程序文件比 module 更大一级的单位,一个 package 中可以有多个 module 和多个子 package。

import 在背后做了哪些工作

我们通常在编写 python 程序时,会在文件的头部使用 import 来导入我们在编写程序过程中所需要的 builtin module 或者是安装的第三方 module,这样就可以在程序文件中使用导入的 module 提供的功能了。看一下官文文档中对 import 的描述。

The process by which Python code in one module is made available to Python code in another module.

那在 import 的背后,程序做了哪些工作?在了解了这些背后的细节后,就自然揭开了 ImportError: attempted relative import with no known parent package 错误的面纱。

首先解密当执行到 import 语句时,干的“第一件”事是什么?
import 的作用是导入 module,因此当执行到 import 语句时,“第一件事”就是查找 module,看看能够查找到指定的 module 名。import 在 sys.path 中查找 module,sys.path 是一个由字符串组成的列表,用于指定模块的搜索路径,默认的搜索路径如下所示。按照sys.path列表中元素顺序进行搜索,搜索到第一个满足条件的就不再往下搜索。
在这里插入图片描述

而对于 python xxx.py 执行程序,会在 sys.path 的首位置添加 xxx.py 所在的目录的绝对路径。如下所示:
在这里插入图片描述

在完成 import 的第一步工作后,就开始执行 “真正的” import 动作了。又因 import 的对象是 module,而 package 也是一种特殊的 module,而对于 import package 和 import module 在细节上是不同的,下面先来了解 import module 背后的工作。


import module 背后发生了什么? (注意,这里不考虑 import package) ^import-module

import 一个 module 的背后,其实就是执行了该 module,然后将执行结果保存到一个变量中(另一个视角为,该变量表示一个命名空间)。又因为除了作为 main module(下文会解释什么是 main module),其他 module 中主要是定义变量、类和函数,即主要用来定义 python 对象,因此执行该 module 就是获取该 module 下定义的 python 对象。因此换一个角度进行理解,import module 背后其实就是把该 module 中的所有 python 对象保存到一个命名空间下,然后在 import 了该 module 的程序文件中,就可以使用 xxx.yy 来使用该 module 中定义的python对象了,其中 xxx 是该命名空间,直率的理解就是将该命名空间下的所有python对象保存在名为 xxx 的变量中,然后使用 xxx. 的方式访问python对象。如下所示:

import xxx   # 导入 mmodule xxx,并命名为 xxximport xxx as x  # 导入 module xxx,并命名为 x

来看一个简单的 import module 的例子,在同一个目录下有 main.pymymodule.py

.
├── main.py
└── mymodule.py
# mymodule.py
NUM = 10class A:passprint("mymodule")
# main.pyimport mymoduleprint(mymodule)print(mymodule.NUM)print(mymodule.A)

python main.py 运行程序,结果如下:

mymodule
<module 'mymodule' from '/workspace/pythonCode/test_dir/mymodule.py'>
10
<class 'mymodule.A'>

从运行结果来看,先执行了 mymodule.py module,因为在 print("mymodule") 语句在 mymodule.py 模块中。
其次,mymodule 作为一个 python 的 module 对象被打印输出。


import package 背后发生了什么?

首先,package 和文件中的文件夹具有同等性质,即一个 package 可以拥有多个子 package 和 多个 module;其次,在python 中,package 被当作一种特殊的 module看待。以下面这个程序目录为例,蓝色为 package,其他为 module。main.py 作为我们程序运行的 main module(下问会介绍什么是 main module)。

在这里插入图片描述

main.py 中内容如下,python main.py 运行该程序。

import mypackage
print(mypackage)
# 运行结果:
# <module 'mypackage' (<_frozen_importlib_external._NamespaceLoader object at 0x7f2c47556200>)>

从运行结果可知,package 被当作是一种 module。因此 import package 应该和 import module 的行为类似,会执行 module 表示的 .py 程序,而 package 又是一个 package,本身不是 .py 程序。(哈哈,有点绕绕的)因此,对于上述的示例,import mypackage 语句就什么也没执行,只是单独地将 mypackge 这个 package 表明为一个 module 并复制给变量(或者称为命名空间)mypackage,所以 mypackage 中就不包含任何 python 对象,因此当尝试 xxx. 的方式调用该 package 下的 module 时,会报错,如下所示:
在这里插入图片描述

关于 import 某个 package 下的 module 的写法,想必只要学了几天python就没有不会的,这里就不再唠叨其中的细节,这里只介绍其中两种写法和其表达的含义。仍然以上面的示例为例。

第一种,import mypackage.module1。这个 import 语句蕴含了两层含义,第一层,将 mypackage 下的 module1 导入(import),并将其保存到名为 mypackage.module1 的变量中。第二层,将 mypackage 这个 module 保存到名为 mypackage 的变量中。
在这里插入图片描述

第二种,from mypackage import module1 as m。将 mypackage 下的 module1 导入,并保存到名为 m 的变量中,注意,这里 mypackage 这个 module 是没有被 import,这里需要和 import mypackage.module1 as m 语句进行区分。

import mypackage.module1 as m 语句和上述第一种几乎一样,只不过是将 mypackage 下的 module1 导入(import)保存到名为 m 的变量中。

理解了上述两种 import 中表达的含义之后,其他形式的 import 自然也就清楚了。

小结一下。无论是 import module 还是 import package,都是需要在 sys.path 中先查找到正确的路径,然后执行该 module。这里在补充两点,第一点,对于多次相同的 import,只会执行一次,执行成功后会将其结果缓存到 sys.modules 中。第二点,上述 import 的方式是 absolute import,没有谈到 relative import,两种 import 的方式存在细微的差别,将在 relative import 中需要避免的问题 解释。


此外,这里有必要再补充一下 package 下的 __init__.py 文件的用途。在上文中介绍到,import module 会将 module 表示的 .py 程序执行一次,而 import package 则什么都不会执行。这里需要补充的是,当 package 下存在 __init__.py 文件 时,import package 会执行 __init__.py 文件,将其结果保存到 package 对应的命名空间中。
在大多项目中,package 下都会存在一个 __init__.py 文件,用以在 import package 时进行一些预处理相关的操作,这可以作为另一个话题来写作了。

ImportError: attempted relative import with no known parent package ^relative-import

先说明 relative import 的规则,然后再解释 ImportError 的原因。

对于 relative import,需要先找到它的绝对路径,即将相对路径转换为绝对路径,然后再 import。转换的方法是,通过该 module 的 __package__ 变量去计算绝对路径。下面看一个例子。

在这里插入图片描述

把程序之间 module 的 import 关系以图的方式呈现,如下所示:
在这里插入图片描述

由上图可以看到,在 /packageTwo 下的 moduleTwo.py 中使用了 relative import,我们以这个例子来解释 relative import 是如何查找 module 的。上述 relative import 的语句为 from .subPackage import submodule,首先将该 relative import 语句转化为 absolute import。因为该 import 语句在 moduleTwo.py 中执行,而 moduleTwo 的 __package__ 属性值为 packageTwo(可以在 moduleTwo.py 中 print(__package__) 查看),relative import 转换为 absolute import 的方式为在:获取执行该 relative import 语句的 module 的 __package__ 属性值,然后将该属性值添加到 relative import 语句前,因此 from .subPackage import submodule 语句被转换为 from packageTwo.subPackage import submodule,而 packageTwo 在 test_dir 目录下,test_dir 目录被添加到了 sys.path 中(回顾上文),因此最终该 relative import 就能被正确查找到。


上面解释了 relative import 的规则后,我们来看下 ImportError: attempted relative import with no known parent package 错误的原因。

仍然是上面的示例,这里运行 packageTwo 下的 moduleTwo.py,复现 ImportError 错误,如下图所示:
在这里插入图片描述

因为在上文中已经详细介绍了 relative import 的规则,这里就不再啰嗦的又分析一遍,直接给出造成该 ImportError 的原因:当 python xxx.py 执行python程序时,xxx.py 会被作为 main module,而 main module 是不属于任何 package 的,即 main module 的 __package__ 属性变量为 None(print(__package__) 查看),因此在上述情况下,不能将 relative import 转换为正确的 absolute import。

总结

本文总结了 python 的 import 机制,全文算是 关于import你需要知道的一切!一个视频足够了 的学习总结。关于 import 还有更多进阶内容,例如动态 import,这些就留到后面继续学习了。

参考资料

关于import你需要知道的一切!一个视频足够了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/194850.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ubuntu安装完qt后发现找不到图标

layout: post # 使用的布局&#xff08;不需要改&#xff09; title: Qt启动问题 # 标题 subtitle: ubuntu安装完Qt #副标题 date: 2023-11-18 # 时间 author: BY ThreeStones1029 # 作者 header-img: img/about_bg.jpg #这篇文章标题背景图片 catalog: true # 是否归档 tags: …

R语言:利用biomod2进行生态位建模

在这里主要是分享一个不错的代码&#xff0c;喜欢的可以慢慢研究。我看了一遍&#xff0c;觉得里面有很多有意思的东西&#xff0c;供大家学习和参考。 利用PCA轴总结的70个环境变量&#xff0c;利用biomod2进行生态位建模&#xff1a; #------------------------------------…

【半监督学习】CNN与Transformer的结合

本文介绍了几篇结合使用CNN和Transformer进行半监督学习的论文&#xff0c;CNN&Trans&#xff08;MIDL2022&#xff09;&#xff0c;Semi-ViT&#xff08;ECCV2022&#xff09;&#xff0c;Semiformer&#xff08;ECCV2022&#xff09;. Semi-Supervised Medical Image Seg…

【Promise12数据集】Promise12数据集介绍和预处理

【Segment Anything Model】做分割的专栏链接&#xff0c;欢迎来学习。 【博主微信】cvxiayixiao 本专栏为公开数据集的介绍和预处理&#xff0c;持续更新中。 要是只想把Promise12数据集的raw形式分割为png形式&#xff0c;快速导航&#xff0c;直接看2&#xff0c;4标题即可 …

交易机器人-规则部分

微信公众号&#xff1a;大数据高性能计算 背景 背景是基于人工去做交易本身无法做到24小时无时无刻的交易&#xff0c;主要是虚拟币本身它是24小时交易&#xff0c;人无法做到24小时盯盘&#xff0c;其次就是如果你希望通过配置更加复杂的规则甚至需要爬取最新的信息走模型进行…

个人博客添加访问人数以及访问时间-githubpage

layout: post # 使用的布局&#xff08;不需要改&#xff09; title: 个人博客添加访问人数以及访问时间 # 标题 subtitle: 个人博客优化 #副标题 date: 2023-11-18 # 时间 author: BY ThreeStones1029 # 作者 header-img: img/about_bg.jpg #这篇文章标题背景图片 catalog: tr…

MySQL 的执行原理(四)

5.5. MySQL 的查询重写规则 对于一些执行起来十分耗费性能的语句&#xff0c;MySQL 还是依据一些规则&#xff0c;竭尽全力的把这个很糟糕的语句转换成某种可以比较高效执行的形式&#xff0c;这个过程也可以 被称作查询重写。 5.5.1. 条件化简 我们编写的查询语句的搜索条件…

【数据结构初阶】单链表SLlist

描述 不同于顺序表&#xff0c;顺序表的数据是存储在一个连续的空间里的 而链表它是链接起来的结构体地址。 所以我们不用像顺序表一样先创建一块空间出来&#xff0c;而是创建一个能存数据节点和节点与下一个节点之间的连接&#xff1b; 所以&#xff1a;“一个能存数据节点…

redis+python 建立免费http-ip代理池;验证+留接口

前言: 效果图: 对于网络上的一些免费代理ip,http的有效性还是不错的;但是,https的可谓是凤毛菱角; 正巧,有一个web可以用http访问,于是我就想到不如直接拿着免费的HTTP代理去做这个! 思路: 1.单页获取ipporttime (获取time主要是为了后面使用的时候,依照时效可以做文章) 2.整…

STM32串口重定向/实现不定长数据接收

STM32串口重定向/实现不定长数据接收 重定向MicroLIB 不定长数据接收 这是一期STM32内容代码分享&#xff0c;关于STM32重定向的代码和一些出现的问题吗&#xff0c;以及串口接收不定长数据思路 重定向 重定向的功能&#xff1a;能够在STM32中使用printf函数通过串口发送数据 …

Redis:Java客户端

前言 "在当今大数据和高并发的应用场景下&#xff0c;对于数据缓存和高效访问的需求日益增长。而Redis作为一款高性能的内存数据库&#xff0c;以其快速的读写能力和丰富的数据结构成为众多应用的首选。与此同时&#xff0c;Java作为广泛应用于企业级开发的编程语言&…

el-table树形数据隐藏子选择框

0 效果 1 代码 type是table数据中用来区分一级和二级的标识 // 隐藏子合同选择框 cellNone(row) {if (row.row.type 3 || row.row.type 4) {return "checkNone";} }, <style lang"scss" scoped>::v-deep {.checkNone .el-checkbox__input {displa…