一、 使用包
1、什么是包
Python 提供了包来管理多个模块源文件。包是一个文件夹,在该文件夹下包含一个 __init__.py 文件,该文件夹可用于包含多个模块源文件;包的本质依然是模块。所以,包的作用是包含多个模块,但包的本质依然是模块,因此包也可包含包。
2、定义包
定义一个包分为两步:
(1)、创建一个文件夹,文件夹名字就是包的名字。
(2)、在该文件夹内添加一个 __init__.py 文件即可。
包的说明文档可放在包下的 __init__.py 文件的开始部分,用三个单引号或双引号包起来。如果该包需要一些可执行代码,也可将可执行代码放在 __init__.py 文件中,这样在导入该包的时候就得到执行。
包的本质就是模块,因此导入包和导入模块的语法是完全相同的。使用 import 导入包的本质就是加载并执行该包下的 __init__.py 文件,然后将整个文件内容赋值给与包同名的变量,该变量的类型是 module。
与模块类似,包被导入后,会在包目录下生成一个 __pycache__ 文件夹,并在该文件夹内为包生成一个 __init__.cpython-36.pyc 文件。导入包就是在导入该包下的 __init__.py 文件,因此可在 __init__.py 文件中定义变量、函数、类等程序单元,但是通常不建议这么做。包的主要作用是包含多个模块及子包,因此 __init__.py 文件的主要作用是导入该包内的其他模块和子包。
为了对包进行更好的理解,下面就来创建一个简单的包,该包下包含三个模块,并使用 __init__.py 文件来加载这些模块,包的名称是 py_package,包含的三个模块文件是:print_shape.py、billing.py、arithmetic_chart.py,以及一个__init__.py文件。
arithmetic_chart.py 模块文件的内容如下,这个模块中定义一个乘法口诀表函数:
1 def print_multiple_chart(n): 2 """打印乘法口诀表函数""" 3 for i in range(n): 4 for j in range(i + 1): 5 if i == j: 6 print('%d * %d = %2d' % ((j + 1), (i + 1), (j + 1) * (i + 1)), end=" ") 7 else: 8 print('%d * %d = %2d' % ((j + 1), (i + 1), (j + 1) * (i + 1)), end=", ") 9 print()
billing.py 模块文件的代码如下:
class Item: """定义代表商品的 Item 类""" def __init__(self, price): self.price = price def __repr__(self): return 'Item[price=%g]' % self.price
print_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('')
现在三个模块文件的代码已完成,暂时让 py_package 包下的 __init__.py 文件为空。目前 py_package 包下的三个模块实际就是该包下的三个成员。接下来就导入包内成员。
3、 导入包内成员
在与 py_package 包相同的目录下新建一个 Python 源代码文件,名称省略,在该源代码文件中导入 py_package 包,代码如下:
1 # 导入 py_package 包,实际导入包下的 __init__.py 文件 2 import py_package 3 4 # 导入 py_package 包下的 print_shape 模块,实际导入包目录下的 print_shape.py 文件 5 import py_package.print_shape 6 7 # 导入 py_package 包下的 billing 模块,实际导入包目录下的 billing.py 文件 8 from py_package import billing 9 10 # 导入 py_package 包下的 arithmetic_chart 模块,实际导入包目录下的 arithmetic_chart.py 文件 11 import py_package.arithmetic_chart 12 13 # 下面调用模块内的方法或函数 14 py_package.print_shape.print_blank_triangle(5) 15 im = billing.Item(5.5) 16 print(im) 17 py_package.arithmetic_chart.print_multiple_chart(5) 18 19 运行代码,输出结果如下: 20 * 21 * * 22 * * 23 * * 24 ********* 25 Item[price=5.5] 26 1 * 1 = 1 27 1 * 2 = 2, 2 * 2 = 4 28 1 * 3 = 3, 2 * 3 = 6, 3 * 3 = 9 29 1 * 4 = 4, 2 * 4 = 8, 3 * 4 = 12, 4 * 4 = 16 30 1 * 5 = 5, 2 * 5 = 10, 3 * 5 = 15, 4 * 5 = 20, 5 * 5 = 25
上面代码中,第一条导入语句 “import py_package” 导入的本质是加载并执行包里的 __init__.py 文件,执行这条导入语句后,只能使用 py_package 目录下的 __init__.py 文件中定义的单元。在这里 py_package\__init__.py 文件内容是空的,因此这条导入语句没有任何作用。
第二条导入语句 “import py_package.print_shape” 本质是加载并执行 py_package 包下的 print_shape.py 文件,并将其赋值py_package.print_shape 变量。因此执行这条导入语句后,在后面的代码中可访问 py_package\print_shape.py 文件中定义的程序单元,但是需要添加 py_package.print_shape 前缀。
第三条导入语句 “from py_package import billing” 的本质是导入 py_package 包下的 billing 成员(或模块)。执行这条语句后,后面代码可使用 py_package\billing.py 文件定义的程序单元,并且只需要添加 billing 前缀。
第四条导入语句与第二条导入语句的效果是一样的。
上面的代码可以成功运行,但是还存在两个问题:
(1)、在调用包内模块中的程序单元时,要使用长前缀,这样显得有些麻烦。
(2)、包内的 __init__.py 文件的功能没有起到作用。
包内的 __init__.py 文件不是用来定义程序单元的,而是用来导入该包内模块的成员,可将模块中的成员导入成包内成员,使用起来更方便。现在修改 py_package 包下的 __init__.py 文件,该文件的代码如下所示:
1 # 从当前包中导入 print_shape 模块 2 from . import print_shape 3 # 从 .print_shape 中导入所有程序单元到 py_package 中 4 from .print_shape import * 5 6 # 从当着包中导入 billing 模块 7 from . import billing 8 # 从 .billing 中导入所有程序单元到 py_package 中 9 from .billing import * 10 11 # 从当前包中导入 arithmetic_chart 模块 12 from . import arithmetic_chart 13 # 从 .arithmetic_chart 中导入所有程序单元到 py_package 中 14 from .arithmetic_chart import *
这个 __init__.py 文件中的导入代码基本相同,都是在导入包中的3个模块和3个模块中的所有成员。要注意的是,使用“from . import print_shape” 方式导入包中的模块,要使用 print_shape 模块内的成员时,仍然要通过添加py_package.print_shape 前缀进行调用。但是使用 “from .print_shape import *” 方式导入时,是将模块内的所有程序单元导入 py_package 模块中,现在就可使用 py_package 前缀调用模块内的程序单元。在与包同名目录下的源代码文件中,示例如下:
# 导入 py_package 包,实际导入的是该包下的 __init__.py 文件 import py_package # 接下来直接使用 py_package 前缀即可调用它所包包含的模块内的程序单元 py_package.print_blank_triangle(5) im = py_package.Item(5.5) print(im) py_package.print_multiple_chart(5)
运行这段代码,输出的结果与前面一样。修改包下的 __init__.py 文件使其导入包下的所有模块,在当前的程序中就可以直接调用模块内的程序单元。
二、 查看模块内容
模块被导入后,模块中包含有哪些变量、函数、类等功能呢?这些信息是模块中各成员的帮助信息,对这些信息有所了解,才能正常的使用模块。
1、模块包含什么
要查看模块包含什么,有两种方法:
(1)、使用 dir() 函数;
(2)、使用模块本身提供的 __all__ 变量。
dir() 函数可返回模块或类所包含的全部程序单元(包括变量、函数、类和方法等),直接使用 dir() 函数时会列出模块内所有的程序单元,包括以下划线开头的程序单元,这些以下划线开头的程序单元有些不希望被外界使用。
比如要查看 string 模块的功能,在 Python 命令行下使用 dir() 函数来查看,示例如下:
import string dir(string) # 输出信息如下: ['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']
从输出信息可以看到,有大量以下划线开头的程序单元,这些程序单元是不希望被外界使用的。要过滤掉这些以下划线开头的程序单元,可使用列表推导式来达成:
[s for s in dir(string) if not s.startswith("_")] 输出信息如下: ['Formatter', 'Template', 'ascii_letters', 'ascii_lowercase', 'ascii_uppercase', 'capwords', 'digits', 'hexdigits', 'octdigits', 'printable', 'punctuation', 'whitespace']
除了使用列表推导式来过滤掉以下划线开头的程序单元外,还可以使用模块中的 __all__ 变量,该变量相当于该模块的开放的功能接口,所以可通过该模块的 __all__ 变量来查看模块内的程序单元。例如在命令行下查看 string 模块提供的程序单元:
string.__all__ # 输出信息如下 ['ascii_letters', 'ascii_lowercase', 'ascii_uppercase', 'capwords', 'digits', 'hexdigits', 'octdigits', 'printable', 'punctuation', 'whitespace', 'Formatter', 'Template']
这里用 __all__ 变量列出的结果与前面用列表推导式列出的结果是相同的,这说明使用这两种方式都可以查看到模块所包含的程序单元。要注意的是,有的模块没有提供 __all__ 变量,查看没有提供 __all__ 变量模块的程序单元时,还是只能使用列表推导式。
2、 使用 __doc__ 属性查看文档
help() 函数可以查看程序单元的帮助信息,是因为该程序单元本身有文档信息,也就是有 __doc__ 属性。所以,使用 help() 函数查看的就是程序单元的 __doc__ 属性值。例如在 Python 交互式解释器中导入 string 模块后查看 capwords() 函数的作用:
help(string.capwords) # 输出信息如下: Help on function capwords in module string: capwords(s, sep=None) capwords(s [,sep]) -> string Split the argument into words using split , capitalize each word using capitalize, and join the capitalized words using join. If the optional second argument sep is absent or None, runs of whitespace characters are replaced by a single space and leading and trailing whitespace are removed, otherwise sep is used to split and join the words.
从输出信息可知,capwords() 函数作用是给指定的 s 字符串中每个单词的首字母变成大写。sep 参数可以指定单词之间的分隔符,默认以空格作为单词分隔符。接下来测试 string.capwords() 函数的用法:
string.capwords('hello world') # 输出如下 'Hello World' string.capwords('hello;world', sep=';') # 输出如下: 'Hello;World'
前面使用 help() 函数查看程序单元的帮助信息,此外,使用程序单元的 __doc__ 属性也可查看其帮助信息,示例如下:
print(string.capwords.__doc__) # 输出如下 capwords(s [,sep]) -> string Split the argument into words using split, capitalize each word using capitalize, and join the capitalized words using join. If the optional second argument sep is absent or None, runs of whitespace characters are replaced by a single space and leading and trailing whitespace are removed, otherwise sep is used to split and join the words.
对比 help(string.capwords)和 print(string.capwords.__doc__) 两个命令的输出结果,可以发现它们的输出结果是一样的,这也说明了使用 help() 函数查看的就是程序单元的 __doc__ 属性值。
在模块中尽量为每个程序单元提供详细的文档信息,这样可使用 hlep() 函数查看该程序单元的文档信息。但实际情况是有些程序单元的文档信息并不详细,此时可查看 Python 库的参考文档(https://docs.python.org/3/library/index.html)。
3、 使用 __file__ 属性查看模块源文件路径
要提升 Python 的编程能力,多阅读优秀的框架、库的源代码都是好的学习方法。要查看模块的源文件路径,可通过模块的 __file__ 属性来查看。例如查看 string 模块的路径:
string.__file__ # 输出如下: 'C:\\ProgramData\\Anaconda3\\lib\\string.py'
根据输出的路径信息,可以找到该模块并查看该模块的全部源代码。Python 中有的模块不是用 Python 语言编写的,有些与底层交互的模块可能是用 C 语言编写的,而且是 C 程序编译后的结果,所以这种模块可能没有 __file__ 属性。
三、 小结
1、模块既是使用 Python 进行模块化编程的重要方式,也是扩展 Python 功能的重要手段。
2、大量第三方模块和库极大地丰富了 Python 语言能力,形成了 Python 强大的生态圈。
3、掌握模块导入的两种方式和导入模块的本质;理解模块自定义语法,包括为模块编写说明文档和测试代码。
4、理解包和模块的区别与联系,掌握使用包管理模块的方式。
5、模块内容的查看和使用,包括查看模块所包含的程序单元,查看这些程序单元的帮助信息,以及查看模块源文件的存储路径。
练习:
1、定义一个 geometry 模块,在该模块下定义 print_triangle(n) 和 print_diamand(n) 两个函数,分别用于在控制台用星号打印三角形和菱形,并为模块和函数提供说明文档。
geometry 模块的代码如下:
1 """ 2 打印三角形和菱形的gemotry模块 3 print_triangle(n)函数用星号打印三角形 4 print_diammand(n)函数用星号打印菱形 5 """ 6 7 def print_triangle(n): 8 """用星号打印三角形的函数 9 参数 n 控制三角形的高度 10 """ 11 for i in range(n): 12 # 打印空格 13 for j in range(0, n - i): 14 print(end=" ") 15 # 打印星号并换行 16 for k in range(2 * i + 1): 17 print("*", end="") 18 print() 19 20 21 def print_diamand(n): 22 """ 23 用星号打印菱形的函数 24 参数 n 控制菱形的高度,且必须是奇数 25 """ 26 if n % 2 == 0: 27 raise ValueError("参数 n 必须是奇数") 28 half_line = n // 2 + 1 29 # 打印上半部分 30 for i in range(half_line): 31 print(" " * (half_line - i), end="") 32 print("*" * (2 * i + 1)) 33 # 打印下半部分 34 for j in range(half_line - 1): 35 print(" " * (j + 2), end="") 36 print("*" * (n - 2 - j * 2))
下是代码是在与 geometry 模块同目录下的源文件中,导入模块并调用模块内的函数:
from geometry import * print_triangle(5) print_diamand(7)
2、 定义一个 py_class 模块,在该模块下定义 Teacher、Student 和 Computer 三个类,并为模块和类提供说明文档。
1 """ 2 这是一个 py_class 模块,该模块下有三个类,分别是 3 Teacher:代表老师的类 4 Student:代表学生的类 5 Computer:代表计算机的类 6 """ 7 8 class Teacher: 9 """定义代表老师的类""" 10 def teach(self, content): 11 print("老师正在教授%s" % content) 12 13 14 class Student: 15 """定义代表学生的类""" 16 def learn(self, content): 17 print("学生正在学习%s" % content) 18 19 20 class Computer: 21 """定义代表计算机的类""" 22 def run(self, program): 23 print("计算机正在运行%s" % program)
3、 定义一个 py_package 包,在该包下提供 foo 和 bar 两个模块,在每一个模块下又包含任意两个函数。
py_package 包下包含的文件有:__init__.py、foo.py、bar.py
foo.py 文件的代码如下所示:
def foo_hello1(): print("foo 模块内的第一个 hello 函数") def foo_hello2(): print("foo 模块内的第二个 hello 函数")
bar.py 文件的代码如下所示:
def bar_hello1(): print("bar 模块内的第一个 hello 函数") def bar_hello2(): print("bar 模块内的第二个 hello 函数")
__init__.py 文件的代码如下所示:
# 下面两种导入方式选用一种 # import foo from .foo import * # import bar from .bar import *