点赞、收藏、加关注,下次找我不迷路
在正式探究底层实现前,先简单回顾一下大家日常使用的import语句的常见形式。
最基本的方式就是直接导入整个模块,语法如下:
import module_name列如我们要使用 Python 的数学计算模块math来计算平方根,代码可以这样写:
import math
result = math.sqrt(25)
print(result) 在这个例子中,我们通过import math导入了math模块,之后使用math.sqrt()来调用模块中的sqrt函数进行平方根计算。这种方式的好处是,通过模块名作为前缀,可以清晰地知道调用的函数或变量来自哪个模块,有效避免了命名冲突。
有时我们并不需要整个模块的所有功能,只想引入其中特定的函数、类或变量,这时可以使用以下语法:
from module_name import item_name例如,我们只想从math模块中导入sqrt函数,代码可以简化为:
from math import sqrt
result = sqrt(25)
print(result)这样在使用sqrt函数时,就不需要再加上模块名前缀,代码看起来更加简洁。但要注意,如果从不同模块导入了同名的函数或变量,就可能会产生命名冲突。
还有一种方式可以导入模块中的所有公共内容,语法为:
from module_name import *不过这种方式在大型项目中并不推荐使用,由于它会将模块中的所有内容都引入到当前命名空间,大大增加了命名冲突的风险,使得代码的可读性和可维护性变差。列如有两个模块module1和module2,它们都有一个名为func的函数,如果同时使用from module1 import *和from module2 import *,那么在调用func时就无法明确它到底来自哪个模块。
当模块名比较长或者为了提高代码的可读性,我们可以给导入的模块取一个别名,语法如下:
import module_name as alias_name以常用的数据分析库numpy为例,一般我们会给它取别名np:
import numpy as np
array = np.array([1, 2, 3, 4])
print(array)这样在后续使用numpy模块的功能时,通过简短的别名np来调用,代码更加简洁易读。
当 Python 解释器遇到import语句时,它会按照一系列步骤来完成模块的导入工作,这个过程可以大致分为以下几个阶段。

Python 解释器第一会在多个地方搜索要导入的模块,主要搜索路径如下:

我们可以通过以下代码来查看 Python 的模块搜索路径:
import sys
print(sys.path)sys.path是一个列表,包含了上述提到的各个搜索路径。
一旦找到了要导入的模块,Python 解释器就会进入模块加载阶段。在这个阶段,解释器会根据模块的类型(如 Python 源文件、编译后的字节码文件、动态链接库等)来选择合适的方式加载模块。

在模块加载完成后,解释器会对模块进行初始化。在这个阶段,模块中的顶层代码(即没有嵌套在任何函数或类定义中的代码)会被执行。这些顶层代码可能包括函数或类的定义、全局变量的赋值、导入其他模块等操作。

例如,在下面的模块example_module.py中:
# example_module.py
print("This is the top - level code in example_module")
global_variable = 10
def example_function():
print("This is an example function")当我们导入example_module时,print("This is the top - level code in example_module")这行代码会被立即执行,输出相应的信息。同时,global_variable会被赋值为 10,example_function函数也会被定义并可以在后续被调用。
需要注意的是,如果模块中存在一些副作用代码(如打印输出、执行函数等),这些副作用会在导入模块时被执行。为了避免这种情况,我们一般会将那些只在直接运行模块时才需要执行的代码放在if __name__ == "__main__":代码块中。例如:
# example_module.py
def example_function():
print("This is an example function")
if __name__ == "__main__":
example_function()这样,当我们直接运行example_module.py时,example_function会被调用;而当这个模块被其他模块导入时,if __name__ == "__main__":代码块中的内容不会被执行,从而避免了不必要的副作用。
Python 为了提高模块导入的效率,引入了模块缓存机制。一旦一个模块被成功导入,它就会被缓存起来。在后续的导入过程中,如果再次尝试导入同一个模块,Python 解释器会直接从缓存中获取该模块,而不会重复执行上述的搜索、加载和初始化过程。这样可以大大减少模块导入的时间开销,特别是在一个程序中多次导入同一个模块的情况下。

我们可以通过sys.modules来查看当前已经被缓存的模块。sys.modules是一个字典,其中键是模块名,值是对应的模块对象。例如:
import sys
import math
print(sys.modules['math']) 这会输出math模块对应的对象,表明math模块已经被缓存。
在一些特殊的编程场景中,我们可能需要在程序运行时动态地决定导入哪个模块,这时候就可以使用 Python 的动态导入机制。__import__函数是 Python 实现动态导入的核心函数,它实际上是import语句在底层调用的函数。

__import__函数的基本语法如下:
__import__(name, globals=None, locals=None, fromlist=(), level=0)参数说明:
下面是一个使用__import__函数动态导入模块的简单示例:
module_name = "math"
math_module = __import__(module_name)
result = math_module.sqrt(25)
print(result) 在这个例子中,我们通过一个字符串变量module_name来指定要导入的模块名,然后使用__import__函数动态导入math模块,并调用其中的sqrt函数。
不过,__import__函数的语法相对复杂,并且在处理相对导入等情况时不够直观,容易出错。因此,在现代 Python 开发中,更推荐使用importlib模块中的import_module函数来进行动态导入。
importlib模块是 Python 标准库中专门用于实现模块导入机制的模块,它提供了更加灵活和强劲的方式来处理模块导入,包括动态导入、自定义导入等功能。
importlib.import_module函数是importlib模块中用于动态导入模块的常用函数,它的语法比__import__函数更加简洁明了。基本语法如下:
importlib.import_module(name, package=None)参数说明:
例如,我们使用importlib.import_module来实现前面__import__函数的示例:
import importlib
module_name = "math"
math_module = importlib.import_module(module_name)
result = math_module.sqrt(25)
print(result) 可以看到,代码更加简洁,并且在处理相对导入等复杂情况时,importlib.import_module函数提供了更好的支持和可读性。
importlib模块还允许我们创建自定义的导入器(importer),通过自定义导入器,我们可以实现一些特殊的模块导入逻辑。例如,我们可以创建一个导入器,使其能够从网络上下载模块并导入,或者从加密的文件中读取模块内容并导入等。
虽然创建自定义导入器的场景相对较少,但在一些特定的应用场景(如开发特定的框架、插件系统等)中,它可以提供超级强劲的功能扩展能力。不过,由于创建自定义导入器涉及到比较复杂的代码编写和对 Python 导入机制的深入理解,这里就不详细展开介绍了。
在 Python 的模块导入中,还有两个重大的概念:绝对导入和相对导入。
绝对导入是从项目的根目录或顶层包开始的导入方式。在使用绝对导入时,我们需要指定完整的包路径来导入模块,无论当前的工作目录是什么。绝对导入方式能够确保导入路径的明确性,在大型项目中超级有用。例如,有一个项目结构如下:
my_project/
├── my_package/
│ ├── module1.py
│ └── module2.py
└── main.py在main.py中,如果要导入my_package包中的module1.py,可以使用绝对导入:
from my_package import module1这里从项目的根目录my_project开始,明确指定了要导入的my_package包下的module1模块。
相对导入是从当前模块所在位置出发的导入方式。在相对导入中,我们使用点(.)来表明当前模块的位置,然后指定相对于当前模块的导入路径。相对导入有两种方式:隐式相对导入和显式相对导入。
from. import module1这里的.表明当前module2.py所在的目录my_package。
2. 显式相对导入:使用多个点(..)表明向上一级包中导入模块。例如,在module2.py中,如果要导入上一级目录中的某个模块,可以使用..。假设my_project目录下还有一个common包,里面有一个util.py模块,module2.py要导入util.py,可以这样写:
from..common import util这里的..表明向上一级目录my_project,然后再指定common包下的util模块。
需要注意的是,相对导入只能在包内使用,即包含__init__.py文件的目录结构中。并且在 Python 3 中,相对导入的语法更加严格,推荐使用显式相对导入,以避免一些潜在的问题。