Python入门(二十)- 模块和包

二十、模块和包

前面章节中,我们已经使用了很多模块(如 string、sys、os 等),通过向程序中导入这些模块,我们可以使用很多“现成”的函数实现想要的功能。

那么,模块到底是什么,模块内部到底是什么样子的,模块可以自定义吗?本章将带领读者详细了解 Python 中的模块,诸如此类疑问,本章都会一一给大家解惑。

20.1 模块化编程

Python 提供了强大的模块支持,主要体现在,不仅 Python 标准库中包含了大量的模块(称为标准模块),还有大量的第三方模块,开发者自己也可以开发自定义模块。通过这些强大的模块可以极大地提高开发者的开发效率。

那么,模块到底指的是什么呢?模块,英文为 Modules,至于模块到底是什么,可以用一句话总结:模块就是 Python 程序。换句话说,任何 Python 程序都可以作为模块,包括在前面章节中写的所有 Python 程序,都可以作为模块。

模块可以比作一盒积木,通过它可以拼出多种主题的玩具,这与前面介绍的函数不同,一个函数仅相当于一块积木,而一个模块(.py 文件)中可以包含多个函数,也就是很多积木。模块和函数的关系如图 1 所示。
在这里插入图片描述

经过前面的学习,读者已经能够将 Python 代码写到一个文件中,但随着程序功能的复杂,程序体积会不断变大,为了便于维护,通常会将其分为多个文件(模块),这样不仅可以提高代码的可维护性,还可以提高代码的可重用性。
代码的可重用性体现在,当编写好一个模块后,只要编程过程中需要用到该模块中的某个功能(由变量、函数、类实现),无需做重复性的编写工作,直接在程序中导入该模块即可使用该功能。

前面讲了封装,并且还介绍了很多具有封装特性的结构,比如说:
诸多容器,例如列表、元组、字符串、字典等,它们都是对数据的封装;
函数是对 Python 代码的封装;
类是对方法和属性的封装,也可以说是对函数和数据的封装。
本节所介绍的模块,可以理解为是对代码更高级的封装,即把能够实现某一特定功能的代码编写在同一个 .py 文件中,并将其作为一个独立的模块,这样既可以方便其它程序或脚本导入并使用,同时还能有效避免函数名和变量名发生冲突。

举个简单的例子,在某一目录下(桌面也可以)创建一个名为 hello.py 文件,其包含的代码如下:

def say ():
    print("Hello,World!")
在同一目录下,再创建一个 say.py 文件,其包含的代码如下:
#通过 import 关键字,将 hello.py 模块引入此文件
import hello
hello.say()
运行 say.py 文件,其输出结果为:
Hello,World!

读者可能注意到,say.py 文件中使用了原本在 hello.py 文件中才有的 say() 函数,相对于 say.py 来说,hello.py 就是一个自定义的模块(有关自定义模块,后续章节会做详细讲解),我们只需要将 hellp.py 模块导入到 say.py 文件中,就可以直接在 say.py 文件中使用模块中的资源。

与此同时,当调用模块中的 say() 函数时,使用的语法格式为“模块名.函数”,这是因为,相对于 say.py 文件,hello.py 文件中的代码自成一个命名空间,因此在调用其他模块中的函数时,需要明确指明函数的出处,否则 Python 解释器将会报错。

20.2 使用模块

20.2.1 导入模块

使用 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 *,但此方式不推荐使用,具体原因本节后续会做详细说明。

下面程序使用导入整个模块的最简单语法来导入指定模块:

# 导入sys整个模块
import sys
# 使用sys模块名作为前缀来访问模块中的成员
print(sys.argv[0])

上面第 2 行代码使用最简单的方式导入了 sys 模块,因此在程序中使用 sys 模块内的成员时,必须添加模块名作为前缀。

运行上面程序,可以看到如下输出结果(sys 模块下的 argv 变量用于获取运行 Python 程序的命令行参数,其中 argv[0] 用于获取当前 Python 程序的存储路径):
C:\Users\mengma\Desktop\hello.py

导入整个模块时,也可以为模块指定别名。例如如下程序:

# 导入sys整个模块,并指定别名为s
import sys as s
# 使用s模块别名作为前缀来访问模块中的成员
print(s.argv[0])

第 2 行代码在导入 sys 模块时才指定了别名 s,因此在程序中使用 sys 模块内的成员时,必须添加模块别名 s 作为前缀。运行该程序,可以看到如下输出结果:
C:\Users\mengma\Desktop\hello.py
也可以一次导入多个模块,多个模块之间用逗号隔开。例如如下程序:

# 导入sys、os两个模块
import sys,os
# 使用模块名作为前缀来访问模块中的成员
print(sys.argv[0])
# os模块的sep变量代表平台上的路径分隔符
print(os.sep)

上面第 2 行代码一次导入了 sys 和 os 两个模块,因此程序要使用 sys、os 两个模块内的成员,只要分别使用 sys、os 模块名作为前缀即可。在 Windows 平台上运行该程序,可以看到如下输出结果(os 模块的 sep 变量代表平台上的路径分隔符):
C:\Users\mengma\Desktop\hello.py
在导入多个模块的同时,也可以为模块指定别名,例如如下程序:

# 导入sys、os两个模块,并为sys指定别名s,为os指定别名o
import sys as s,os as o
# 使用模块别名作为前缀来访问模块中的成员
print(s.argv[0])
print(o.sep)

上面第 2 行代码一次导入了sys 和 os 两个模块,并分别为它们指定别名为 s、o,因此程序可以通过 s、o 两个前缀来使用 sys、os 两个模块内的成员。在 Windows 平台上运行该程序,可以看到如下输出结果:
C:\Users\mengma\Desktop\hello.py

下面程序使用了 from…import 最简单的语法来导入指定成员:

# 导入sys模块的argv成员
from sys import argv
# 使用导入成员的语法,直接使用成员名访问
print(argv[0])

第 2 行代码导入了 sys 模块中的 argv 成员,这样即可在程序中直接使用 argv 成员,无须使用任何前缀。运行该程序,可以看到如下输出结果:
C:\Users\mengma\Desktop\hello.py

导入模块成员时,也可以为成员指定别名,例如如下程序:

# 导入sys模块的argv成员,并为其指定别名v
from sys import argv as v
# 使用导入成员(并指定别名)的语法,直接使用成员的别名访问
print(v[0])

第 2 行代码导入了 sys 模块中的 argv 成员,并为该成员指定别名 v,这样即可在程序中通过别名 v 使用 argv 成员,无须使用任何前缀。运行该程序,可以看到如下输出结果:
C:\Users\mengma\Desktop\hello.py

form…import 导入模块成员时,支持一次导入多个成员,例如如下程序:

# 导入sys模块的argv,winver成员
from sys import argv, winver
# 使用导入成员的语法,直接使用成员名访问
print(argv[0])
print(winver)

上面第 2 行代码导入了 sys 模块中的 argv、 winver 成员,这样即可在程序中直接使用 argv、winver 两个成员,无须使用任何前缀。运行该程序,可以看到如下输出结果(sys 的 winver 成员记录了该 Python 的版本号):
C:\Users\mengma\Desktop\hello.py
3.6

一次导入多个模块成员时,也可指定别名,同样使用 as 关键字为成员指定别名,例如如下程序:

# 导入sys模块的argv,winver成员,并为其指定别名v、wv
from sys import argv as v, winver as wv
# 使用导入成员(并指定别名)的语法,直接使用成员的别名访问
print(v[0])
print(wv)

上面第 2 行代码导入了 sys 模块中的 argv、winver 成员,并分别为它们指定了别名 v、wv,这样即可在程序中通过 v 和 wv 两个别名使用 argv、winver 成员,无须使用任何前缀。运行该程序,可以看到如下输出结果:
C:\Users\mengma\Desktop\hello.py
3.6

不推荐使用 from import 导入模块所有成员
在使用 from…import 语法时,可以一次导入指定模块内的所有成员(此方式不推荐),例如如下程序:

#导入sys 棋块内的所有成员
from sys import *
#使用导入成员的语法,直接使用成员的别名访问
print(argv[0])
print(winver)

上面代码一次导入了 sys 模块中的所有成员,这样程序即可通过成员名来使用该模块内的所有成员。该程序的输出结果和前面程序的输出结果完全相同。

需要说明的是,一般不推荐使用“from 模块 import”这种语法导入指定模块内的所有成员,因为它存在潜在的风险。比如同时导入 module1 和 module2 内的所有成员,假如这两个模块内都有一个 foo() 函数,那么当在程序中执行如下代码时:
foo()

上面调用的这个 foo() 函数到底是 module1 模块中的还是 module2 模块中的?因此,这种导入指定模块内所有成员的用法是有风险的。

但如果换成如下两种导入方式:
import module1
import module2 as m2

接下来要分别调用这两个模块中的 foo() 函数就非常清晰。程序可使用如下代码:

#使用模块module1 的模块名作为前缀调用foo()函数
module1.foo()
#使用module2 的模块别名作为前缀调用foo()函数
m2.foo()
或者使用 from...import 语句也是可以的:
#导入module1 中的foo 成员,并指定其别名为foo1
from module1 import foo as fool
#导入module2 中的foo 成员,并指定其别名为foo2
from module2 import foo as foo2

此时通过别名将 module1 和 module2 两个模块中的 foo 函数很好地进行了区分,接下来分别调用两个模块中 foo() 函数就很清晰:

foo1() #调用module1 中的foo()函数
foo2() #调用module2 中的foo()函数

20.2.2 自定义模块

到目前为止,读者已经掌握了导入 Python 标准库并使用其成员(主要是函数)的方法,接下来要解决的问题是,怎样自定义一个模块呢?

前面章节中讲过,Python 模块就是 Python 程序,换句话说,只要是 Python 程序,都可以作为模块导入。例如,下面定义了一个简单的模块(编写在 demo.py 文件中):

name = "Python教程"
add = "https://editor.csdn.net/md?not_checkout=1&articleId=125641897"
print(name,add)
def say():
    print("人生苦短,我学Python!")
class CLanguage:
    def __init__(self,name,add):
        self.name = name
        self.add = add
    def say(self):
        print(self.name,self.add)

可以看到,我们在 demo.py 文件中放置了变量(name 和 add)、函数( say() )以及一个 Clanguage 类,该文件就可以作为一个模板。
一个模块通常包含3部分内容:测试代码、模块说明、执行代码。

  1. 编写测试代码
    但通常情况下,为了检验模板中代码的正确性,我们往往需要为其设计一段测试代码,例如:
say()
clangs = CLanguage("Python入门","https://editor.csdn.net/")
clangs.say()
运行 demo.py 文件,其执行结果为:
Python教程 https://editor.csdn.net/md?not_checkout=1&articleId=125641897
人生苦短,我学Python!
C语言中文 https://editor.csdn.net/

通过观察模板中程序的执行结果可以断定,模板文件中包含的函数以及类,是可以正常工作的。

在此基础上,我们可以新建一个 test.py 文件,并在该文件中使用 demo.py 模板文件,即使用 import 语句导入 demo.py:

import demo

注意,虽然 demo 模板文件的全称为 demo.py,但在使用 import 语句导入时,只需要使用该模板文件的名称即可。

此时,如果直接运行 test.py 文件,其执行结果为:

Python教程 https://editor.csdn.net/md?not_checkout=1&articleId=125641897
人生苦短,我学Python!
C语言中文 https://editor.csdn.net/

可以看到,当执行 test.py 文件时,它同样会执行 demo.py 中用来测试的程序,这显然不是我们想要的效果。正常的效果应该是,只有直接运行模板文件时,测试代码才会被执行;反之,如果是其它程序以引入的方式执行模板文件,则测试代码不应该被执行。

要实现这个效果,可以借助 Python 内置的 name 变量。当直接运行一个模块时,name 变量的值为 main;而将模块被导入其他程序中并运行该程序时,处于模块中的 name 变量的值就变成了模块名。因此,如果希望测试函数只有在直接运行模块文件时才执行(引入模块过程不执行),则可在调用测试函数时增加判断,即只有当 name ==‘main’ 时才调用测试函数。

因此,我们可以修改 demo.py 模板文件中的测试代码为:

if __name__ == '__main__':
    say()
    clangs = CLanguage("Python入门","https://editor.csdn.net/")
    clangs.say()

这样,当我们直接运行 demo.py 模板文件时,其执行结果不变;而运行 test.py 文件时,其执行结果为:

Python教程 https://editor.csdn.net/md?not_checkout=1&articleId=125641897

显然,这里执行的仅是模板文件中的输出语句,测试代码并未执行。

  1. 自定义模块编写说明文档
    我们知道,在定义函数或者类时,可以为其添加说明文档,以方便用户清楚的知道该函数或者类的功能。自定义模块也不例外。

为自定义模块添加说明文档,和函数或类的添加方法相同,即只需在模块开头的位置定义一个字符串即可。例如,为 demo.py 模板文件添加一个说明文档:

'''
demo 模块中包含以下内容:
name 字符串变量:初始值为“Python教程”
add    字符串变量:初始值为“https://editor.csdn.net/md?not_checkout=1&articleId=125641897”
say() 函数
CLanguage类:包含 name 和 add 属性和 say() 方法。
'''

在此基础上,我们可以通过模板的 doc 属性,来访问模板的说明文档。例如,在 test.py 文件中添加如下代码:

import demo
print(demo.__doc__)
程序运行结果为:
Python教程 https://editor.csdn.net/md?not_checkout=1&articleId=125641897

demo 模块中包含以下内容:
name 字符串变量:初始值为“Python教程”
add    字符串变量:初始值为“https://editor.csdn.net/md?not_checkout=1&articleId=125641897”
say() 函数
CLanguage类:包含 name 和 add 属性和 say() 方法。

20.2.3 加载模块

很多初学者经常遇到这样的问题,即自定义 Python 模板后,在其它文件中用 import(或 from…import) 语句引入该文件时,Python 解释器同时如下错误:
ModuleNotFoundError: No module named ‘模块名’

意思是 Python 找不到这个模块名,这是什么原因导致的呢?要想解决这个问题,读者要先搞清楚 Python 解释器查找模块文件的过程。

通常情况下,当使用 import 语句导入模块后,Python 会按照以下顺序查找指定的模块文件:

  • 在当前目录,即当前执行的程序文件所在目录下查找;
  • 到 PYTHONPATH(环境变量)下的每个目录中查找;
  • 到 Python 默认的安装目录下查找。

以上所有涉及到的目录,都保存在标准模块 sys 的 sys.path 变量中,通过此变量我们可以看到指定程序文件支持查找的所有目录。换句话说,如果要导入的模块没有存储在 sys.path 显示的目录中,那么导入该模块并运行程序时,Python 解释器就会抛出 ModuleNotFoundError(未找到模块)异常。

解决“Python找不到指定模块”的方法有 3 种,分别是:

  • 向 sys.path 中临时添加模块文件存储位置的完整路径;
  • 将模块放在 sys.path 变量中已包含的模块加载路径中;
  • 设置 path 系统环境变量。

不过,在详细介绍这 3 种方式之前,为了能更方便地讲解,本节使用前面章节已建立好的 hello.py 自定义模块文件(D:\python_module\hello.py)和 say.py 程序文件(C:\Users\Administrator\Desktop\say.py,位于桌面上),它们各自包含的代码如下:

#hello.py
def say ():
    print("Hello,World!")
#say.py
import hello
hello.say()

显然,hello.py 文件和 say.py 文件并不在同一目录,此时运行 say.py 文件,其运行结果为:

Traceback (most recent call last):
  File "C:\Users\mengma\Desktop\say.py", line 1, in <module>
    import hello
ModuleNotFoundError: No module named 'hello'

可以看到,Python 解释器抛出了 ModuleNotFoundError 异常。接下来,分别用以上 3 种方法解决这个问题。

  1. 临时添加模块完整路径
    模块文件的存储位置,可以临时添加到 sys.path 变量中,即向 sys.path 中添加 D:\python_module(hello.py 所在目录),在 say.py 中的开头位置添加如下代码:
import sys
sys.path.append('D:\\python_module')

注意:在添加完整路径中,路径中的 ‘’ 需要使用 \ 进行转义,否则会导致语法错误。再次运行 say.py 文件,运行结果如下:

Hello,World!

可以看到,程序成功运行。在此基础上,我们在 say.py 文件中输出 sys.path 变量的值,会得到以下结果:

import sys
print(sys.path)
输出结果:
['C:\\Users\\Administrator\\Desktop', 'D:\\python3.6\\Lib\\idlelib', 'D:\\python3.6\\python36.zip', 'D:\\python3.6\\DLLs', 'D:\\python3.6\\lib', 'D:\\python3.6', 'C:\\Users\\Administrato\\AppData\\Roaming\\Python\\Python36\\site-packages', 'D:\\python3.6\\lib\\site-packages', 'D:\\python3.6\\lib\\site-packages\\win32', 'D:\\python3.6\\lib\\site-packages\\win32\\lib', 'D:\\python3.6\\lib\\site-packages\\Pythonwin', 'D:\\python_module']

需要注意的是,通过该方法添加的目录,只能在执行当前文件的窗口中有效,窗口关闭后即失效。

  1. 将模块保存到指定位置

如果要安装某些通用性模块,比如复数功能支持的模块、矩阵计算支持的模块、图形界面支持的模块等,这些都属于对 Python 本身进行扩展的模块,这种模块应该直接安装在 Python 内部,以便被所有程序共享,此时就可借助于 Python 默认的模块加载路径。

Python 程序默认的模块加载路径保存在 sys.path 变量中,因此,我们可以在 say.py 程序文件中先看看 sys.path 中保存的默认加载路径,在方法一中我们已经查看了sys.path的值。

上面的运行结果中,列出的所有路径都是 Python 默认的模块加载路径,但通常来说,我们默认将 Python 的扩展模块添加在 lib\site-packages 路径下,它专门用于存放 Python 的扩展模块和包。

所以,我们可以直接将我们已编写好的 hello.py 文件添加到 lib\site-packages 路径下,就相当于为 Python 扩展了一个 hello 模块,这样任何 Python 程序都可使用该模块。

移动工作完成之后,再次运行 say.py 文件,可以看到成功运行的结果:

Hello,World!
  1. 设置环境变量
    PYTHONPATH 环境变量(简称 path 变量)的值是很多路径组成的集合,Python 解释器会按照 path 包含的路径进行一次搜索,直到找到指定要加载的模块。当然,如果最终依旧没有找到,则 Python 就报 ModuleNotFoundError 异常。

由于不同平台,设置 path 环境变量的设置流程不尽相同,因此接下来就使用最多的 Windows、Linux、Mac OS X 这 3 个平台,给读者介绍如何设置 path 环境变量。

  • 在 Windows 平台上设置环境变量
    首先,找到桌面上的“计算机”(或者我的电脑),并点击鼠标右键,单击“属性”。此时会显示“控制面板\所有控制面板项\系统”窗口,单击该窗口左边栏中的“高级系统设置”菜单,出现“系统属性”对话框,如图 1 所示。
    在这里插入图片描述
    图 1 系统属性对话框

如图 1 所示,点击“环境变量”按钮,此时将弹出图 2 所示的对话框:

在这里插入图片描述

图 2 环境变量对话框

如图 2 所示,通过该对话框,就可以完成 path 环境变量的设置。需要注意的是,该对话框分为上下 2 部分,其中上面的“用户变量”部分用于设置当前用户的环境变量,下面的“系统变量”部分用于设置整个系统的环境变量。

通常情况下,建议大家设置设置用户的 path 变量即可,因为此设置仅对当前登陆系统的用户有效,而如果修改系统的 path 变量,则对所有用户有效。
对于普通用户来说,设置用户 path 变量和系统 path 变量的效果是相同的,但 Python 在使用 path 变量时,会先按照系统 path 变量的路径去查找,然后再按照用户 path 变量的路径去查找。

这里我们选择设置当前用户的 path 变量。单击用户变量中的“新建”按钮, 系统会弹出如图 3 所示的对话框。

在这里插入图片描述

图 3 新建PYTHONPATH环境变量

其中,在“变量名”文本框内输入 PYTHONPATH,表明将要建立名为 PYTHONPATH 的环境变量;在“变量值”文本框内输入 .;d:\python_ module。注意,这里其实包含了两条路径(以分号 ;作为分隔符):
第一条路径为一个点(.),表示当前路径,当运行 Python 程序时,Python 将可以从当前路径加载模块;
第二条路径为 d:\python_ module,当运行 Python 程序时,Python 将可以从 d:\python_ module 中加载模块。

然后点击“确定”,即成功设置 path 环境变量。此时,我们只需要将模块文件移动到和引入该模块的文件相同的目录,或者移动到 d:\python_ module 路径下,该模块就能被成功加载。

  • 在 Linux 上设置环境变量
    启动 Linux 的终端窗口,进入当前用户的 home 路径下,然后在 home 路径下输入如下命令:
ls - a

该命令将列出当前路径下所有的文件,包括隐藏文件。Linux 平台的环境变量是通过 .bash_profile 文件来设置的,使用无格式编辑器打开该文件,在该文件中添加 PYTHONPATH 环境变量。也就是为该文件增加如下一行:

#设置PYTHON PATH 环境变量
PYTHONPATH=.:/home/Administrator/python_module

Linux 与 Windows 平台不一样,多个路径之间以冒号(:)作为分隔符,因此上面一行同样设置了两条路径,点(.)代表当前路径,还有一条路径是 /home/mengma/python_module(mengma 是在 Linux 系统的登录名)。

在完成了 PYTHONPATH 变量值的设置后,在 .bash_profile 文件的最后添加导出 PYTHONPATH 变量的语句。

#导出PYTHONPATH 环境变量
export PYTHONPATH

重新登录 Linux 平台,或者执行如下命令:

source.bash_profile

这两种方式都是为了运行该文件,使在文件中设置的 PYTHONPATH 变量值生效。

在成功设置了上面的环境变量之后,接下来只要把前面定义的模块(Python 程序)放在与当前所运行 Python 程序相同的路径中(或放在 /home/Administrator/python_module 路径下),该模块就能被成功加载了。

  • 在Mac OS X 上设置环境变量
    在 Mac OS X 上设置环境变量与 Linux 大致相同(因为 Mac OS X 本身也是类 UNIX 系统)。启动 Mac OS X 的终端窗口(命令行界面),进入当前用户的 home 路径下,然后在 home 路径下输入如下命令:
ls -a

该命令将列出当前路径下所有的文件,包括隐藏文件。Mac OS X 平台的环境变量也可通过,bash_profile 文件来设置,使用无格式编辑器打开该文件,在该文件中添加 PYTHONPATH 环境变量。也就是为该文件增加如下一行:

#设置PYTHON PATH 环境变盘
PYTHONPATH=.:/Users/Administrator/python_module

Mac OS X 的多个路径之间同样以冒号(:)作为分隔符,因此上面一行同样设置了两条路径:点(.)代表当前路径,还有一条路径是 /Users/Administrator/python_module。
在完成了 PYTHONPATH 变量值的设置后,在 .bash_profile 文件的最后添加导出 PYTHONPATH 变量的语句。

#导出PYTHON PATH 环境变量
export PYTHONPATH

重新登录 Mac OS X 系统,或者执行如下命令:

source.bash_profile

这两种方式都是为了运行该文件,使在文件中设置的 PYTHONPATH 变量值生效。

在成功设置了上面的环境变量之后,接下来只要把前面定义的模块(Python 程序)放在与当前所运行 Python 程序相同的路径中(或放在 Users/Administrator/python_module 路径下),该模块就能被成功加载了。

20.2.4 模块的本质及内容

为了更好的帮助大家理解导入模块,本节将讲解Python模块的本质及包含的内容。

下面定义一个新的模块,该模块比较简单,所有不再为之编写测试程序代码。

# 一个简单的测试模块:testmodule
print("this is a test module")
name = "testmodule"
def hello():
    print("Hello,world")

接下来在相同路径下,编写程序

import testmodule

print("==================="
print (type(testmodule))
print(testmodule)

由于环境变量中包含(.),因此Python总可以加载同目录下的模块。
运行结果:

this is a test module
====================
<class 'module'>
<module 'testmodule' from '存放地址'>
<founction hello at 内存地址>

从上面的输出结果可以看出,当程序导入模块时,模块被执行了一次,然后所有的代码被加载入内存,并建立了一个同名的module对象testmodule。模块中定义的所有程序单元相当于该模块对象的成员。
再一次验证了Python中,万物皆对象。
使用from…import语句导入的模块,道理是相同的

  1. 加载入内存
  2. 执行模块
  3. 导入指定变量,函数等成员(整体引入是导入所有变量、函数)
    注意:部分引入时,如果打印整个模块会报错。
    在导入模块后,可以在模块文件所在目录下看到一个名为“–pycache–"的文件夹(此处的两个横线代表下划线,因为我打不出来连续的_,所以使用-代替,后面的也是),打开该文件夹,可以看到Python为每个模块都生成一个*.cpython-36.pyc文件,本例中生成testmodule.cpython-36.pyc文件,该文件其实时Python为模块编译生成的字节码。用于提升该模块的运行效率。
    我们了解了模块的本质就是对象后,就不难理解模块中都包含什么了。
    Python提供两种凡是查看模块包含什么。
  • 使用dir()函数
  • 使用模块本身提供的–all–变量。
    前面章节我们使用了很多次dir()函数,限制做个回顾
>>> import string
>>> dir(string)
['Formatter', 'Template', '_ChainMap', '__all__', '__builtins__', '__cached__', '__doc__', 
'__file__', '__loader__', '__name__', '__package__', '__spec__', '_re', '_sentinel_dict', 
'_string', 'ascii_letters', 'ascii_lowercase', 'ascii_uppercase', 'capwords', 'digits', 
'hexdigits', 'octdigits', 'printable', 'punctuation', 'whitespace']
>>>

使用–all–变量查看

>>> string.__all__
['ascii_letters', 'ascii_lowercase', 'ascii_uppercase', 'capwords', 'digits', 'hexdigits', 'octdigits', 'printable', 'punctuation', 'whitespace', 'Formatter', 'Template']
>>>

–all–变量看到的是可以共外部使用的函数及变量,以下划线或双下划线开头的是模块不希望我们使用的。具体–all–变量是什么我们将在下节介绍。

20.2.4 模块的特殊属性

事实上,当我们向文件导入某个模块时,导入的是该模块中那些名称不以下划线(单下划线“_”或者双下划线“__”)开头的变量、函数和类。因此,如果我们不想模块文件中的某个成员被引入到其它文件中使用,可以在其名称前添加下划线。
例如,前面的testmodule模块

# 一个简单的测试模块:testmodule
print("this is a test module")
name = "testmodule"
def hello():
    print("Hello,world")
def _disPython():
    print("使用单下划线定义函数,此函数不能被外界引用")
def __disPython():
    print("使用双下划线定义函数,此函数不能被外界引用")

外界引用内部函数会报错。
除此之外,还可以借助模块提供的 --all-- 变量。

20.2.4.1 --all–变量

该变量的值是一个列表,存储的是当前模块中一些成员(变量、函数或者类)的名称。
–all–变量主要有两个作用:

  • 限制外部引用
  • 查看可以使用的模块内容
  1. 通过在模块文件中设置 --all–变量,当其它文件以“from 模块名 import *”的形式导入该模块时,该文件中只能使用 --all-- 列表中指定的成员。
    也就是说,只有以“from 模块名 import *”形式导入的模块,当该模块设有 --all–变量时,只能导入该变量指定的成员,未指定的成员是无法导入的。举个例子:
# 一个简单的测试模块:testmodule
print("this is a test module")
name = "testmodule"
def hello():
    print("Hello,world")
def disPython():
    print("使用单下划线定义函数,此函数不能被外界引用")
__all__=["name","hello"]
# test.py
from testmodule import *
testmodule.hello()
testmodule.disPython()

可见,–all–变量只包含name 和 hello的函数名,不包含 disPython() 函数的名称。此时直接执行 test.py 文件,其执行结果为:

Hello,world
Traceback (most recent call last):
  File "C:/Users/Administrator/Desktop/2.py", line 4, in <module>
    disPython()
NameError: name 'disPython' is not defined

显然,对于 test.py 文件来说,testmodule.py 模块中的 disPython() 函数是未引入,这样调用是非法的。

再次声明,–all-- 变量仅限于在其它文件中以“from 模块名 import *”的方式引入。也就是说,如果使用以下 2 种方式引入模块,则 --all-- 变量的设置是无效的。

  1. 以“import 模块名”的形式导入模块。通过该方式导入模块后,总可以通过模块名前缀(如果为模块指定了别名,则可以使用模快的别名作为前缀)来调用模块内的所有成员(除了以下划线开头命名的成员)。

  2. 以“from 模块名 import 成员”的形式直接导入指定成员。使用此方式导入的模块,all 变量即便设置,也形同虚设。

20.2.4.2 --doc–属性

前面章节中我们使用help()函数来查看程序单元的帮助信息。比如导入string模块后,即可使用help()函数来查看指定程序单元的帮助信息。

help(string)

因为我在单位偷偷的敲笔记,弄了一个简化版的python做试验,帮助信息根本就没有,这里就不做演示了。

使用help()函数之所以能够查看帮助信息,其实就是因为该程序单元本身有文档信息,也就是–doc–属性。也就是help()显示的是–doc–属性值。
下面的代码效果是完全相同的:

print(string.__doc__)
20.2.4.3 --file–属性

通过模块的–file–属性,可以查看指定模块的源文件路径。例如:

string.__file__

输出结果就是string模块源代码的本地存放地址。
需要说明的是,并不是所有的模块都是使用Python语言编写的,有些与底层交互的模块可能是用C语言编写的,而且是C程序编译之后的效果,因此这种模块可能没有–file–属性,或者根本看不懂。

20.3 包

实际开发中,一个大型的项目往往需要使用成百上千的 Python 模块,如果将这些模块都堆放在一起,势必不好管理。而且,使用模块可以有效避免变量名或函数名重名引发的冲突,但是如果模块名重复怎么办呢?因此,Python提出了包(Package)的概念。

什么是包呢?简单理解,包就是文件夹,只不过在该文件夹下必须存在一个名为“–(双下划线)init__.py” 的文件。
注意,这是 Python 2.x 的规定,而在 Python 3.x 中,–init__.py 对包来说,并不是必须的。

每个包的目录下都必须建立一个–init__.py 的模块,可以是一个空模块,可以写一些初始化代码,其作用就是告诉 Python 要将该目录当成包来处理。
注意,–init__.py 不同于其他模块文件,此模块的模块名不是 --init__,而是它所在的包名。例如,在 settings 包中的 --init__.py 文件,其模块名就是 settings。

包是一个包含多个模块的文件夹,它的本质依然是模块,因此包中也可以包含包。例如,在前面章节中,我们安装了 numpy 模块之后可以在 Lib\site-packages 安装目录下找到名为 numpy 的文件夹,它就是安装的 numpy 模块(其实就是一个包),它所包含的内容如图 1 所示。

在这里插入图片描述

图 1 numpy包(模块)

从图 1 可以看出,在 numpy 包(模块)中,有必须包含的 --init__.py 文件,还有 matlib.py 等模块源文件以及 core 等子包(也是模块)。这正印证了我们刚刚讲过的,包的本质依然是模块,包可以包含包。
Python 库:相比模块和包,库是一个更大的概念,例如在 Python 标准库中的每个库都有好多个包,而每个包中都有若干个模块。

20.3.1 创建包

包其实就是文件夹,更确切的说,是一个包含“–init__.py”文件的文件夹。因此,如果我们想手动创建一个包,只需进行以下 2 步操作:
新建一个文件夹,文件夹的名称就是新建包的包名;
在该文件夹中,创建一个 --init__.py 文件(前后各有 2 个下划线‘_’),该文件中可以不编写任何代码。当然,也可以编写一些 Python 初始化代码,则当有其它程序文件导入包时,会自动执行该文件中的代码。

例如,现在我们创建一个非常简单的包,该包的名称为 my_package,可以仿照以上 2 步进行:
创建一个文件夹,其名称设置为 my_package;
在该文件夹中添加一个 --init__.py 文件,此文件中可以不编写任何代码。不过,这里向该文件编写如下代码:

'''
创建第一个 Python 包
'''
print('https://editor.csdn.net/')

可以看到,–init__.py 文件中,包含了 2 部分信息,分别是此包的说明信息和一条 print 输出语句。

由此,我们就成功创建好了一个 Python 包。

创建好包之后,我们就可以向包中添加模块(也可以添加包)。这里给 my_package 包添加 2 个模块,分别是 module1.py、module2.py,各自包含的代码分别如下所示(

#module1.py模块文件
def display(arc):
    print(arc)
#module2.py 模块文件
class CLanguage:
    def display(self):
        print("https://editor.csdn.net/md?not_checkout=1&articleId=125641897")

现在,我们就创建好了一个具有如下文件结构的包:
my_package
┠── --init__.py
┠── module1.py
┗━━ module2.py

当然,包中还有容纳其它的包,不过这里不再演示,有兴趣的读者可以自行调整包的结构。

20.3.2 包的导入

通过前面的学习我们知道,包其实本质上还是模块,因此导入模块的语法同样也适用于导入包。无论导入我们自定义的包,还是导入从他处下载的第三方包,导入方法可归结为以下 3 种:

import 包名[.模块名 [as 别名]]
from 包名 import 模块名 [as 别名]
from 包名.模块名 import 成员名 [as 别名]

用 [] 括起来的部分,是可选部分,即可以使用,也可以直接忽略。
注意,导入包的同时,会在包目录下生成一个含有 --init__.cpython-36.pyc 文件的 --pycache__ 文件夹。

  1. import 包名[.模块名 [as 别名]]
    以前面创建好的 my_package 包为例,导入 module1 模块并使用该模块中成员可以使用如下代码:
import my_package.module1
my_package.module1.display("https://editor.csdn.net/md?not_checkout=1&articleId=125641897")
运行结果为:
https://editor.csdn.net/md?not_checkout=1&articleId=125641897

可以看到,通过此语法格式导入包中的指定模块后,在使用该模块中的成员(变量、函数、类)时,需添加“包名.模块名”为前缀。当然,如果使用 as 给包名.模块名”起一个别名的话,就使用直接使用这个别名作为前缀使用该模块中的方法了,例如:

import my_package.module1 as module
module.display("https://editor.csdn.net/md?not_checkout=1&articleId=125641897")
程序执行结果为:
https://editor.csdn.net/md?not_checkout=1&articleId=125641897

另外,当直接导入指定包时,程序会自动执行该包所对应文件夹下的 --init__.py 文件中的代码。例如:

import my_package
my_package.module1.display("https://editor.csdn.net/md?not_checkout=1&articleId=125641897")

直接导入包名,并不会将包中所有模块全部导入到程序中,它的作用仅仅是导入并执行包下的 --init__.py 文件,因此,运行该程序,在执行 --init__.py 文件中代码的同时,还会抛出 AttributeError 异常(访问的对象不存在):

https://editor.csdn.net/
Traceback (most recent call last):
  File "C:\Users\Administrator\Desktop\demo.py", line 2, in <module>
    my_package.module1.display("https://editor.csdn.net/md?not_checkout=1&articleId=125641897")
AttributeError: module 'my_package' has no attribute 'module1'

我们知道,包的本质就是模块,导入模块时,当前程序中会包含一个和模块名同名且类型为 module 的变量,导入包也是如此:

import my_package
print(my_package)
print(my_package.__doc__)
print(type(my_package))
运行结果为:
https://editor.csdn.net/
<module 'my_package' from 'C:\\Users\\Administrator\\Desktop\\my_package\\__init__.py'>
创建第一个 Python 包
<class 'module'>
  1. from 包名 import 模块名 [as 别名]
    仍以导入 my_package 包中的 module1 模块为例,使用此语法格式的实现代码如下:
from my_package import module1
module1.display("https://editor.csdn.net/md?not_checkout=1&articleId=125641897")
运行结果为:
https://editor.csdn.net/
https://editor.csdn.net/md?not_checkout=1&articleId=125641897

可以看到,使用此语法格式导入包中模块后,在使用其成员时不需要带包名前缀,但需要带模块名前缀。

当然,我们也可以使用 as 为导入的指定模块定义别名,例如:

from my_package import module1 as module
module.display("https://editor.csdn.net/md?not_checkout=1&articleId=125641897")

此程序的输出结果和上面程序完全相同。

同样,既然包也是模块,那么这种语法格式自然也支持 from 包名 import * 这种写法,它和 import 包名 的作用一样,都只是将该包的 --init__.py 文件导入并执行。

  1. from 包名.模块名 import 成员名 [as 别名]
    此语法格式用于向程序中导入“包.模块”中的指定成员(变量、函数或类)。通过该方式导入的变量(函数、类),在使用时可以直接使用变量名(函数名、类名)调用,例如:
from my_package.module1 import display
display("https://editor.csdn.net/md?not_checkout=1&articleId=125641897")
运行结果为:
https://editor.csdn.net/
https://editor.csdn.net/md?not_checkout=1&articleId=125641897

当然,也可以使用 as 为导入的成员起一个别名,例如:

from my_package.module1 import display as dis
dis("https://editor.csdn.net/md?not_checkout=1&articleId=125641897")

该程序的运行结果和上面相同。

另外,在使用此种语法格式加载指定包的指定模块时,可以使用 * 代替成员名,表示加载该模块下的所有成员。例如:

from my_package.module1 import *
display("https://editor.csdn.net/md?not_checkout=1&articleId=125641897")
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值