不乱于心,不困于情。
不畏将来,不念过往。如此,安好。

Python 的 import 是怎么工作的?

Python 的 import 是非常直观的,但即使这样,有时候你会发现,明明包就在那里,我们仍会遇到 ModuleNotFoundError,明明相对路径非常正确,就是报错

ImportError: attempted relative import with no known parent package

导入同一个目录的模块和不同的目录的模块是完全不同的,本文通过分析使用 import 经常遇到的一些问题,来帮助你轻松搞定 import ,据此,你可以轻松创建属于自己的包。

主要内容:

0、什么是模块(module),什么是包(package)

1、import 时发生了什么

2、什么时候用相对导入,什么时候用绝对导入

3、一个自定义包的例子

0、什么是模块(module),什么是包(package)

模块(module)

模块与包的关系,可以类比文件和目录,模块就是文件。

Python 文档中这样描述,一个 Python 文件就是一个模块,Python 的文件名(不带后缀.py)就是模块名。

一个 module 可以包含变量、函数和类,它们是该 module 定义的命名空间的一部分,因此变量的命名问题不是问题,因为两个不同的模块可以有同名的变量、函数和类。

包(package)

模块与包的关系,可以类比文件和目录,包就是目录。

package 里面可以有 module,也可以有子包(sub-package)。一个模块定义一个命名空间,以便变量、函数和类可以在两个不同的模块中具有相同的名称,同样的,一个包对其组成的包和模块做同样的事情,可以通过点号访问主包中的模块和包。

一个基本的 package 可以包含 sub-package、modules、__init__.py(Python 3.3 之后非必需)、setup.py。一个可能的 package 结构如下所示:

而 setup.py 存在于你的 package 所在的主目录中,包含配置信息,如所需的依赖项、脚本和子包。你还可以指定有关 package 的元数据,例如 package 的名称、作者、描述等。

setup.py 是 pip 用来安装你的包的文件。

1、import 时发生了什么

先举一个简单的例子,比如说同一个目录有两个文件,file1.py 和 file2.py,内容很简单,就打印各自的文件名,不同的是 file2.py 里面 import 了 file1:

#file1.py
print("This is file1.py")
#file2.py
print("This is file2.py")
import file1

运行 file2.py 可以得到下面的结果:

❯ python file2.py
This is file2.py
This is file1.py

可以看出:

  • import 很直观,用谁就 import 谁。
  • import 语句就是一个普通的语句,可以放在任何位置。
  • 一个文件被 import 的时候,就会被执行,其内部的类或对象将添加到其命名空间。

我们还要知道 import 的搜索顺序,只需要记住一点,那就是 import 会去 sys.path 里面搜索。

比如我在 file2.py 的末尾添加一行代码:import sys; print(sys.path) 就可以打印 import 的搜索路径:图片

可以看出 sys.path 的顺序

  1. 会先搜索执行脚本所在的路径
  2. 标准库
  3. 第三方库 site-packages

关于 sys.path 需要你注意的是

1、在解释器环境下,sys.path[0] 就是解释器启动时所在的路径 ”

2、sys.path 并不会依赖当前程序的工作路径 – os.getcwd(),仅仅依赖第一个脚本所在的路径:

图片

3、如果一个模块导入另一个模块,而后者又导入另一个模块,则第一个模块的 sys.path 是解释器搜索第二个导入语句的位置。

一旦模块或包被找到,就会执行该模块或包。如果包里面有初始化文件 __init__.py,导入的时候,会先执行 __init__.py

然后要导入的项目就添加到了其命名空间内,我们可以通过 xx.yy 的方式来使用。

2、什么时候用相对导入,什么时候用绝对导入

先看看什么是绝对导入,所谓绝对导入就是这样的形式:

import aa
import aa.bb
from aa import bb

这样的方式很直观, import 会去 sys.path 查找就行了,如果遇到了 ModuleNotFoundError,思考一下为什么  sys.path 没有我们要导入的包,或者手动把这个包的路径插入到 sys.path 中去。

再看看什么是相对导入,所谓相对导入就是这样的形式:

from . import aa
from .aa import bb
from .. import yy

也就是说相对路径中有个 . 号,用来表明要导入的模块或当前的包的相对位置。

举个例子,我们 pythonimportexample 目录下新建一个目录 subpackage1,在 subpackage1 内新建两个文件 file3.py、file4.py,

内容如下:

file3.py :

print("This is file3.py")

file4.py

from . import file3
print("This is file4.py")

只要我们直接运行 file4.py,那是一定会报错的:图片

Python 提示我们:

ImportError: attempted relative import with no known parent package

也就是说相对导入不知道父包是谁,换句话说,这是一个子包,必须让父包来调用它,直接运行这个文件是不行的,即使你在 file4.py 的目录 subpackage1 同级的目录执行该文件也是不行的,见上图。

但是在 file4.py 的目录 subpackage1 同级的目录作为一个 module 来执行是可以的,如下图:

图片

换句话说,我们把 subpackage1 作为一个包来让别人用,相对导入是可以的,比如说我们在目录 subpackage1 同级的目录新建一个 file5.py 的文件,内容如下:

file5.py:

from subpackage1 import file4

然后,执行  python file5.py 可以看出,相对导入已经正常工作:

图片

结论

  • 如果是当做脚本文件直接运行的,使用绝对导入
  • 如果是当做模块供其他文件导入,使用相对导入

3、一个自定义包的例子

先上一个图来看下目录及引用结构,方块的是目录,椭圆的是文件,曲线是引用:

图片

其中 import_example 目录下有 setup.py 和 run.py

run.py 导入了 file4、file5、file6。

file4 导入了 file3,file5 导入了 file3

file6 导入了 file2,file2 导入了 file1

现在我们来执行一下 run.py 看下效果:

图片

可以看出所有相对导入都已正常工作,虽然 file3 被导入了两次,但只执行了一次,说明 Python 内部已经考虑了同一个包的多重导入问题。

自定义包就是让其他文件导入使用的,因此 pythonimportexample目录下都使用相对导入,源代码见:

https://gitee.com/somenzz/code-example/tree/master/import_example

点阅读原文也可以直接访问。

这里还有一些自定义包的例子:

dbinterface[1]

transferfile[2]

最后的话

本文分享了什么是模块(module),什么是包(package),import 的搜索路径,也分享了相对导入和绝对导入的区别,最后举了一个非常实用的 import 例子,方便你构建自己的包。

参考资料

[1]dbinterface: https://github.com/somenzz/dbinterface
[2]transferfile: https://github.com/somenzz/transferfile
赞(0) 打赏
未经允许不得转载:seo优化_前端开发_渗透技术 » Python 的 import 是怎么工作的?

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏