第8章 模块
8.1 模块概述
模块的英文是Modules,可以认为是一盒(箱)主题积木,通过它可以拼出某一个主题的东西。这与第6章介绍的函数不同,一个函数相当于一块积木,而一个模块中可以包括很多函数,也就是很多积木,所以也可以说模块相当于一盒积木。
在Python中,一个拓展名为“.py”的文件就称之为一个模块。
通常情况下,我们把能够实现某一特定功能的代码放置在一个文件中作为一个模块,从而方便其他程序和脚本导入并使用。另外,使用模块也可以避免函数名和变量名冲突。
经过前面的学习,我们知道对于Python代码可以写到一个文件中。但是随着程序不断变大,为了便于维护,需要将其分为多个文件,这样可以提高代码的可维护性。另外,使用模块还可以提高代码的可重用性。即编写好一个模块后,只要是实现该功能的程序,都可以导入这个模块实现。
8.2 自定义模块
在Python中,自定义模块有两个作用:一个是规范代码,让代码更易于阅读,另一个是方便其他程序使用已经编写好的代码,提高开发效率。
实现自定义模块主要分为两部分,一部分是创建模块,另一个部分是导入模块。
8.2.1 创建模块
创建模块时,可以将模块中相关的代码(变量定义和函数定义等)编写在一个单独的文件中,并且将该文件命名为“模块名+.py”的形式。
注意:创建模块时,设置的模块名不能是Python自带的标准模块名称。
实例01:创建计算BMI指数的模块
def fun_bmi(person,height,weight):
'''功能:根据身高和体重计算BMI指数
person:姓名
height:身高,单位:米
weight:体重,单位:千克
''' print(person + "的身高:" + str(height) + "米\t体重:" + str(weight) + "千克")
bmi=weight/(height*height) # 用于计算BMI指数,公式为:BMI=体重/身高的平方
print(person + "的BMI指数为:"+str(bmi)) # 输出BMI指数
# 此处省略了显示判断结果的代码
def fun_bmi_upgrade(*person):
'''功能:根据身高和体重计算BMI指数(升级版)
*person:可变参数该参数中需要传递带3个元素的列表,
分别为姓名、身高(单位:米)和体重(单位:千克)
''' # 此处省略了函数主体代码
注意:模块文件的扩展名必须是“.py”。
8.2.2 使用import 语句导入模块
创建模块后,就可以在其他程序中使用该模块了。要使用模块需要以模块的形式加载模块中的代码,这可以使用import语句实现。import语句的基本语法格式如下:
import modulename[as alias]
其中,modulename为要导入模块的名称;[as alias]为给模块起的别名,通过该别名也可以使用模块。
下面将导入实例01所编写的模块bmi,并执行该模块中的函数。在模块文件bmi.py的同级目录下创建一个名称为mai.py的文件,在该文件中,导入模块bmi,并且执行该模块中的fun_bmi()函数,代码如下:
import bmi # 导入bmi模块
bmi.fun_bmi("大白",1.80,130) # 执行模块中的fun_bmi()函数
执行上面的代码,运行结果如下。
大白的身高:1.8米 体重:130千克
大白的BMI指数为:40.123456790123456
说明:在调用模块中的变量、函数或者类时,需要在变量名、函数名或者类名前添加“模块名.”作为前缀。例如,上面代码中的bmi.fun_bmi,表示调用bm i模块中的fun_bmi() 函数。
多学两招:
如果模块名比较长不容易记住,可以在导入模块时,使用as 关键字为其设置一个别名,然后就可以通过这个别名来调用模块中的变量、函数和类等。例如,将上面导入模块的代码修改为以下内容:
import bmi as m # 导入bmi模块并设置别名为m
然后,在调用bmi 模块中的fun_bmi() 函数时,可以使用下面的代码:
m.fun_bmi("大白",1.80,130) # 执行模块中的fun_bmi()函数
使用import 语句还可以一次导入多个模块,在导入多个模块时,模块名之间使用逗号“,”进行分隔。例如,分别创建了bmi.py、tips.py 和 differenttree.py 3 个模块文件。想要将这3个模块全部导入,可以使用下面的代码:
import bmi,tips,differenttree
8.2.3 使用from...import 语句导入模块
在使用import 语句导入模块时,每执行一条import 语句都会创建一个新的命名空间(namespace),并且在该命名空间中执行与.py 文件相关的所有语句。在执行时,需在具体的变量、函数和类名前加上“模块名.”前缀。如果不想在每次导入模块时都创建一个新的命名空间,而是将具体的定义导入到当期的命名空间,这时可以使用from...import 语句。使用from...import 语句导入模块后,不需要在添加前缀,直接通过具体的变量、函数和类名等访问即可。
说明:命名空间可以理解我记录对象名字和对象之间对应关系的空间。目前Python的命名空间大部分都是通过字典(dict)来实现的。其中,key 是标识符;value 是具体的对象。例如,key 是变量的名字,value 则是变量的值。
from...import 语法格式如下:
from modelname import member
参数说明:
- modelname:模块名称,区分字母大小写,需要和定义模块时设置的模块名称大小写保存一致。
- member:用于指定要导入的变量、函数或者类等。可以同时导入多个定义,各个定义之间使用逗号“.” 分隔。如果想导入全部定义,也可以使用通配符号“*” 代替。
多学两招:在导入模块时,如果使用通配符“*” 导入全部定义后,想查看具体导入了哪些定义,可以通过显示dir() 函数的值来查看。例如,执行print(dir()) 语句后将显示类似下面的内容。
['__annotations__', '__builtins__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'm']
……
例如,通过下面的3 条语句都可以从模块导入指定的定义。
from bmi import fun_bmi # 导入bmi模块的fun_bmi函数
from bmi import fun_bmi,fun_bmi_uparade # 导入bmi模块的fun_bmi和fun_upgrade函数
from bmi import * # 导入bmi模块的全部定义(包括变量和函数)
注意:在使用from...import 语句导入模块中的定义时,需要保证所导入的内容在当前的命名空间中是唯一的,否则将出现冲突,后导入的同名变量、函数或者会覆盖先导入的。这时就需要使用import语句进行导入。
实例02:导入两个包括同名函数的模块
创建两个模块,一个是矩形模块,其中包括计算矩形周长和面积的函数;另一个是圆形,其中包括计算圆形周长和面积的函数。然后在另一个Python文件中导入这两个模块,并调用相应的函数计算周长的面积。具体步骤如下:
(1)创建矩形模块,对应的文件名为rectangle.py,在该文件中定义两个函数,一个用于计算矩形的周长,另一个用于计算矩形的面积,具体代码如下:
def firth(width,height):
'''功能:计算周长
参数:width(宽度)、height(高)
'''
return(width + height)*2
def area(width,height):
'''功能:计算面积
参数:width(宽度)、height(高)
'''
return width * height
if __name__ == '__main__':
print(area(10,20))
(2)创建圆形模块,对应的文件名称为circular.py,在该文件中定义两个函数,一个用于计算圆形的周长,另一个用于计算圆形的面积,具体代码如下:
import math # 导入标志模块math
PI = math.pi # 圆周率
def girth(r):
'''功能:计算周长
参数:r(半径)
'''
return round(2 * PI * r ,2 ) # 计算周长并保留两位小数
def area(r):
'''功能:计算面积
参数:r(半径)
'''
return round(PI * r * r ,2 ) # 计算面积并保留两位小数
if __name__ == '__main__':
print(girth(10))
(3)创建一个名称为compute.py 的Python文件,首先导入矩形模块的全部定义,然后导入圆形模型的全部定义,最后分别调用计算矩形周长的函数和计算圆形周长的函数,代码如下:
from rectangle import * # 导入矩形模块
from circular import * # 导入圆形模块
if __name__ == '__main__':
print('圆形的周长为:',girth(10)) # 调用计算圆形周长的函数
print('矩形的周长为:',area(10,20)) # 调用计算矩形周长的函数
执行compute.py文件,将显示如下结果。
圆形的周长为: 62.83
Traceback (most recent call last):
File "D:\python3.6.5\练习文件\main.py", line 11, in <module>
print('矩形的周长为:',girth(10,20)) # 调用计算矩形周长的函数
TypeError: girth() takes 1 positional argument but 2 were given
从图中可以看出执行步骤(3)的第5行代码时出现异常,这是因为原本想执行的矩形模块的girth() 函数被圆形模块的girth() 函数给盖住了。解决该问题的方法是,不使用from...import 语句导入,而是使用import 语句导入。修改后的代码如下:
import rectangle as r # 导入矩形模块
import circular as c # 导入圆形模块
if __name__ == '__main__':
print('圆形的周长为:',c.girth(10)) # 调用计算圆形周长的函数
print('矩形的周长为:',r.area(10,20)) # 调用计算矩形周长的函数
运行结果如下:
圆形的周长为: 62.83
矩形的周长为: 200
8.2.4 模块搜索目录
当使用import 语句导入模块时,默认情况下,会按照以下顺序进行查找。
(1)在当前目录(即执行的Python 脚本文件所在目录)下查找。
(2)到PYTHONPATH(环境变量)下的每个目录中查找。
(3)到Python 的默认安装目录下查找。
以上各个目录的具体位置保存在标准模块sys 的sys.path 变量中。可以通过以下代码输出具体的目录。
import sys # 导入标准模块sys
print(sys.path) # 输出具体目录
……
注意:使用import 语句导入模块时,模块名是区分字母大小写的。
这时,我们可以通过以下3 种方式添加指定的目录到sys.path 中。
1. 临时添加
临时添加即在导入模块的Python 文件中添加。
2. 增加.pth文件(推荐)
注意:创建.pth 文件后,需要重新打开要执行的导入模块的Python 文件,否则新添加的目录不起作用。
说明:通过该方法添加的目录只在当前版本的Python 中有效。
3. 在PYTHONPATH 环境变量中添加
说明:通过该方法添加的目录可以在不同版本的Python *享。
8.3 以主程序的形式执行
这里先来创建一个模块,名称为christmastree,该模块的内容为第6章中的编写的实例05的代码。该段代码中,首先定义一个全局变量,然后创建一个名称为fun_christmastree() 的函数,最后在通过print() 函数输出一些内容。代码如下:
pinetree = '我是一棵松树' # 定义一个全局变量(松树)
def fun_christmastree(): # 定义函数
'''功能:一个梦
无返回值
'''
pinetree = '挂上彩灯、礼物……我变成一棵圣诞树 @^.^@ \n' # 定义局部变量
print(pinetree) # 输出局部变量的值
#**************************函数体外*****************************#
print('\n下雪了……\n')
print('=============开始做梦……==============\n')
fun_christmastree() # 调用函数
print('=============梦醒了……==============\n')
pinetree = '我身上落满雪花,'+ pinetree + '-_-' # 为全局变量赋值
print(pinetree) # 输出全局变量的值
在与Christmastree 模块同级的目录下,创建一个名称为main.py 的文件,在该文件中,导入christmastree 模块,再通过print() 语句输出模块中的全局变量pinetree 的值,代码如下:
import christmastree # 导入Christmastree模块
print("全局变量的值为:",christmastree.pinetree)
执行上面的代码,显示结果如下:
下雪了…… =============开始做梦……============== 挂上彩灯、礼物……我变成一棵圣诞树 @^.^@ =============梦醒了……============== 我身上落满雪花,我是一棵松树-_-
全局变量的值为: 我身上落满雪花,我是一棵松树-_-
>>>
从上的运行结果可以看出,导入模块后,不仅输出了全局变量的值,而且模块中原有的测试代码也被执行了。这个结果显然不是我们想要的。那么如何输出全局变量的值呢?实际上,可以在模块中,将原本直接执行的代码放在一个if 语句中。因此,可以将模块Christmastree 的代码修改为以下内容:
pinetree = '我是一棵松树' # 定义一个全局变量(松树)
def fun_christmastree(): # 定义函数
'''功能:一个梦
无返回值
'''
pinetree = '挂上彩灯、礼物……我变成一棵圣诞树 @^.^@ \n' # 定义局部变量
print(pinetree) # 输出局部变量的值
#**************************函数体外*****************************#
if __name__=='__main__':
print('\n下雪了……\n')
print('=============开始做梦……==============\n')
fun_christmastree() # 调用函数
print('=============梦醒了……==============\n')
pinetree = '我身上落满雪花,'+ pinetree + '-_-' # 为全局变量赋值
print(pinetree) # 输出全局变量的值
再次执行导入模块的main.py 文件,可以看出示测试代码并没有执行。
全局变量的值为: 我是一棵松树
此时,如果执行christmastree.py 文件,结果如下:
下雪了…… =============开始做梦……============== 挂上彩灯、礼物……我变成一棵圣诞树 @^.^@ =============梦醒了……============== 我身上落满雪花,我是一棵松树-_-
说明:在每个模块的定义中都包括一个记录模块名称的变量__name__,程序可以检查该变量,以确定它们在哪个模块中执行。如果一个模块不是被导入到其他程序中执行,那么它可能在解释器的*模块中执行。*模块的__name__变量的值为__main__。
8.4 Python中的包
使用模块可以避免函数名和变量名重名引发的冲突。那么,如果模块名重复应该怎么办呢?在Python中,提出了包(Package)的概念。包是一个分层次的目录结构,它将一组功能相近的模块组织在一个目录下。这样,既可以起到规范代码的作用,又能避免模块名重名引起的冲突。
说明:包简单的理解就是“文件夹”,只不过在该文件夹下必须存在一个名称为“__init__.py”的文件。
8.4.1 Python程序的包结构
在实际项目开发时,通常情况下,会创建多个包用于存放不同类的文件。
8.4.2 创建和使用包
下面将分别介绍如何创建和使用包。
1. 创建包
创建包实际上就是创建一个文件夹,并且在该文件夹中创建一个名称为“__init__.py”的Python文件。在__init__.py文件中,可以不编写任何代码,也可以编写一些Python代码。在__init__.py文件中所编写的代码,在导入包时会自动执行。
说明:__init__.py文件是一个模块文件,模块名称对应的包名。例如,在settings包中创建的__init__.py文件,对应的模块名为settings。
至此,名称为settings的包创建完毕了,创建完毕之后便可以在该包中创建所需的模块了。
2. 使用包
创建包以后,就可以在包中创建相应的模块,然后再使用import语句从包中加载模块。从包中加载模块通常有以下3 种方式:
- 通过“import+完整包名+模块名”形式加载指定模块
通过“import+完整包名+模块名”形式是指:假如有一个名称为settings的包,在该包下有一个名称为size 的模块,那么要导入size 模块,可以使用下面的代码:
import settings.size
通过该方式导入模块后,在使用时需要使用完整的名称。例如,在已经创建的settings包中创建一个名称为size的模块,并且在该模块中定义两个变量,代码如下:
width = 800 # 宽度
heigtht = 600 # 高度
这时,通过“import+完整包名+模块名”形式导入size模块后,在调用width和height变量时,就需要在变量名前加入“settings.size”前缀。对应的代码如下:
import settings.size # 导入settings包下的size模块
if __name__=='__main__':
print('宽度:',settings.size.width)
print('高度:',settings.size.height)
执行上面的代码后,将显示以下内容:
宽度: 800
高度: 600
- 通过“from+完整包名+import+模块名”形式加载指定模块
“from+完整包名+import+模块名”形式是指:假如有一个名称为settings的包,在该包下有一个名称为size的模块,那么要导入size模块,可以使用下面的代码:
form settings import size
通过该方式导入模块后,在使用时不需要带包前缀,但是需要带模块名。例如,想通过“from+完整包名+import+模块名”形式导入上面已经创建的size模块,并且调用width和height变量,就可以通过下面的代码实现:
from settings import size # 导入settings包下的size模块
if __name__=='__main__':
print('宽度:',size.width)
print('高度:',size.height)
执行上面的代码后,将显示以下内容:
宽度: 800
高度: 600
- 通过“from+完整包名+模块名+import+定义名”形式加载指定模块
“from+完整包名+模块名+import+定义名”形式是指:加入有一个名称为settings的包,在该包下有一个名称为size的模块,那么要导入size模块中的width和height变量,可以使用下面的代码:
from settings.size import width,height
通过该方式导入模块的函数、变量或类后,在使用时直接使用函数、变量或类名即可。例如,想通过“from+完整包名+模块名+import+定义名”形式导入上面已经创建的size模块的width和height,并输出,就可以通过下面的代码实现:
# 导入settings包下size模块中的width和height变量
from settings.size import width,height
if __name__=='__main__':
print('宽度:',width) # 输出宽度
print('高度:',height) # 输出高度
执行上面的代码后,将显示以下内容:
宽度: 800
高度: 600
说明:在通过“from+完整包名+模块名+import+定义名”形式加载指定模块时,可以使用星号“*” 代替定义名,表示加载该模块下的全部定义。
实例03:在指定包中创建通过的设置和获取尺寸的模块
创建一个名称为settings 的包,在该包下创建一个名称为size的模块,通过该模块实现设置和获取尺寸的通用功能。具体步骤如下:
(1)在settings包中,创建一个名称为size的模块,在该模块中,定义两个保护类型的的全局变量,分别代表宽度和高度,然后定义一个change()函数,用于修改两个全局变量的值,在定义两个函数,分别用于获取宽度和高度,具体代码如下:
width = 800 # 定义保护类型的全局变量(宽度)
height = 600 # 定义保护类型的全局变量(高度)
def change(w,h):
global _width # 全局变量(宽度)
_width = w # 重新给宽度赋值
global _height # 全局变量(高度)
_height = h # 重新给高度赋值
def getWidth(): # 获取宽度的函数
global _width
return _width
def getHeight(): # 获取高度的函数
global _height
return _height
(2)在settings 包的上一层目录中创建一个名称为main.py的文件,在该文件中导入settings包下的size模块的全部定义,并且调用change()函数重新设置宽度和高度,然后在分别调用getWidth()和getHeight()函数获取修改后的宽度和高度,具体代码如下:
from settings.size import * # 导入size模块下的全部定义
if __name__=='__main__':
change(1024,768) # 调用change()函数改变尺寸
print('宽度:',getWidth()) # 输出宽度
print('高度:',getHeight()) # 输出高度
执行本实例,显示结果如下:
宽度: 1024
高度: 768
8.5 引用其他模块
在Python中,除了可以自定义模块外,还可以引用其他模块,主要包括使用标准模块和第三方模块。下面分别进行介绍。
8.5.1 导入和使用标准模块
在Python中,自带了很多实用的模块,称为标准模块(也可以称为标准库),对于标准模块,我们可以直接使用import语句导入Python文件中使用。例如,导入标准模块random(用于生成随机数),可以使用下面的代码:
import random # 导入标准模块random
说明:在导入标准模块时,也可以使用as 关键字为其指定别名。通常情况下,如果模块名比较长,则可以为其设置别名。
导入标准模块后,可以通过模块名调用其提供的函数。例如,导入random模块后,就可以调用randint()函数生成一个指定范围的随机数整数。例如,生成一个0~10(包括0和10)的随机整数的时代代码如下:
import random # 导入标准模块random
print(random.randint(0,10)) # 输出0~10的随机数
执行上面的代码,可能会输出0~10 中的任意一个数。
实例04:生成有数字、字母组成的4 位验证码
在IDLE 中创建一个名称为checkcode.py 的文件,然后在该文件中导入Python 标准模块中random 模块(用于生成随机数),然后定义一个保存验证码的变量,在应用for 语句实现一个重复4 次的循环,在该循环中,调用random 模块提供的randrange() 和randint() 方法生成符合要求的验证码,最后输出生成的验证码,代码如下:
import random # 导入标准模块中的random
if __name__=='__main__':
checkcode = "" # 保存验证码的变量
for i in range(4): # 循环4次
index = random.randrange(0,4) # 生成0~3中的一个数
if index != i and index + 1 != i:
checkcode += chr(random.randint(97,122)) # 生成a~z中的一个小写字母
elif index + 1 == i:
checkcode += chr(random.randint(65,90)) # 生成A~Z中的一个大写字母
else:
checkcode += str(random.randint(1,9)) # 生成1~9中的一个数字
print("验证码:",checkcode) # 输出生成的验证码
执行本实例,显示如下结果:
验证码: k5Ni
除了random模块外,Python还提供了大约200多个内置的标准模块,涵盖了Python运行时服务、文字模式匹配、操作系统接口、数学运算、对象永久保存、网络和Internet脚本和GUI构建等方面。
模块名 | 描述 |
sys | 与Python解释器及其环境操作相关的标准库 |
time | 提供与时间相关的各种函数的标准库 |
os | 提供了访问凑在哦系统服务功能的标准库 |
calendar | 提供了与日期相关的各种函数的标准库 |
urllib | 用于读取来自网上(服务器上)的数据的标准库 |
json | 用于使用JSON序列化和反序列化对象 |
re | 用于在字符串中执行正则表达式匹配和替换 |
math | 提供算术运算函数的标准库 |
decimal | 用于进行精确控制运算精度、有效数位和四舍五入操作的十进制运算 |
shutil | 用于进行高级文件操作,如复制、移动和重命名等 |
logging | 提供了灵活的记录事件、错误、警告和调试信息等日志的功能 |
tkinter | 使用Python进行GUI编程的标准库 |
8.5.2 第三方模块的下载与安装
在进行Python程序开发时,除了可以使用Python内置的标准模块外,还有很多第三方模块可以被我们所使用。对于这些第三方模块,可以在Python官方推出的http://pypi.python.org/pypi中找到。
在使用第三方模块时,需要先下载并安装该模块,然后就可以像使用标准模块一样导入并使用了。本节主要介绍如何下载和安装。下载和安装第三方模块可以使用Python提供的pip命令实现。pip命令的语法格式如下:
pip<command>[modulename]
参数说明:
- command:用于指定要执行的命令。常用的参数值有install(用于安装第三方模块)、uninstall(用于卸载已经安装的第三方模块)、list(用于显示已经安装的第三方模块)等。
- modulename:可选参数,用于指定要安装或者卸载的模块名,当command为install或者uninstall时不能省略。
例如,安装第三方numpy模块(用于科学计算),可以在命令行窗口中输入以下代码:
pip install mumpy
执行上面代码,将在线安装numpy模块。
注意:添加完环境变量需要重启命令窗口。
说明:在大型程序中可能需要导入很多模块,推荐先导入Python提供的标准模块,然后在导入第三方模块,最后导入自定义模块。
多学两招:如果想要查看Python中的都有哪些模块(包括标准模块和第三方模块),可以在INLE中输入以下命令:
help('modules')
如果只是想要查看已经安装的第三方模块,可以在命令行窗口中输入以下命令:
pip list