CH11_模块和包
CH11_模块和包
本章目标
- 掌握模块的定义和使用
- 掌握包的创建和使用
模块
概述
前面章节中,我们已经使用了很多模块(如 string、sys、os 等),通过向程序中导入这些模块,我们可以使用很多“现成”的函数实现想要的功能。
Python 提供了强大的模块支持,主要体现在,不仅 Python 标准库中包含了大量的模块(称为标准模块),还有大量的第三方模块,开发者自己也可以开发自定义模块。通过这些强大的模块可以极大地提高开发者的开发效率。
那么,模块到底指的是什么呢?模块,英文为 Modules,至于模块到底是什么,可以用一句话总结:模块就是 Python 程序。换句话说,任何 Python 程序都可以作为模块,包括在前面章节中写的所有 Python 程序,都可以作为模块。
模块可以比作一盒积木,通过它可以拼出多种主题的玩具,这与前面介绍的函数不同,一个函数仅相当于一块积木,而一个模块(.py 文件)中可以包含多个函数,也就是很多积木。模块和函数的关系如图 1 所示。
经过前面的学习,读者已经能够将 Python 代码写到一个文件中,但随着程序功能的复杂,程序体积会不断变大,为了便于维护,通常会将其分为多个文件(模块),这样不仅可以提高代码的可维护性,还可以提高代码的可重用性。
代码的可重用性体现在,当编写好一个模块后,只要编程过程中需要用到该模块中的某个功能(由变量、函数、类实现),无需做重复性的编写工作,直接在程序中导入该模块即可使用该功能。
前面讲了封装,并且还介绍了很多具有封装特性的结构,比如说:
- 诸多容器,例如列表、元组、字符串、字典等,它们都是对数据的封装;
- 函数是对 Python 代码的封装;
- 类是对方法和属性的封装,也可以说是对函数和数据的封装。
本节所介绍的模块,可以理解为是对代码更高级的封装,即把能够实现某一特定功能的代码编写在同一个 .py 文件中,并将其作为一个独立的模块,这样既可以方便其它程序或脚本导入并使用,同时还能有效避免函数名和变量名发生冲突。
举个简单的例子,在某一目录下(桌面也可以)创建一个名为 hello.py 文件,其包含的代码如下:
1 | def say (): |
在同一目录下,再创建一个 say.py 文件,其包含的代码如下:
1 | #通过 import 关键字,将 hello.py 模块引入此文件 |
运行 say.py 文件,其输出结果为:
1 | Hello,World! |
say.py 文件中使用了原本在 hello.py 文件中才有的 say() 函数,相对于 day.py 来说,hello.py 就是一个自定义的模块(有关自定义模块,后续章节会做详细讲解),我们只需要将 hellp.py 模块导入到 say.py 文件中,就可以直接在 say.py 文件中使用模块中的资源。
与此同时,当调用模块中的 say() 函数时,使用的语法格式为“模块名.函数”,这是因为,相对于 say.py 文件,hello.py 文件中的代码自成一个命名空间,因此在调用其他模块中的函数时,需要明确指明函数的出处,否则 Python 解释器将会报错。
导入模块
使用 Python 进行编程时,有些功能没必须自己实现,可以借助 Python 现有的标准库或者其他人提供的第三方库。比如说,在前面章节中,我们使用了一些数学函数,例如余弦函数 cos()、绝对值函数 fabs() 等,它们位于 Python 标准库中的 math(或 cmath)模块中,只需要将此模块导入到当前程序,就可以直接拿来用。
在前面已经看到使用 import 导入模块的语法,但实际上 import 还有更多详细的用法,主要有以下两种:
import 模块名1 [as 别名1], 模块名2 [as 别名2],…
:使用这种语法格式的 import 语句,会导入指定模块中的所有成员(包括变量、函数、类等)。不仅如此,当需要使用模块中的成员时,需用该模块名(或别名)作为前缀,否则 Python 解释器会报错。from 模块名 import 成员名1 [as 别名1],成员名2 [as 别名2],…
: 使用这种语法格式的 import 语句,只会导入模块中指定的成员,而不是全部成员。同时,当程序中使用该成员时,无需附加任何前缀,直接使用成员名(或别名)即可。
其中,第二种 import 语句也可以导入指定模块中的所有成员,即使用 form 模块名 import *,但此方式不推荐使用,具体原因本节后续会做详细说明。
用 [] 括起来的部分,可以使用,也可以省略。
import 模块名 as 别名
下面程序使用导入整个模块的最简单语法来导入指定模块:
1 | # 导入sys整个模块 |
上面第 2 行代码使用最简单的方式导入了 sys 模块,因此在程序中使用 sys 模块内的成员时,必须添加模块名作为前缀。
运行上面程序,可以看到如下输出结果(sys 模块下的 argv 变量用于获取运行 Python 程序的命令行参数,其中 argv[0] 用于获取当前 Python 程序的存储路径):
1 | C:\Users\mengma\Desktop\hello.py |
导入整个模块时,也可以为模块指定别名。例如如下程序:
1 | # 导入sys整个模块,并指定别名为s |
第 2 行代码在导入 sys 模块时才指定了别名 s,因此在程序中使用 sys 模块内的成员时,必须添加模块别名 s 作为前缀。运行该程序,可以看到如下输出结果:
1 | C:\Users\mengma\Desktop\hello.py |
也可以一次导入多个模块,多个模块之间用逗号隔开。例如如下程序:
1 | # 导入sys、os两个模块 |
上面第 2 行代码一次导入了 sys 和 os 两个模块,因此程序要使用 sys、os 两个模块内的成员,只要分别使用 sys、os 模块名作为前缀即可。在 Windows 平台上运行该程序,可以看到如下输出结果(os 模块的 sep 变量代表平台上的路径分隔符):
1 | C:\Users\mengma\Desktop\hello.py |
在导入多个模块的同时,也可以为模块指定别名,例如如下程序:
1 | # 导入sys、os两个模块,并为sys指定别名s,为os指定别名o |
上面第 2 行代码一次导入了sys 和 os 两个模块,并分别为它们指定别名为 s、o,因此程序可以通过 s、o 两个前缀来使用 sys、os 两个模块内的成员。在 Windows 平台上运行该程序,可以看到如下输出结果:
1 | C:\Users\mengma\Desktop\hello.py |
from 模块名 import 成员名 as 别名
下面程序使用了 from…import 最简单的语法来导入指定成员:
1 | # 导入sys模块的argv成员 |
第 2 行代码导入了 sys 模块中的 argv 成员,这样即可在程序中直接使用 argv 成员,无须使用任何前缀。运行该程序,可以看到如下输出结果:
1 | C:\Users\mengma\Desktop\hello.py |
导入模块成员时,也可以为成员指定别名,例如如下程序:
1 | # 导入sys模块的argv成员,并为其指定别名v |
第 2 行代码导入了 sys 模块中的 argv 成员,并为该成员指定别名 v,这样即可在程序中通过别名 v 使用 argv 成员,无须使用任何前缀。运行该程序,可以看到如下输出结果:
1 | C:\Users\mengma\Desktop\hello.py |
form…import 导入模块成员时,支持一次导入多个成员,例如如下程序:
1 | # 导入sys模块的argv,winver成员 |
上面第 2 行代码导入了 sys 模块中的 argv、 winver 成员,这样即可在程序中直接使用 argv、winver 两个成员,无须使用任何前缀。运行该程序,可以看到如下输出结果(sys 的 winver 成员记录了该 Python 的版本号):
1 | C:\Users\mengma\Desktop\hello.py |
一次导入多个模块成员时,也可指定别名,同样使用 as 关键字为成员指定别名,例如如下程序:
1 | # 导入sys模块的argv,winver成员,并为其指定别名v、wv |
上面第 2 行代码导入了 sys 模块中的 argv、winver 成员,并分别为它们指定了别名 v、wv,这样即可在程序中通过 v 和 wv 两个别名使用 argv、winver 成员,无须使用任何前缀。运行该程序,可以看到如下输出结果:
1 | C:\Users\mengma\Desktop\hello.py |
不推荐使用 from import 导入模块所有成员
在使用 from…import 语法时,可以一次导入指定模块内的所有成员(此方式不推荐),例如如下程序:
1 | #导入sys 棋块内的所有成员 |
上面代码一次导入了 sys 模块中的所有成员,这样程序即可通过成员名来使用该模块内的所有成员。该程序的输出结果和前面程序的输出结果完全相同。
需要说明的是,一般不推荐使用“from 模块 import”这种语法导入指定模块内的所有成员,因为它存在潜在的风险。比如同时导入 module1 和 module2 内的所有成员,假如这两个模块内都有一个 foo() 函数,那么当在程序中执行如下代码时:
1 | foo() |
上面调用的这个 foo() 函数到底是 module1 模块中的还是 module2 模块中的?因此,这种导入指定模块内所有成员的用法是有风险的。
但如果换成如下两种导入方式:
1 | import module1 |
接下来要分别调用这两个模块中的 foo() 函数就非常清晰。程序可使用如下代码:
1 | #使用模块module1 的模块名作为前缀调用foo()函数 |
或者使用 from…import 语句也是可以的:
1 | #导入module1 中的foo 成员,并指定其别名为foo1 |
此时通过别名将 module1 和 module2 两个模块中的 foo 函数很好地进行了区分,接下来分别调用两个模块中 foo() 函数就很清晰:
1 | foo1() #调用module1 中的foo()函数 |
自定义模块
前面章节中讲过,Python 模块就是 Python 程序,换句话说,只要是 Python 程序,都可以作为模块导入。例如,下面定义了一个简单的模块(编写在 demo.py 文件中):
1 | name = "淘宝网" |
可以看到,我们在 demo.py 文件中放置了变量(name 和 add)、函数( say() )以及一个 Clanguage 类,该文件就可以作为一个模板。
但通常情况下,为了检验模板中代码的正确性,我们往往需要为其设计一段测试代码,例如:
1 | say() |
运行 demo.py 文件,其执行结果为:
1 | 淘宝网 http://www.taobao.com |
通过观察模板中程序的执行结果可以断定,模板文件中包含的函数以及类,是可以正常工作的。
在此基础上,我们可以新建一个 test.py 文件,并在该文件中使用 demo.py 模板文件,即使用 import 语句导入 demo.py:
1 | import demo |
注意,虽然 demo 模板文件的全称为 demo.py,但在使用 import 语句导入时,只需要使用该模板文件的名称即可。
我们知道,在定义函数或者类时,可以为其添加说明文档,以方便用户清楚的知道该函数或者类的功能。自定义模块也不例外。
为自定义模块添加说明文档,和函数或类的添加方法相同,即只需在模块开头的位置定义一个字符串即可。例如,为 demo.py 模板文件添加一个说明文档:
1 | ''' |
在此基础上,我们可以通过模板的 doc 属性,来访问模板的说明文档。例如,在 test.py 文件中添加如下代码:
1 | import demo |
运行结果为:
1 | 淘宝网 http://www.taobao.com |
包
概述
实际开发中,一个大型的项目往往需要使用成百上千的 Python 模块,如果将这些模块都堆放在一起,势必不好管理。而且,使用模块可以有效避免变量名或函数名重名引发的冲突,但是如果模块名重复怎么办呢?因此,Python提出了包(Package)的概念。
什么是包呢?简单理解,包就是文件夹,只不过在该文件夹下必须存在一个名为“init.py” 的文件。
注意,这是 Python 2.x 的规定,而在 Python 3.x 中,init.py 对包来说,并不是必须的。
每个包的目录下都必须建立一个 init.py 的模块,可以是一个空模块,可以写一些初始化代码,其作用就是告诉 Python 要将该目录当成包来处理。
注意,init.py 不同于其他模块文件,此模块的模块名不是 __init__,而是它所在的包名。例如,在 settings 包中的 init.py 文件,其模块名就是 settings。
包是一个包含多个模块的文件夹,它的本质依然是模块,因此包中也可以包含包。
Python 库:相比模块和包,库是一个更大的概念,例如在 Python 标准库中的每个库都有好多个包,而每个包中都有若干个模块。
创建包
包其实就是文件夹,更确切的说,是一个包含“_init_.py”文件的文件夹。因此,如果我们想手动创建一个包,只需进行以下 2 步操作:
- 新建一个文件夹,文件夹的名称就是新建包的包名;
- 在该文件夹中,创建一个 _init_.py 文件(前后各有 2 个下划线‘_’),该文件中可以不编写任何代码。当然,也可以编写一些 Python 初始化代码,则当有其它程序文件导入包时,会自动执行该文件中的代码(本节后续会有实例)。
例如,现在我们创建一个非常简单的包,该包的名称为 my_package,可以仿照以上 2 步进行:
- 创建一个文件夹,其名称设置为 my_package;
- 在该文件夹中添加一个 _init_.py 文件,此文件中可以不编写任何代码。不过,这里向该文件编写如下代码:
1 | ''' |
可以看到,_init_.py 文件中,包含了 2 部分信息,分别是此包的说明信息和一条 print 输出语句。
由此,我们就成功创建好了一个 Python 包。
创建好包之后,我们就可以向包中添加模块(也可以添加包)。这里给 my_package 包添加 2 个模块,分别是 module1.py、module2.py,各自包含的代码分别如下所示:
1 | #module1.py模块文件 |
现在,我们就创建好了一个具有如下文件结构的包:
1 | my_package |
当然,包中还有容纳其它的包,不过这里不再演示,有兴趣的读者可以自行调整包的结构。
导入包
通过前面的学习我们知道,包其实本质上还是模块,因此导入模块的语法同样也适用于导入包。无论导入我们自定义的包,还是导入从他处下载的第三方包,导入方法可归结为以下 3 种:
import 包名[.模块名 [as 别名]]
from 包名 import 模块名 [as 别名]
from 包名.模块名 import 成员名 [as 别名]
用 [] 括起来的部分,是可选部分,即可以使用,也可以直接忽略。
注意,导入包的同时,会在包目录下生成一个含有 init.cpython-36.pyc 文件的 pycache 文件夹。
1.import 包名[.模块名[as 别名]]
以前面创建好的 my_package 包为例,导入 module1 模块并使用该模块中成员可以使用如下代码:
1 | import my_package.module1 |
运行结果:
1 | http://www.jd.com |
可以看到,通过此语法格式导入包中的指定模块后,在使用该模块中的成员(变量、函数、类)时,需添加“包名.模块名”为前缀。当然,如果使用 as 给包名.模块名”起一个别名的话,就使用直接使用这个别名作为前缀使用该模块中的方法了,例如:
1 | import my_package.module1 as module |
直接导入包名,并不会将包中所有模块全部导入到程序中,它的作用仅仅是导入并执行包下的 _init_.py 文件,因此,运行该程序,在执行 _init_.py 文件中代码的同时,还会抛出 AttributeError 异常(访问的对象不存在):
1 | http://os-guangchuang.gitee.io/note |
我们知道,包的本质就是模块,导入模块时,当前程序中会包含一个和模块名同名且类型为 module 的变量,导入包也是如此:
1 | import my_package |
运行结果:
1 | http://os-guangchuang.gitee.io/note |
2.from 包名 import 模块名 [as 别名]
仍以导入 my_package 包中的 module1 模块为例,使用此语法格式的实现代码如下:
1 | from my_package import module1 |
运行结果:
1 | http://os-guangchuang.gitee.io/note |
可以看到,使用此语法格式导入包中模块后,在使用其成员时不需要带包名前缀,但需要带模块名前缀。
当然,我们也可以使用 as 为导入的指定模块定义别名,例如:
1 | from my_package import module1 as module |
此程序的输出结果和上面程序完全相同。
同样,既然包也是模块,那么这种语法格式自然也支持 from 包名 import *
这种写法,它和 import 包名 的作用一样,都只是将该包的 _init_.py 文件导入并执行。
3.from 包名.模块名 import 成员名 [as 别名]
此语法格式用于向程序中导入“包.模块”中的指定成员(变量、函数或类)。通过该方式导入的变量(函数、类),在使用时可以直接使用变量名(函数名、类名)调用,例如:
1 | from my_package.module1 import display |
运行结果:
1 | http://os-guangchuang.gitee.io/note |
当然,也可以使用 as 为导入的成员起一个别名,例如:
1 | from my_package.module1 import display as dis |
该程序的运行结果和上面相同。
另外,在使用此种语法格式加载指定包的指定模块时,可以使用 * 代替成员名,表示加载该模块下的所有成员。例如:
1 | from my_package.module1 import * |
查看模块
dir()函数
事实上,在前面章节的学习中,曾多次使用 dir() 函数。通过 dir() 函数,我们可以查看某指定模块包含的全部成员(包括变量、函数和类)。注意这里所指的全部成员,不仅包含可供我们调用的模块成员,还包含所有名称以双下划线“__”开头和结尾的成员,而这些“特殊”命名的成员,是为了在本模块中使用的,并不希望被其它文件调用。
这里以导入 string 模块为例,string 模块包含操作字符串相关的大量方法,下面通过 dir() 函数查看该模块中包含哪些成员:
1 | import string |
运行结果:
1 | ['Formatter', 'Template', '_ChainMap', '_TemplateMetaclass', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '_re', '_string', 'ascii_letters', 'ascii_lowercase', 'ascii_uppercase', 'capwords', 'digits', 'hexdigits', 'octdigits', 'printable', 'punctuation', 'whitespace'] |
可以看到,通过 dir() 函数获取到的模块成员,不仅包含供外部文件使用的成员,还包含很多“特殊”(名称以 2 个下划线开头和结束)的成员,列出这些成员,对我们并没有实际意义。
因此,这里给读者推荐一种可以忽略显示 dir() 函数输出的特殊成员的方法。仍以 string 模块为例:
1 | import string |
运行结果:
1 | ['Formatter', 'Template', 'ascii_letters', 'ascii_lowercase', 'ascii_uppercase', 'capwords', 'digits', 'hexdigits', 'octdigits', 'printable', 'punctuation', 'whitespace'] |
显然通过列表推导式,可在 dir() 函数输出结果的基础上,筛选出对我们有用的成员并显示出来。
_all_变量
除了使用 dir() 函数之外,还可以使用 all 变量,借助该变量也可以查看模块(包)内包含的所有成员。
仍以 string 模块为例,举个例子:
1 | import string |
运行结果:
1 | ['ascii_letters', 'ascii_lowercase', 'ascii_uppercase', 'capwords', 'digits', 'hexdigits', 'octdigits', 'printable', 'punctuation', 'whitespace', 'Formatter', 'Template'] |
显然,和 dir() 函数相比,_all_ 变量在查看指定模块成员时,它不会显示模块中的特殊成员,同时还会根据成员的名称进行排序显示。
不过需要注意的是,并非所有的模块都支持使用 _all_ 变量,因此对于获取有些模块的成员,就只能使用 dir() 函数。
_doc_属性
在使用 dir() 函数和 _all_ 变量的基础上,虽然我们能知晓指定模块(或包)中所有可用的成员(变量、函数和类), 但对于以上的输出结果,对于不熟悉 string 模块的用户,还是不清楚这些名称分别表示的是什么意思,更不清楚各个成员有什么功能。
针对这种情况,我们可以使用 help() 函数来获取指定成员(甚至是该模块)的帮助信息。以前面章节创建的 my_package 包为例,该包中包含 _init_.py 、module1.py 和 module2.py 这 3 个模块,它们各自包含的内容分别如下所示:
1 | #***__init__.py 文件中的内容*** |
现在,我们先借助 dir() 函数,查看 my_package 包中有多少可供我们调用的成员:
1 | import my_package |
运行结果:
1 | ['Shopping', 'display', 'module1', 'module2'] |
通过此输出结果可以得知,在 my_package 包中,有以上 4 个成员可供我们使用。接下来,我们使用 help() 函数来查看这些成员的具体含义(以 module1 为例):
1 | import my_package |
运行结果:
1 | Help on module my_package.module1 in my_package: |
通过输出结果可以得知,module1 实际上是一个模块文件,其包含 display() 函数,该函数的功能是直接输出指定的 arc 参数。同时,还显示出了该模块具体的存储位置。
以 my_package 包 module1 模块中的 display() 函数为例,我们尝试用 _doc_ 变量获取其说明文档:
运行结果:
1 | 直接输出指定的参数 |
其实,help() 函数底层也是借助 _doc_ 属性实现的。
_file_属性
当指定模块(或包)没有说明文档时,仅通过 help() 函数或者 _doc_ 属性,无法有效帮助我们理解该模块(包)的具体功能。在这种情况下,我们可以通过 _file_ 属性查找该模块(或包)文件所在的具体存储位置,直接查看其源代码。
仍以前面章节创建的 my_package 包为例,下面代码尝试使用 _file_ 属性获取该包的存储路径:
1 | import my_package |
运行结果:
1 | C:\Users\mengma\Desktop\my_package\__init__.py |
注意,因为当引入 my_package 包时,其实际上执行的是 _init_.py 文件,因此这里查看 my_package 包的存储路径,输出的 _init_.py 文件的存储路径。
再以 string 模块为例:
1 | import string |
运行结果:
1 | D:\python3.6\lib\string.py |
由此,通过调用 _file_ 属性输出的绝对路径,我们可以很轻易地找到该模块(或包)的源文件。
注意,并不是所有模块都提供 file 属性,因为并不是所有模块的实现都采用 Python 语言,有些模块采用的是其它编程语言(如 C 语言)。
课后作业
1.略