所谓的模块导入,是指在一个模块中使用另一个模块的代码的操作,它有利于代码的复用。
也许你看到这个标题,会说我怎么会发这么基础的文章?
与此相反。恰恰我觉得这篇文章的内容可以算是 Python 的进阶技能,会深入地探讨并以真实案例讲解 Python import Hook 的知识点。
当然为了使文章更系统、全面,前面会有小篇幅讲解基础知识点,但请你有耐心的往后读下去,因为后面才是本篇文章的精华所在,希望你不要错过。
1. 导入系统的基础
1.1 导入单元构成
导入单元有多种,可以是模块、包及变量等。
对于这些基础的概念,对于新手还是有必要介绍一下它们的区别。
模块:类似 *.py,*.pyc, *.pyd ,*.so,*.dll 这样的文件,是 Python 代码载体的最小单元。
包 还可以细分为两种:
Regular packages:是一个带有 __init__.py 文件的文件夹,此文件夹下可包含其他子包,或者模块
Namespace packages
关于 Namespace packages,有的人会比较陌生,我这里摘抄官方文档的一段说明来解释一下。
Namespace packages 是由多个 部分 构成的,每个部分为父包增加一个子包。各个部分可能处于文件系统的不同位置。部分也可能处于 zip 文件中、网络上,或者 Python 在导入期间可以搜索的其他地方。命名空间包并不一定会直接对应到文件系统中的对象;它们有可能是无实体表示的虚拟模块。
命名空间包的 __path__
属性不使用普通的列表。而是使用定制的可迭代类型,如果其父包的路径 (或者最高层级包的 sys.path) 发生改变,这种对象会在该包内的下一次导入尝试时自动执行新的对包部分的搜索。
命名空间包没有 parent/__init__.py
文件。实际上,在导入搜索期间可能找到多个 parent 目录,每个都由不同的部分所提供。因此 parent/one 的物理位置不一定与 parent/two 相邻。在这种情况下,Python 将为顶级的 parent 包创建一个命名空间包,无论是它本身还是它的某个子包被导入。
1.2 相对/绝对对导入
当我们 import 导入模块或包时,Python 提供两种导入方式:
相对导入(relative import ):import foo.bar 或者 form foo import bar
绝对导入(absolute import):from . import B 或 from ..A import B,其中.表示当前模块,..表示上层模块
你可以根据实际需要进行选择,但有必要说明的是,在早期的版本( Python2.6 之前),Python 默认使用的相对导入。而后来的版本中( Python2.6 之后),都以绝对导入为默认使用的导入方式。
使用绝对路径和相对路径各有利弊:
当你在开发维护自己的项目时,应当使用相对路径导入,这样可以避免硬编码带来的麻烦。
而使用绝对路径,会让你模块导入结构更加清晰,而且也避免了重名的包冲突而导入错误。
1.3 导入的标准写法
在 PEP8 中有一条,对模块的导入顺序提出了要求,不同来源模块导入,应该有清晰的界限,使用一空行来分开。
import 语句应当分行书写
# bad
import os,sys
# good
import os
import sys
import语句应当使用absolute import
# bad
from ..bar import Bar
# good
from foo.bar import test
import语句应当放在文件头部,置于模块说明及docstring之后,全局变量之前
import语句应该按照顺序排列,每组之间用一个空格分隔,按照内置模块,第三方模块,自己所写的模块调用顺序,同时每组内部按照字母表顺序排列
# 内置模块
import os
import sys
# 第三方模块
import flask
# 本地模块
from foo import bar
2. __import__ 的妙用
在 Python 中使用 import 关键字来实现模块/包的导入,可以说是基础中的基础。
但这不是唯一的方法,还有 importlib.import_module()
和 __import__()
等。
对于 __import__
,普通的开发者,可能就会比较陌生。
和 import 不同的是,__import__
是一个函数,也正是因为这个原因,使得 __import__
的使用会更加灵活,常常用于框架中,对于插件的动态加载。
实际上,当我们调用 import 导入模块时,其内部也是调用了 __import__
,请看如下两种导入方法,他们是等价的。
# 使用 import
import os
# 使用 __import__
os = __import__('os')
通过举一反三,下面两种方法同样也是等价的。
# 使用 import .. as ..
import pandas as pd
# 使用 __import__
pd = __import__('pandas')
上面我说 __import__
常常用于插件的动态,事实上也只有它能做到(相对于 import 来说)。
插件
通常会位于某一特定的文件夹下,在使用过程中,可能你并不会用到全部的插件,也可能你会新增插件。
如