详解Python import机制(二):绝对导入与相对导入

4,006 阅读5分钟

简介

我们接着讨论import的相关机制,本文会着重讨论绝对导入与相对导入的概念。

import运行流程

当我们使用 import os 时,import是怎么工作的?

Python首先会在sys.modules中搜索名为os的模块,如果sys.modules缓存中存在,则将缓存映射的内容直接返回,导入流程结束。

如果缓存中没有os模块,Python会继续搜索其内置模块列表,这些模块是Python预先安装的,简单理解就是Python的标准库,os就是标准库,所以import流程至此结束。

如果在内置模块中依旧没有找到,则会在sys.path列表定义的路径中搜索该模块。

当Python搜到到模块时,会在本地作用域内初始化相应的module对象,这样,我们在当前文件中使用该模块就不会报错了。

Python的import机制非常灵活,这也带来了一些严重的安全风险,如利用导入机制重写Python的核心功能或利用导入机制执行恶意代码。

恶意开发者会将自己的恶意第三方包提交到PyPi供人下载,这些包名通常与流行的包名相似,功能也相似,只是多些恶意代码,比如我们常用从requests来实现HTTP的GET与POST请求,那么恶意开发者可以将requests的源码download下来,添加恶意代码,然后修改为request提交到PyPi,恶意代码可以利用导入机制重写Python,从而获得你设备一定程度的控制权。

为了避免这种情况,你只能在安装第三方库时,确认好第三方库的名称。

安全与灵活就像鱼和熊掌,是不可兼得的。

import语句的标准写法

在编写import语句时,无论你使用使用相对导入还是绝对导入,都应该遵循PEP 8中提及的建议,这会让代码看起来更加优雅

1.导入语句应卸载文件的顶部,但在模块(.py文件)注释或说明文字之后

2.导入语句要根据导入内容的不同进行分类,一般将其分为3类。 第一类:导入Python内置模块。 第二类:导入相关的第三方库模块 第三类:导入程序本地的模块(即当前应用的模块)

3.导入不同类别模块时,需要使用空行分开

标准如下:

"""这是PEP 8建议的导入模块标准"""

# 内置模块
import os
import time

# 第三方模块
import flask

# 本地模块
from test import test1

绝对导入Absolute Import

无论是绝对导入还是相对导入,都需要一个参照物,不然「绝对」与「相对」的概念就无从谈起。

绝对导入的参照物是项目的根文件夹。

绝对导入要求我们使用从项目的根文件夹到要导入模块的完整路径。

假设我们有如下目录结构:

└── project
    ├── package1
    │   ├── module1.py
    │   └── module2.py
    └── package2
        ├── __init__.py
        ├── module3.py
        ├── module4.py
        └── subpackage1
            └── module5.py

根文件夹为project,它下面有package1与package2这两个文件夹,其中package1 下有module1.py与module2.py这两个模块,而package2下有__init__.py以及module3.py与module4.py这3个模块,此外还有subpackage1目录,其下包含module5.py模块。

package1/module2.py中包含一个名为function1的函数。 package2/__init__.py中包含一个名为calss1的类。 package2/subpackage1/module5.py中包含一个名为function2的函数。

那么使用绝对路径导入的实例如下:

from package1 import mudule1
from package1.module2 import function1
from package2 import class1
from package2.subpackage1.module5 import function2

「绝对路径要求我们必须从最顶层的文件夹开始,为每个包或每个模块提供出完整详细的导入路径。」

在Python3.x中,绝对导入是默认的导入形式,也是PEP 8推荐的导入形式,它相当直观,可以很明确的知道要导入的包或模块在哪里。

此外「使用绝对导入的模块(.py文件)其路径位置发生了改变,绝对导入依旧生效」,但如果绝对导入的对象路径发生了变化,此时就需要改写绝对导入语句了。

但有时,绝对导入显得冗长复杂,这个时候就可以尝试使用相对导入

相对导入

相对导入的参照物为当前位置,当我们使用相对导入时,需要给出相对与当前位置,想导入资源所在的位置。

细究而言,其实有两种相对导入,分别是「隐式相对导入」与「显示相对导入」,简单提一嘴。

依旧是如下目录结构

└── project
    ├── package1
    │   ├── module1.py
    │   └── module2.py
    └── package2
        ├── __init__.py
        ├── module3.py
        ├── module4.py
        └── subpackage1
            └── module5.py

package1/module2.py中包含一个名为function1的函数。 package2/__init__.py中包含一个名为calss1的类。 package2/subpackage1/module5.py中包含一个名为function2的函数。

那么在 package2/module3.py 中引用 module4.py,其写法如下

# package2/module3.py
import module4 # 隐式相对导入
from . import module4 # 显式相对导入
from package2 import module4 # 绝对导入

所谓「隐式相对导入」就是不显式的告知Python相对于当前位置进行导入,这种方式在Python3.x中已经不推荐使用,主要原因就是「隐式相对导入」不够明了。

如果你还在使用python2.x,可以通过from __future__ import absolute_import禁用隐式相对导入。

如果你想在 package1/module1.py 中使用 function1,使用相对导入可以这样写

# package1/module1.py
from .module2 import function1 # 因为module2与module1在同一个目录中,所以使用.module2

如果你想在 package2/module3.py 中导入class1和function2,使用相对导入可以这样写

# package2/module3.py
from . import class1 # 单点,表示从当前包中导入 class1,具体而言就是从__init__.py中导入class1
from .subpackage1.module5 import function2 # 因为subpackage1与module3.py在同一目录中,所用使用.subpackage1

相对导入相较于绝对导入而言,通常更加简洁,但对于一些目录结构可能会发生变化的项目而言,相对导入没有那么直观,可能会产生混乱。

本篇文章比较快速的介绍了import中相对导入与绝对导入相关概念。

如果本文对你有所帮助,请点击「好看」支持一下,这对本小号很重要。