一. 什么是模块(module)?
在实际应用中,有时程序所要实现功能比较复杂,代码量也很大。若把所有的代码都存储在一个文件中,则不利于代码的复用和维护。一种更好的方式是将实现不同功能的代码分拆到多个文件中分别进行存储,这样使得结构更加清晰,更易于维护。
为了实现这些需求,Python将一系列相关的代码组织在一起存储在一个文件中,这就是 模块(module)。最常见的模块是以.py“为后缀的文件(即用Python编写的代码文件),文件名就是模块名。当然模块还有一些其他的形式,例如”.so"文件、“.dll” 文件等,但初学者几乎接触不到。在一个模块中可以定义变量、函数、类等,也能包含可执行代码。Python中的模块可以分成3类:(1)Python内置的模块;(2)第三方模块;(3)用户自定义的模块。
看一个简单的例子,我们可以自定义一个加法模块add,文件名为“add.py”。
#
# @file add.py
#
def add(a,b):
return a + b
然后在当前目录中,通过import语句导入该模块,格式为import 模块名,之后就可以使用模块中定义的add函数进行运算了。
>>> import add
>>> add.add(3, 5)
>>> 8
注:上图第二条语句中的第1个add表示的是add模块,第2个add表示的是该模块中定义的add函数。
二. 模块是如何组织的?
Python中包含各种各样功能的模块,数量繁多。若把所有模块都放到一个目录中则很难使用和维护,例如容易产生模块名冲突的问题。因此我们需要一种合理的组织方式来提高模块的结构性和可维护性。Python是通过包来组织模块的。
什么是包(package)呢?包本质上就是一个目录(文件夹),目录名就是包名。 在包中可以包含多个子目录(子包)或模块。
例如我们可以将自定义的加法模块add、减法模块sub、乘法模块mul和除法模块dev组织成一个包arithmetic,包的架构如下图所示。
再比如在音频数据处理中,会用到很多模块来实现对音频数据的不同处理。我们就可以设计一个包——sound来组织这些模块,其下面又包含了3个子包effects、filters和formats,分别对应不同类型的功能模块,包的架构如下所示。
注:可以发现包中通常都包含一个__init__.py文件。在Python3.x中,这并不是必需的,__init__.py文件的作用将在后面进行介绍。
三. 如何导入模块?
要使用模块必须先进行导入,导入模块有两类方式:
方式一:import …
- import […包].模块 [as 模块别名]
例如,import add,import arithmetic.add,import sound.filters.equalizer - import 包 [as 包别名]
例如,import arithmetic
方式二:from … import …
- from 包.[…包] import 模块 [as 模块别名] / *
例如,from arithmetic import add,from arithmetic import * - from […包].模块 import 变量/函数/类 [as 别名]/ *
例如,from arithmetic.add import add,from arithmetic.add import *
四. 每种导入方式有何区别?
上述两类模块导入的方式有什么区别呢?我们在实际编程时,该选择哪种方式导入模块呢?要回答这个问题,我们就需要了解模块导入背后的运行机制。在讲解这个机制之前,先简单介绍一下命名空间的概念。
1. 命名空间
命名空间(namespace)是映射到对象的名称 ,大多使用字典来实现。命名空间是在不同的时刻创建的,且拥有不同的生命周期。在Python语言中,有3类命名空间:内置命名空间、全局命名空间和局部命名空间。
-
内置命名空间(build-in namespace)
内置命名空间记录了以下信息:
内置命名空间中最常用的就是内置函数了,如下所示:
内置命名空间在Python解释器启动时创建,直至Python解释器退出都有效。 因此,Python解释器一旦启动,所有的内置函数不需要import,就可直接使用。 -
全局命名空间(global namespace)
用来记录模块级的变量、常量、函数、类以及其他导入的模块,可通过 globals() 函数查看。全局命名空间在读取模块定义时创建,通常也会持续到Python解释器退出。
可以看到,在全局命名空间中已经包含了一些名称,例如:
__name__
__doc__
__loader__
__spec__
__annotations__
__buitins__
这些名称都以双下划线开头和结尾,表示这是系统为每个模块自动生成的。这里重点介绍一下 __name__ 。它用来标识所在模块的名字,可能有两种取值:若该模块是当前正在运行的模块,则 __nam__ 的取值为 ‘__main__’; 若该模块不是正在运行的模块而是通过 import 语句导入的模块,则 __name__ 的取值为该模块的名称。在Python程序中,会经常看到如下语句:
if __name__ == 'main':
该语句的作用是:导入该模块时条件不成立,因而不会执行该语句后面的代码。
- 局部命名空间(local namespace)
用来记录函数或类中定义的变量、常量等,可通过 locals() 函数查看。
def func(a):
b = 2
c = a + b
print(locals())
>>> func(3)
{
'a': 3, 'b': 2, 'c': 5}
函数的局部命名空间在调用该函数时创建,在函数返回或抛出不在函数内部处理的异常时销毁。
当Python解释器解释运行程序时,如遇到某个名称(如变量x)时,就会在所有命名空间中进行查找以确定其所映射的对象。查找的顺序如下:
- 查找局部命名空间。若找到,则停止。
- 若存在父函数,则查找父函数的局部命名空间(闭包)。若找到,则停止。
- 查找全局命名空间。若找到,则停止。
- 查找内置命名空间。若找到,则停止;若还找不到,Python将报错NameError 。
2. import … 的运行机制
- import […包].模块 [as 模块别名]
这条语句的执行过程包括以下几个步骤:
(1)判断要导入的模块是否已被加载
导入模块时要分两种情况处理:第一情况是该模块之前已经被加载到内存中了;第二种情况时该模块尚未被加载到内存中。如何判断是哪种情况呢?这就需要用到数据结构 sys.modules。
sys模块包含系统级的信息,像正在运行的Python版本(sys.version)、系统级选项、最大允许递归的深度(sys.getrecursionlimit() 和 sys.setrecursionlimit() ) 。sys.modules 是一个全局字典,Python程序启动时就被加载到了内存中。sys.modules记录当前所有已加载模块的信息,字典的键字就是模块名,键值就是模块对象。
>>> import sys
>>> sys.modules
{
'sys': <module 'sys' (built-in)>, 'builtins': <module 'builtins' (built-in)>, '_frozen_importlib': <module '_frozen_importlib' (frozen)>, '_imp': <module '_imp' (built-in)>, '_thread': <module '_thread' (built-in)>, '_warnings': <module '_warnings' (built-in)>, '_weakref': <module '_weakref' (built-in)>, '_io': <module '_io' (built-in)>, 'marshal': <module 'marshal' (built-in)>, 'posix': <module 'posix'