模块是提供自包含的变量的包(命名空间)从而将部件组织为系统的一种可行方式。
一个模块文件顶层定义的所有变量在被导入的时候都变成了模块对象的属性。
模块能够提供的核心功能:
import的作用是在一个文件中导入模块。这看起来有点像C/C++语言中的#include操作。但是python的导入和C/C++的#include是完全不一样的。在python中,导入并非只是把一个文件文本插入另一个文件这么简单,导入实际是运行时的操作。(在C/C++中#include是将别的源代码内容插入到所指定的位置,就是这么简单,这就是C/C++编译器在预处理阶段对#include进行的操作)
程序在第一次导入指定文件时,会执行三个步骤:
这三个步骤(这三个步骤简记为:搜索,编译,运行)只在程序执行期间模块第一次被导入时才会进行,在这之后导入相同模块时,会跳过这三个步骤,而只提取内存中已加载的模块对象。实际上,Python把导入的模块存储到一个名为sys.modules的表中,并在每次导入操作开始的时候先检查该表中是否存在本次导入的模块,如果不存在,则执行上面的三个步骤。
我们都知道导入一个模块的时候,使用的语句如下所示:
import 模块名例如:import re,没有带上.py后缀,也没有指出路径。路径和后缀是故意省略掉的,因为python使用了标准模块搜索路径来找出import语句所对应的模块文件。
大多数情况下,我们通常不需要配置模块路径。但如果你想在整个目录的边界都能导入用户自定义的模块。那么你就需要知道路径搜索的原理并进行定制。Python在程序启动时配置sys.path,这其实就是模块搜索的路径字符串列表。sys.path包含以下五个方面的内容。
sys.path 可以通过打印sys.path来查看这些路径。这个目录名称字符串列表就是Python内部的实际搜索路径:在导入时,Python会自左至右搜索这个列表中的每个目录,并使用第一个能够匹配的文件。
当遍历模块搜索路径找到符合import语句的源代码文件之后,如果需要的话(python会检查文件最近一次的修改时间和生成的字节码对应的python版本,从而决定是否需要编译),python接下来会将其编译成字节码。
python通过检查文件最后一次修改时间,如果发现字节码文件比源文件旧,或者是由不同版本的python编译的,就会在程序运行时自动重新生成字节代码,否则不会进行编译。
注意:
import操作的最后步骤是执行模块的字节码。被导入文件中所有语句会从头到尾依次执行(导入实际上会执行代码),其中遇到的顶层赋值语句都会产生所导入模块的属性。
下面是一个例子,我们创建一个名为test.py的文件,写入如下代码。
print("我被导入了")
num = 3
def func():
print("Hello Python!")然后在交互式命令行模式下,导入test模块。程序执行结果如下所示:
>>> import test
我被导入了
>>> test.num
3
>>> test.func()
Hello Python!可以看到import之后就会打印“我被导入了”,这说明导入实际上会执行代码。接下来使用 object.attr 的方式来使用test模块的变量和函数。
无论是那个版本的Python,总会在你修改源文件之后重新生成新的字节码文件。但是版本差异的处理方式不一样,在3.2之前的版本中使用魔数,在3.2之后保存多个版本的文件名。(这个实际上和Python解释器有关,在Cpython中确实如此。但是在pypy中,无论是3.2之前还是之后,都会将字节码存放在__pycache目录下。)
字节码的生成是完全自动的,这是程序运行过程中的副产品。
文件名后缀在import语句中被刻意省略,Python会选择在搜索路径中第一个能够匹配导入名称的文件。事实上,导入语句的本质是外部组件(源文件,字节码,编译扩展包等)暴露的接口。

对于使用者而言,不在乎被导入的文件是什么类型,因为它们使用起来和Python编写的模块文件并无差异。
如果在不同的路径下存在a.py和a.so,那么python会选择第一次搜索到的a.xx文件,但是如果在同一路径下存在a.py和a.so,python也会有一个规则来选择文件,但是这个规则可能会变动。因此,我们的模块名必须要独特,来保持其唯一性。
实际上,你可以重新定义import操作的行为,使用导入钩子(import hook)。导入钩子能够让你从压缩文件中加载文件,执行解密等操作。意味着你可以导入任何类型的文件。现在的Python标准库路径中就有一个.zip文件。可以打印sys.path来查看。如下:
>>> import sys
>>> sys.path
[..., '/usr/lib/python38.zip', ...]优化字节码文件一般的后缀是.pyo,这种文件比字节码文件可能快上5%左右。因此一般情况下,如果追求速度,那么直接选择使用PyPy,而不是使用优化字节码。比较优化字节码快的速度依旧是非常有限的。
导入(import)会在搜索,编译,运行导入的文件。如果需要配置搜索路径,那么可以通过配置PYTHONPATH来实现。
import操作和模块是Python中程序架构的核心。较大的程序可以拆分为几个文件,在运行时利用导入链接在一起。导入和模块的意义就是为程序提供结构化的设计,让程序将其逻辑分割成一些独立完备的软件组件。一个模块中的程序代码和另一个的程序代码彼此隔离。模块最小化了程序内不同部分之间的名称冲突。