python 的 import 机制

news2024/9/24 19:16:58

引言

对于初学 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你需要知道的一切!一个视频足够了

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

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

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

基于springboot实现应急救援物资管理系统项目【项目源码】计算机毕业设计

基于springboot实现应急救援物资管理系统演示 JAVA简介 JavaScript是一种网络脚本语言&#xff0c;广泛运用于web应用开发&#xff0c;可以用来添加网页的格式动态效果&#xff0c;该语言不用进行预编译就直接运行&#xff0c;可以直接嵌入HTML语言中&#xff0c;写成js语言&a…

Centos7 重置 Root 密码

Centos7 重置 Root 密码 1.启动服务器2.编辑启动项3.修改密码4.重新登陆 1.启动服务器 启动服务器后&#xff0c;不要直接进入系统&#xff0c;在开机页面按键盘【E】 2.编辑启动项 按【E】后进入如下页面&#xff0c;并按向下箭头&#xff0c;找到如图位置&#xff0c;添加如…

慢日志查询

概述 MySQL的慢查询日志是MySQL提供的一种日志记录&#xff0c;它用来记录在MySQL中响应时间超过阀值的语句&#xff0c;具体指运行时间超过 long_query_time 值的SQL&#xff0c;则会被记录到慢查询日志中&#xff0c;ong_query_time 的默认值为 10&#xff0c;意思是运行10S…

JAVA多线程(5)

JAVA多线程(5) 线程安全问题概述 卖票问题分析 单窗口卖票 一个窗口(单线程)卖100张票没有问题 单线程程序是不会出现线程安全问题的 多个窗口卖不同的票 3个窗口一起卖票,卖的票不同,也不会出现问题 多线程程序,没有访问共享数据,不会产生问题 多个窗口卖相同的票 3个窗口…

QT下使用QChart绘制曲线

目录 头文件内容构造函数AddSeries方法UpdateSeries方法AppendSeriesData方法SetLegendVisiableSetRubberBandCPP内容测试函数 需要用到的头文件&#xff1a; #include <QtCharts/QChart> #include <QtCharts/QChartView> #include <QtCharts/QValueAxis> #…

大数据安全 测试

测试1、用户 hive/1.common2.hadoop.fql.comLEXIN.COM 和 nn/1.common2.hadoop.fql.com 分别对 Hive 进行查询 &#xff08;1&#xff09;HDFS 配置 vim /usr/local/fqlhadoop/hadoop/conf/core-site.xml <property><name>hadoop.proxyuser.hive.hosts</name&g…

16. @PostConstruct注解和开关原理(验证码开关、IP开关)

1►PostConstruct注解 PostConstruct是java自带的注解&#xff0c;会在java项目启动的时候先执行下面的方法 2►开关原理&#xff08;验证码开关&#xff09; 我们的项目具有验证码功能&#xff0c;旧版不支持关闭&#xff0c;新版已经支持关闭了。 我们打开页面“参数管…

go-zero微服务的使用

一、入门案例 1、使用goland创建一个工程 2、新建一个user.proto syntax "proto3";package user; // 这个地方表示生成的go的包名叫user option go_package "./user";message UserInfoRequest {int64 userId 1; }message UserInfoResponse {int64 user…

2024年csdn最新最全的Postman接口测试: postman实现参数化

什么时候会用到参数化 比如&#xff1a;一个模块要用多组不同数据进行测试 验证业务的正确性 Login模块&#xff1a;正确的用户名&#xff0c;密码 成功&#xff1b;错误的用户名&#xff0c;正确的密码 失败 postman实现参数化 在实际的接口测试中&#xff0c;部分参数…

【机器学习】特征工程:特征选择、数据降维、PCA

各位同学好&#xff0c;今天我和大家分享一下python机器学习中的特征选择和数据降维。内容有&#xff1a; &#xff08;1&#xff09;过滤选择&#xff1b;&#xff08;2&#xff09;数据降维PCA&#xff1b;&#xff08;3&#xff09;sklearn实现 那我们开始吧。 一个数据集中…

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

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

2024年csdn最新最全面的fiddler教程【1】

Fiddler简介 Fiddler是比较好用的web代理调试工具之一&#xff0c;它能记录并检查所有客户端与服务端的HTTP/HTTPS请求&#xff0c;能够设置断点&#xff0c;篡改及伪造Request/Response的数据&#xff0c;修改hosts&#xff0c;限制网速&#xff0c;http请求性能统计&#xff…

ERR:Navicat连接Sql Server报错

错误信息&#xff1a;报错&#xff1a;未发现数据源名称并且未指定默认驱动程序。 原因&#xff1a;Navicat没有安装Sqlserver驱动。 解决方案&#xff1a;在Navicat安装目录下找到sqlncli_x64.msi安装即可。 一键安装即可。 Navicat链接SQL Server配置 - MarchXD - 博客园 …

【腾讯云 HAI域探秘】——即时职场生存指南小游戏以及【自行搭建Stable Diffusion图片AI绘制 | ChatGLM2-6B AI进行智能对话 | Pytorch2.0 AI框架视频处理】

利用HAI的ChatGLM2 6B做一个即时对话小游戏 ChatGLM2-6B 是开源中英双语对话模型 ChatGLM-6B 的第二代版本&#xff0c;在保留了初代模型对话流畅、部署门槛较低等众多优秀特性的基础之上&#xff0c;ChatGLM2-6B 引入了更强大的性能、更长的上下文、更高效的推理&#xff0c;…

论文阅读——DiffusionDet

在目标检测上使用扩散模型 前向过程&#xff1a;真实框-->随机框 后向过程&#xff1a;随机框-->真实框 前向过程&#xff1a; 一般一张图片真实框的数目不同&#xff0c;填补到同一的N个框&#xff0c;填补方法可以是重复真实框&#xff0c;填补和图片大小一样的框&a…

Linux操作文件的底层系统调用

目录 1.概述 2.open的介绍 3.write 的介绍 4.read 5.close的介绍 6.文件描述符 1.概述 C语言操作文件的几个库函数:fopen,fread,fwrite,fclose; 系统调用:open,read,write,close; 系统调用方法实现在内核中;(陷入内核,切换到内核) 2.open的介绍 open重载:两个参数用于打…

SpringSecurity6 | 默认登录页

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; Java从入门到精通 ✨特色专栏&#xf…

Javaweb之Vue生命周期的详细解析

2.4 生命周期 vue的生命周期&#xff1a;指的是vue对象从创建到销毁的过程。vue的生命周期包含8个阶段&#xff1a;每触发一个生命周期事件&#xff0c;会自动执行一个生命周期方法&#xff0c;这些生命周期方法也被称为钩子方法。其完整的生命周期如下图所示&#xff1a; 状…

vue3项目安装eslint和prettier

【几乎最全/全网最长的 2 万 字】前端工程化完整流程&#xff1a;从头搭到尾&#xff08;vue3 vite qiankun docker tailwindcss iview......&#xff09;_前端工程化流程-CSDN博客 vue3tsvite项目中使用eslintprettierstylelinthusky指南 - 掘金 上面两篇文章相互结合操…