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,并命名为 xxx

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

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

.
├── main.py
└── mymodule.py
# mymodule.py
NUM = 10

class A:
    pass

print("mymodule")
# main.py

import mymodule

print(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你需要知道的一切!一个视频足够了

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【心得】基于flask的SSTI个人笔记

目录 计算PIN码 例题1 SSTI的引用链 例题2 SSTI利用条件&#xff1a; 渲染字符串可控&#xff0c;也就说模板的内容可控 我们通过模板 语法 {{ xxx }}相当于变相的执行了服务器上的python代码 利用render_template_string函数参数可控&#xff0c;或者部分可控 render_…

分库分表

分库&#xff0c;分表&#xff0c;分库分表 “只分库“&#xff0c;“只分表“&#xff0c;“既分库又分表" 何时分库 在面对高并发的情况下&#xff0c;数据库连接成为性能瓶颈。当数据QPS过高导致数据库连接数不足时&#xff0c;考虑分库。在读多写少的场景下&#x…

基于Vue+SpringBoot的超市账单管理系统 开源项目

项目编号&#xff1a; S 032 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S032&#xff0c;文末获取源码。} 项目编号&#xff1a;S032&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统设计3.1 总体设计3.2 前端设计3…

SQL零基础入门教程,贼拉详细!贼拉简单! 速通数据库期末考!(十)

SQL 函数 SQL 拥有很多可用于计数和计算的内建函数。 比如&#xff1a; AVG() - 返回平均值 COUNT() - 返回行数 MAX() - 返回最大值 MIN() - 返回最小值 SUM() - 返回总和 FIRST() - 返回第一个记录的值 LAST() - 返回最后一个记录的值 GROUP BY 学习SQL函数前&#xff0c…

基于卡尔曼滤波实现行人目标跟踪

目录 1. 作者介绍2. 目标跟踪算法介绍2.1 目标跟踪背景2.2 目标跟踪任务分类2.3 目标跟踪遇到的问题2.4 目标跟踪方法 3. 卡尔曼滤波的目标跟踪算法介绍3.1 所用数据视频说明3.2 卡尔曼滤波3.3 单目标跟踪算法3.3.1 IOU匹配算法3.3.2 卡尔曼滤波的使用方法 3.4 多目标跟踪算法 …

腾讯云轻量数据库1核1G评测和租用价格表

腾讯云轻量数据库测评&#xff0c;轻量数据库100%兼容MySQL 5.7和8.0&#xff0c;腾讯云提供1C1G20GB、1C1G40GB、1C2G80GB、2C4G120GB、2C8G240GB五种规格轻量数据库&#xff0c;阿腾云atengyun.com分享腾讯云轻量数据库测评、轻量数据库详细介绍、特性、配置价格和常见问题解…

【算法】最小生成树——普利姆 (Prim) 算法

目录 1.概述2.代码实现2.1.邻接矩阵存储图2.2.邻接表存储图2.3.测试 3.应用 1.概述 &#xff08;1&#xff09;在一给定的无向图 G (V, E) 中&#xff0c;(u, v) 代表连接顶点 u 与顶点 v 的边&#xff0c;而 w(u, v) 代表此边的权重&#xff0c;若存在 T 为 E 的子集且为无循…

华为模拟器dhcp实验

实验需求&#xff0c;pc1 pc2 pc3 获取到地址且能ping通&#xff0c;pc1 pc2 为地址池模式&#xff0c;pc3为接口模式 上配置 #sysname AR1# dhcp enable # interface GigabitEthernet0/0/0ip address 10.0.47.254 255.255.255.0 dhcp select relaydhcp relay server-ip 10.0…

认识.NET Aspire:高效构建云原生应用的利器

简介 在几天前的.NET 8发布会上&#xff0c;来自微软的Glenn Condron和David Fowler为我们演示了.NET Aspire&#xff0c;在Visual Studio的帮助下&#xff0c;它展现出了惊人的开发效率。 短短的十分钟内&#xff0c;David现场演示了如何轻松创建了一个具有服务发现&#xf…

基于不确定性感知的脑肿瘤分割多维互学习

Uncertainty-Aware Multi-Dimensional Mutual Learning for Brain and Brain Tumor Segmentation 一基于不确定性感知的脑肿瘤分割多维互学习背景贡献实验方法Uncertainty-Aware Mutual Learning&#xff08;具有不确定性的相互学习&#xff09; Thinking 一基于不确定性感知的…

设计模式常见面试题

简单梳理下二十三种设计模式&#xff0c;在使用设计模式的时候&#xff0c;不仅要对其分类了然于胸&#xff0c;还要了解每个设计模式的应用场景、设计与实现&#xff0c;以及其优缺点。同时&#xff0c;还要能区分功能相近的设计模式&#xff0c;避免出现误用的情况。 什么是…

Git精讲

Git基本操作 创建Git本地仓库 git initgit clone 配置Git git config [--global] user.name "Your Name" git config [--global] user.email "emailexample.com"–global是一个可选项。如果使用了该选项&#xff0c;表示这台机器上所有的Git仓库都会使…

Network(三)动态路由与ACL配置

一 三层交换机 1 三层交换机概述 三层交换二层交换三层转发 2 虚拟接口概述 在三层交换机上配置的VLAN接口为虚拟接口&#xff0c;使用Vlanif&#xff08;VLAN虚拟接口&#xff09;实现VLAN间路由&#xff0c;VLAN接口的引入使得应用更加灵活 三层交换机VLAN间通信的转发…

Cross-View Transformers for Real-Time Map-View Semantic Segmentation 论文阅读

论文链接 Cross-View Transformers for Real-Time Map-View Semantic Segmentation 0. Abstract 提出了 Cross-View Transformers &#xff0c;一种基于注意力的高效模型&#xff0c;用于来自多个摄像机的地图视图语义分割使用相机感知的跨视图注意机制隐式学习从单个相机视…

第93步 深度学习图像分割:PSPNet建模

基于WIN10的64位系统演示 一、写在前面 本期&#xff0c;我们继续学习深度学习图像分割系列的另一个模型&#xff0c;PSPNet。 二、PSPNet简介 &#xff08;1&#xff09;金字塔池化模块 (Pyramid Pooling Module) PSPNet的核心是其金字塔池化模块&#xff0c;该模块能够捕…

4 redis的HyperLogLog入门原理

一、HyperLogLog&#xff08;字符串类型&#xff09; 需求&#xff1a;大型网站(不在大厂基本上用不到) 每个网页每天的 UV 数据(独立访客)&#xff0c;统计如何实现&#xff1f;(尽量少的占用存储空间) Redis 提供了 HyperLogLog 数据结构就是用来解决这种统计问题的。Hyper…

[ 云计算 | AWS 实践 ] Java 如何重命名 Amazon S3 中的文件和文件夹

本文收录于【#云计算入门与实践 - AWS】专栏中&#xff0c;收录 AWS 入门与实践相关博文。 本文同步于个人公众号&#xff1a;【云计算洞察】 更多关于云计算技术内容敬请关注&#xff1a;CSDN【#云计算入门与实践 - AWS】专栏。 本系列已更新博文&#xff1a; [ 云计算 | …

六、文件上传漏洞

下面内容部分&#xff1a;参考 一、文件上传漏洞解释 解释&#xff1a;文件上传漏洞一般指的就是用户能够绕过服务器的规则设置将自己的木马程序放置于服务器实现远程shell&#xff08;例如使用蚁剑远程连接&#xff09;&#xff0c;常见的木马有一句话木马(php) 无需启用sho…

各类语言真实性能比较列表

这篇文章是我所做或将要做的所有真实世界性能比较的索引。如果你对想要看到的其他真实世界案例有建议&#xff0c;请在评论中添加。 用例 1 — JWT 验证 & MySQL 查询 该用例包括&#xff1a; 从授权头部获取 JWT验证 JWT 并从声明中获取电子邮件使用电子邮件执行 MySQL…

〖大前端 - 基础入门三大核心之JS篇㊳〗- DOM访问元素节点

说明&#xff1a;该文属于 大前端全栈架构白宝书专栏&#xff0c;目前阶段免费&#xff0c;如需要项目实战或者是体系化资源&#xff0c;文末名片加V&#xff01;作者&#xff1a;不渴望力量的哈士奇(哈哥)&#xff0c;十余年工作经验, 从事过全栈研发、产品经理等工作&#xf…