简介
我们接着讨论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中相对导入与绝对导入相关概念。
如果本文对你有所帮助,请点击「好看」支持一下,这对本小号很重要。