共196 次浏览

第八章 模块和包

8.1 模块概述

在Python中,模块是一个扩展名为 .py 的文件。 将实现某一特定功能的代码放置在一个文件夹中可以作为一个模块,从而方便其他程序直接调用。同时,使用模块还可以避免函数名和变量名冲突的问题。例如,下面代码的功能是检查敏感字,将代码保存在savestring.py,则该文件就是一个模块。

 def savestring(s):
 import re  
 print("欢迎进入敏感字过滤系统")
 s1 = r'(渗透)|(攻击)|(脚本)'    #模式字符串
 match = re.match(s1,s)       #进行模式匹配
 if match==None:
   print("您输入的文字安全通过!")
 else:
   print("警告!您输入的文字存在敏感字,请重新整理后输入!")

8.2 自定义模块

自定义的模块不仅可以规范代码,还可以方便其它程序直接调用,从而提高开发效率。

8.2.1 创建模块

将模块中的代码编写在一个单独的文件中,然后将其命名为模块名.py 即可。 需要注意的是模块名不能和标准模块重名,如果出现重名的问题,在导入标准模块时就会把这些定义的文件当成模板来加载,通常会引起错误。比如说我们下面新建一个文件名为friends.py的模块:

 def makeFriends( par ):
    print "Hello : ", par,"My name is 赵日天, I want to make friends with you!"
    return

8.2.2 使用import 语句导入模块

模块创建完毕之后,就可以在其它程序中使用该模块了。导入模块可以通过import 语句来实现。

 import  modulename [as alias]

modulename为导入的模块名称;[as alias ]为给模块起的别名,可以省略。现在我们就可以直接导入上面的模块 ,并且使用其中的函数。我们也可以导入其具体的一个函数,使用from……import……语句进行。这一点类似于Java中的import,比如我们要导入某一个具体的函数,但是完全可以将整个包导入,或者是直接 *,虽然结果都一样,但是个人的习惯因人而异。

 import frieds
 #也可以导入具体的函数
 #from makeFriends import friends
 friends.makeFriends("赵日地")
 #打印结果为   Hello: 赵日地,My name is 赵日天 ,I want to make friends with you!

上面我们创建的后缀为py的文件我们就直接将它当作包的名字导入,然后就可以直接使用其中的函数。当解释器遇到import时,会在当前路径下搜索该模块。需要注意的是在调用模块中的变量、函数或类时,需要在变量名、函数名或者类的前面添加模块名称作为前缀。同样的,使用一个import语句可以一次加载多个模块,模块名称之间可以逗号隔开。

8.2 包

8.2.1 Python 中的包

包是一组模块的集合,而模块是一个Python文件,所以包就可以理解为存放着若干个模块的集合。并且在该目录下有一个_init _.py的文件(包的初始化文件) ,可以在该文件里导入包里的所有模块。

掌握了包是什么之后,接下来学习如何定义包。定义包更简单,主要有两步:创建一个文件夹,该文件夹的名字就是该包的包名。

在该文件夹内添加一个 init.py 文件即可。

下面定义一个非常简单的包。先新建一个 first_package 文件夹,然后在该文件夹中添加一个 init.py 文件,该文件内容如下: ”’这是学习包的第一个示例”’
 print(‘this is first_package’)上面的 Python 源文件非常简单,该文件开始部分的字符串是该包的说明文档,接下来是一条简单的输出语句。下面通过如下程序来使用该包:

 # 导入first_package包(模块)
 import first_packageprint('==========')
 print(first_package.__doc__)
 print(type(first_package)) 
  print(first_package)


再次强调,包的本质就是模块,因此导入包和导入模块的语法完全相同。因此,上面程序中第 2 行代码导入了 first_package 包。程序最后三行代码输出了包的说明文档、包的类型和包本身。运行该程序,可以看到如下输出结果:这是学习包的第一个示例 <class ‘module’>
 <module ‘first_package’ from ‘G:\\publish\\codes\\09\\9.3\\first_package\\__init__.py’>从上面的输出结果可以看出,在导入 first_package 包时,程序执行了该包所对应的文件夹下的 init.py;从倒数第二行输出可以看到,包的本质就是模块;从最后一行输出可以看到,使用 import first_package 导入包的本质就是加载井执行该包下的 init.py 文件,然后将整个文件内容赋值给与包同名的变量,该变量的类型是 module。与模块类似的是,包被导入之后,会在包目录下生成一个 pycache 文件夹,并在该文件夹内为包生成一个 init.cpython-36.pyc 文件。由于导入包就相当于导入该包下的 init.py 文件,因此我们完全可以在 init.py 文件中定义变量、函数、类等程序单元,但实际上往往并不会这么做。想一想原因是什么?包的主要作用是包含多个模块,因此 init.py 文件的主要作用就是导入该包内的其他模块。下面再定义一个更加复杂的包,在该包下将会包含多个模块,并使用 init.py 文件来加载这些模块。新建一个 fk_package 包,并在该包下包含三个模块文件:

print_shape.py

billing.py

arithmetic_chart.pyfk_package 的文件结构如下:fk_package ┠──arithmetic_chart.py ┠──billing.py ┠──print_shape.py ┗━━init.py其中,arithmetic_chart.py 模块文件的内容如下: def print_multiple_chart(n):
   ‘打印乘法口角表的函数’    

for i in range(n):      
     for j in range(i + 1):      
       print('%d * %d = %2d' % ((j + 1) , (i + 1) , (j + 1)* (i + 1)), end=' ')      
       print('')


上面模块文件中定义了一个打印乘法口诀表的函数。billing.py 模块文件的内容如下:


 class Item:    ‘定义代表商品的Item类’  
   def __init__(self, price):        
     self.price = price    def __repr__(self):    
       return ‘Item[price=%g]’ % self.

priceprint_shape.py 模块文件的内容如下: 
def print_blank_triangle(n):  
   ‘使用星号打印一个空心的三角形’  

  if n <= 0:        
     raise ValueError('n必须大于0')  
     for i in range(n):      
       print(' ' * (n - i - 1), end='')  
       print('*', end='')    
       if i != n - 1:        
         print(' ' * (2 * i - 1), end='')  
         else:          
           print('*' * (2 * i - 1), end='')    
           if i != 0:          
             print('*')      
             else:          
               print('')


tk_package 包下的 init.py 文件暂时为空,不用编写任何内容。上面三个模块文件都位于 fk_package 包下,总共提供了两个函数和一个类。这意味着 fk_package 包(也是模块)总共包含 arithmetic_chart、 billing 和 print_shape 三个模块。在这种情况下,这三个模块就相当于 fk_package 包的成员。导入包内成员如果需要使用 arithmetic_chart、 billing 和 print_shape 这三个模块,则可以在程序中执行如下导入代码: # 导入fk_package包,实际上就是导入包下__init__.py文件

 import fk_package
 # 导入fk_package包下的print_shape模块,
 # 实际上就是导入fk_package目录下的print_shape.py
 import fk_package.print_shape
 # 实际上就是导入fk_package包(模块)导入print_shape模块
 from fk_package import billing
 # 导入fk_package包下的arithmetic_chart模块,
 # 实际上就是导入fk_package目录下的arithmetic_chart.py
 import fk_package.arithmetic_chart
 ​
 fk_package.print_shape.print_blank_triangle(5)
 im = billing.Item(4.5)
 print(im)
 fk_package.arithmetic_chart.print_multiple_chart(5)

上面程序中第 2 行代码是“import fk_package”,由于导入包的本质只是加载并执行包里的 init.py 文件,因此执行这条导入语句之后,程序只能使用 fk_package 目录下的 init.py 文件中定义的程序单元。对于本例而言,由于 fk_package__init__.py 文件内容为空,因此这条导入语句没有任何作用。第 5 行导入语句的本质就是加载并执行 fk_package 包下的 print_shape.py 文件,并将其赋值给 fk_package.print_shape 变量。因此执行这条导入语句之后,程序可访问 fk_package\print_shape.py 文件所定义的程序单元,但需要添加 fk_package.print_shape 前缀。第 8 行导入语句的本质是导入 fk_package 包(也是模块)下的 billing 成员(其实是模块)。因此执行这条导入语句之后,程序可使用 fk_package\billing.py 文件定义的程序单元,而且只需要添加 billing 前缀。第 11 行代码与第 5 行代码的导入效果相同。该程序后面分别测试了 fk_package 包下的 print_shape、billing、arithmetic_chart 这三个模块的功能。运行上面程序,可以看到三个模块的功能完全可以正常显示。上面程序虽然可以正常运行,但此时存在两个问题:

为了调用包内模块中的程序单元,需要使用很长的前缀,这实在是太麻烦了。包内 init.py 文件的功能完全被忽略了。想一想就知道,包内的 init.py 文件并不是用来定义程序单元的,而是用于导入该包内模块的成员,这样即可把模块中的成员导入变成包内成员,以后使用起来会更加方便。将 fk_package 包下的 init.py 文件编辑成如下形式: 

# 从当前包导入print_shape模块
 from . import print_shape
 # 从.print_shape导入所有程序单元到fk_package中
 from .print_shape import *
 # 从当前包导入billing模块
 from . import billing
 # 从.billing导入所有程序单元到fk_package中
 from .billing import *
 # 从当前包导入arithmetic_chart模块
 from . import arithmetic_chart
 # 从.arithmetic_chart导入所有程序单元到fk_package中
 from .arithmetic_chart import *

该程序的代码基本上差不多,都是通过如下两行代码来处理导入的: # 从当前包导入print_shape模块
 from . import print_shape
 # 从.print_shape导入所有程序单元到fk_package中
 from .print_shape import *上面第一行 from…import 用于导入当前包(模块)中的 print_shape(模块),这样即可在 tk_package 中使用 print_shape 模块。但这种导入方式是将 print_shape 模块导入了 fk_package 包中,因此当其他程序使用 print_shape 内的成员时,依然需要通过 fk_package.print_shape 前缀进行调用。第二行导入语句用于将 print_shape 模块内的所有程序单元导入 fk_package 模块中,这样以后只要使用 fk_package.前缀就可以使用三个模块内的程序单元。例如如下程序: # 导入fk_package包,实际上就是导入包下__init__.py文件

 import fk_package
 ​
 # 直接使用fk_package前缀即可调用它所包含的模块内的程序单元。
 fk_package.print_blank_triangle(5)
 im = fk_package.Item(4.5)
 print(im)

 fk_package.print_multiple_chart(5)上面第 2 行代码是导入 tk_package 包,导入该包的本质就是导入该包下的 init.py 文件。而 init.py 文件又执行了导入,它们会把三个模块内的程序单元导入 tk_package 包中,因此程序的下面代码可使用 tk_package.前缀来访问三个模块内的程序单元。运行上面程序,同样可以看到正常的运行结果。