Python学习笔记(16)-黑马教程-模块与包

模块概念

  • Python 模块(Module),是一个 Python 文件,以 .py 结尾,包含了 Python 对象定义和Python语句;

  • 模块名同样也是一个标识符,需要符合标识符的命名规则;

  • 在模块中定义的全局变量、函数、类都是提供给外界直接使用的工具;

  • 模块就好比工具包,要想使用这个工具包中的工具,就需要先导入这个模块。

模块的导入

模块的导入要用到import命令,如下所示:

1
2
import 模块名1
import 模块名2

当模块导入后,通过模块名.就可以使用模块提供的工具,例如全局变量、函数和类。

模块导入案例

现在我们演示一下如何导入模块,我们现在先同一个文件夹下创建2个模块,分别为hm_01_测试模块1.pyhm_01_测试模块1.py,再新建一个python文件,hm_03_import导入模块.py

这2个模块文件分别如下所示:

hm_01_测试模块1.py代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
# 全局变量
title = "模块1"
# 函数
def say_hello():
print("我是 %s" % title)
# 类
class Dog():
pass

hm_02_测试模块2.py代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
# 全局变量
title = "模块2"
# 函数
def say_hello():
print("我是 %s" % title)
# 类
class Cat():
pass

现在创建导入模块的文件,即hm_03_import导入模块.py文件,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
import hm_01_测试模块1
import hm_02_测试模块2
hm_01_测试模块1.say_hello()
hm_02_测试模块2.say_hello()
dog = hm_01_测试模块1.Dog()
print(dog)
cat = hm_02_测试模块2.Cat()
print(cat)

运行结果如下所示:

1
2
3
4
我是 模块1
我是 模块2
<hm_01_测试模块1.Dog object at 0x0000021F03B475F8>
<hm_02_测试模块2.Cat object at 0x0000021F03B47668>

使用as指定模块的别名

如果一个模块的名字太长,可以使用as指定模块的名称,以方便在代码中的使用,格式如下:

1
import 模块名1 as 模块名

需要注意的是,给模块取别名的时候,要使用大驼峰命名法,还是以前面的案例为例说明一下:

1
2
3
4
5
6
7
8
9
10
11
import hm_01_测试模块1 as DogModule # 将hm_01_测试模块1重命名为DogModule
import hm_02_测试模块2 as CatModule
DogModule.say_hello()
CatModule.say_hello()
dog = DogModule.Dog()
print(dog)
cat = CatModule.Cat()
print(cat)

运行结果如下所示:

1
2
3
4
我是 模块1
我是 模块2
<hm_01_测试模块1.Dog object at 0x0000026D1FFF85F8>
<hm_02_测试模块2.Cat object at 0x0000026D1FFF8668>

需要注意的是,输出的结果中,模块名称还是原来的名称,例如hm_01_测试模块1

导入模块中的部分工具

当我们遇到一种情况,也就是说,只想导入某一模块中的一部分东西,不想全部导入的情况。此时就需要使用from...import这种方式。

前面提到的import 模块名这种方式是一次性把模块中的所有工具全部导入,并且通过模块名/别名来访问。现在提到的这个from...import的使用语法如下所示:

1
2
# 从模块中导入某一工具
from 模块名1 import 工具名

在这种情况下,当我们导入某个模块的部分工具后,不需要通过模块名.部分工具来访问,可以直接使用。

现在还是看前面的案例,如下所示:

1
2
3
4
5
6
from hm_01_测试模块1 import Dog
from hm_02_测试模块2 import say_hello
say_hello()
wangcai = Dog()
print(wangcai)

运行结果如下所示:

1
2
我是 模块2
<hm_01_测试模块1.Dog object at 0x0000026122974F98>

但是,有一种情况需要注意,如果两个模块存在同名的函数最后导入的函数会覆盖先前导入的函数,因此一旦出现这种情况,最好使用as经其中的一个取一个别名,现在看案例:

1
2
3
4
from hm_01_测试模块1 import say_hello
from hm_02_测试模块2 import say_hello
say_hello()

运行结果如下所示:

1
我是 模块2

从结果中我们可以看出来,hm_02_测试模块2中的say_hello函数覆盖了hm_01_测试模块1中的say_hello函数。

现在我们改变一下顺序,先导入hm_02_测试模块2,再导入hm_01_测试模块1,结果如下所示:

1
2
3
4
5
# from hm_01_测试模块1 import say_hello
from hm_02_测试模块2 import say_hello
from hm_01_测试模块1 import say_hello
say_hello()

运行结果如下所示:

1
我是 模块1

从结果中我们可以看出来,hm_01_测试模块1中的say_hello函数覆盖了hm_02_测试模块2中的say_hello函数。

为了避免这种情况,就需要将另外一个函数通过as来指为别名,如下所示:

1
2
3
4
5
6
# from hm_01_测试模块1 import say_hello
from hm_02_测试模块2 import say_hello as module2_say_hello
from hm_01_测试模块1 import say_hello
say_hello()
module2_say_hello()

运行结果如下所示:

1
2
我是 模块1
我是 模块2

这样就避免了同名函数的覆盖问题。

导入模块的所有内容

如果我们使用from...import *时,就可以导入某个模块的所有内容。这与前面的import 模块名的区别就在,import 模块名导入后,需要通过模块名.函数/类才能访问某个函数,而使用from...import *则是直接访问即可,不用加模块名.,如下所示:

1
2
3
4
5
6
7
from hm_01_测试模块1 import *
print(title)
say_hello()
wangcai = Dog()
print(wangcai)

运行结果如下所示:

1
2
3
模块1
我是 模块1
<hm_01_测试模块1.Dog object at 0x000001ACD3C14F98>

但是这种方法不建议使用,因为一旦出现函数重名,就会出问题。

模块的搜索顺序

当Python解释器导入一个模块时,它会遵循一定的规则来搜索相应的导入顺序:

  1. 搜索当前目录指模块名的文件,如果有就直接导入;
  2. 如果没有,再搜索系统目录

在Python中,每一个模块都有一个内置属性__file__可以查看模块的完整路径

当我们在实际开发时,给文件取名时,不要和系统的模块文件重名

来看一个案例,这个代码文件我们命名为hm_08_模块的搜索顺序.py

1
2
3
4
5
6
import random
# 生成一个 0 ~ 10的数字
rand = random.randint(0, 10)
print(rand)

运行结果如下所示:

1
3

当我们运行上面的代码时,Python解释器会加载系统目录中下的random.py模块。如果我们在当前目录下也有一个random.py文件,那么上面的代码就不会运行,此时我们在这个文件的同一个目录下新建一个random.py文件,输入以下内容:

1
print("Hello, python")

现在这个文件夹下的所有文件如下所示:

再次运行前面的那个代码,也就是hm_08_模块的搜索顺序.py文件,就出现了错误,如下所示:

1
2
3
4
5
Hello, python
Traceback (most recent call last):
File "D:\netdisk\bioinfo.notes\Python\黑马教程笔记\模块\hm_08_模块的搜索顺序.py", line 4, in <module>
rand = random.randint(0, 10)
AttributeError: module 'random' has no attribute 'randint'

错误信息显示,random.py这个模块不含有randint函数。这是因为当前目录下已经有了random.py这个文件,Python解释器就不再导入系统目录中的random.py这个模块。

这里还需要注意的是,当我们导入了random.py这个模块时,它的内容Hello, python也被显示了出来,这个后面我们会再次提到,这里先放下。

现在我们来查看一下这个random.py到底是哪个,就用到了__file__这个属性,如下所示:

1
2
3
import random
print(random.__file__)

结果如下所示:

1
2
Hello, python
D:\netdisk\bioinfo.notes\Python\黑马教程笔记\模块\random.py

从结果我们可以看出来,这个random.py文件来源于D:\netdisk\bioinfo.notes\Python\黑马教程笔记\模块\这个目录。

现在我们把当前目录的random.py这个文件删除,再运行代码,如下所示:

1
C:\Anaconda3\lib\random.py

从结果中我们可以发现,现在random.py这个文件来源于C:\Anaconda3\lib\random.py这个目录。

模块的开发原则

一个独立的Python文件就是一个模块,因此每一个我们写的Python文件都应该是可以被导入的。在实际工作中,当我们每开发一个Python时,它都可以被导入,这样就会极大地提高工作效率。

当一个Python被当作模块被导入时,这个Python文件中所有没有任何缩进的代码都会被执行一遍。现在我们来说明一下这句话是什么意思。

现在我们在一个目录中新建一个文件,命名为hm_09_name模块.py,输入以下内容:

1
print("小明开发的模块")

在同一个目录中再新建一个文件,命名为hm_10_name测试导入.py,输入以下内容:

1
2
3
import hm_09_name模块
print("-" * 50)

现在我们运行hm_10_name测试导入.py文件,结果如下所示:

1
2
小明开发的模块
--------------------------------------------------

从结果中我们可以知道以下信息:

  1. hm_10_name测试导入.py文件导入了hm_09_name模块.py这个模块后,hm_09_name模块.py中的代码print("小明开发的模块")就自动执行了;
  2. 根据我们前面提到的内容,也就是“当一个Python被当作模块被导入时,这个Python文件中所有没有任何缩进的代码都会被执行一遍”这句话,因为在hm_09_name模块.py中,print("小明开发的模块")这句话前面是没有空格的,它就被自动执行了,现在我们改变一下代码,如下所示:
1
2
3
4
5
6
def say_hello():
print("你好,我是Say hello函数")
print("小明开发的模块")
say_hello()

再次运行hm_10_name测试导入.py文件,如下所示:

1
2
3
小明开发的模块
你好,我是Say hello函数
--------------------------------------------------

从结果中我们还可以发现,say_hello()这个函数前面也是没有空格的,也被自动执行了。但是,我们想要的最终结果中时,不想让它执行,这就涉及到Python的__name__属性。

__name__属性

__name__是Python的一个内置属性,记录着一个字符串,如果一个Python是被其他文件导入的__name__就是模块名,如果是当前执行的程序__name__就是__main__

现在看一下前面的案例,解释一下上面的这段话是什么意思。

hm_09_name模块.py中输入以下代码:

1
2
3
4
5
6
7
def say_hello():
print("你好,我是Say hello函数")
# 如果直接执行这个模块,那么__name__就是_main__
print(__name__)
print("小明开发的模块")
say_hello()

结果如下所示:

1
2
3
__main__
小明开发的模块
你好,我是Say hello函数

从结果中我们可以发现,hm_09_name模块.py这个模块中的print(__name__)输出的结果是__main__

现在我们执行hm_10_name测试导入.py这个文件,结果如下所示:

1
2
3
4
hm_09_name模块
小明开发的模块
你好,我是Say hello函数
--------------------------------------------------

从结果中我们可以发现,执行hm_10_name测试导入.py这个文件,print(__name__)输出的结果是hm_09_name模块

从上面的结果我们就可以知道了前面那段话的意思:

__name__是Python的一个内置属性,记录着一个字符串,如果一个Python是被其他文件导入的,__name__就是模块名,如果是当前执行的程序__name__就是__main__

那么如果我们想要这样的一个目的:怎么样才能做到,模块测试的代码(也就是print("小明开发的模块")say_hello()这两句代码)只有在执行模块时才会被运行?

那么我们就需要先判断__name__这个属性内容,如果__name__的内容是__main__,就说明我们当前在执行这个模块。因此我们可以在hm_09_name模块.py中添加一个段if语句来判断__name__内容,如下所示:

1
2
3
4
5
6
7
8
def say_hello():
print("你好,我是Say hello函数")
if __name__ == "__main__":
print(__name__)
print("小明开发的模块")
say_hello()

结果运行如下所示:

1
2
3
__main__
小明开发的模块
你好,我是Say hello函数

现在我们再切换到hm_10_name测试导入.py中,代码如下所示:

1
2
3
import hm_09_name模块
print("-" * 50)

代码运行如下所示:

1
--------------------------------------------------

现在hm_10_name测试导入.py就不再运行原来的些内容了。原来的运行结果如下所示:

1
2
3
4
hm_09_name模块
小明开发的模块
你好,我是Say hello函数
--------------------------------------------------

对比一下两者的运行结果,就明白了。这里我们再说一下,如果在网络上查找一些Python原代码文件,有的时候会看到以下这样的格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 导入模块
# 定义全局变量
# 定义类
# 定义函数
# 在代码的最下文
def main():
# ...
pass
# 根据 __name__判断是否执行下方代码
if __name__ == "__main__":
main()

如果出现了这样格式的代码,就说明了这样的代码不仅能够作为一个独立文件进行测试,还可以被当作模块导入其它的文件中。

包的概念

包是一个包含多个模块的特殊目录,在这个目录下有一个特殊的文件,命名为__init__.py,包的命名方式和变量一致,通常是小写字母_xxxx

使用包的好处就是,可以通过import包名这样的方式一次性导入包中所有的模块

案例使用

现在来演示一下包,需求如下:

  1. 新建一个hm_message的包;
  2. 在目录下,新建两个文件send_messagereceive_message
  3. send_message文件中定义一个send函数;
  4. receive_message文件中定义一个receive函数;
  5. 在外部直接导入hm_message包。

第一步:新建一个包目录

在PyCharm中单右键,新建一个Directory,命名为hm_message,如下所示:

新建后的包如下所示:

第二步:在包中新建一个__init__.py文件

包中必须要包含一个__init__.py文件,因此要在包这个目录上单击右键,新建一个python文件,取名为__init__.py,如下所示:

现在我们已经创建了一个包的基本结构。现在删除这个包,看另外一种创建包的方式。那就是右键,新建Python Package,如下所示:

这样的话,PyCharm就能自动创建一个__init__.py文件。

第三步:创建相应的Python文件

由于我们在需求中提到以下内容:

  1. 新建一个hm_message的包;
  2. 在目录下,新建两个文件send_messagereceive_message
  3. send_message文件中定义一个send函数;
  4. receive_message文件中定义一个receive函数;
  5. 在外部直接导入hm_message包。

现在新建send_message.py文件与receive_message.py文件,现在在send_message.py文件中输入以下内容:

1
2
def send(text):
print("正在发达 %s..." % text)

这样我们就在send_message.py文件中就定义了send()这个函数。

现在在receive_message.py文件中输入以下代码:

1
2
def receive():
return "这是来自于 10xxx的 短信"

这样我们就在receive_message.py中创建了receive()这个函数。

第四步:写入__init__.py文件

如果想让上述的包hm_message能够顺利地被其他文件所调用,那么我们还需要更改一下__init__.py文件,输入以下代码:

1
2
from . import send_message
from . import receive_message

这个代码表示在__init__.py中导入send_message.pyreceive_message.py文件。

现在我们新建一个Python文件,命名为hm_11_导入包.py,导入这个包试一下,代码如下所示:

1
2
3
4
5
import hm_message
hm_message.send_message.send("Hello")
txt = hm_message.receive_message.receive()
print(txt)

运行结果如下所示:

1
2
正在发达 Hello...
这是来自于 10xxx的 短信

上述过程就是创建一个包,并且调用的过程,需要注意的就是包(package)中__init__.py文件的书写。

发布模块

如果希望自己开发的模块分享给其他的人,那么就需要按以下步骤操作即可。

制作发布压缩包的步骤

现在我们把前面的那个包hm_message制作成一个可以分享的压缩包。

我们先新建一个New Project,命名为12_发布模块,现在把原来的那个包文件复制到这个目录中去,如下所示:

第一步:制作setup.py文件

新建一个setup.py文件,这个文件的内容非常固定,如下所示:

1
2
3
4
5
6
7
8
9
10
11
from distutils.core import setup
setup(name="hm_message", # 包名
version="1.0", # 版本
description="itheima's 发送和接收消息模块", # 描述信息
long_description="完整的发送和接收消息模块", # 完整描述信息
author="itheima", # 作者
author_email="itheima@itheima.com", # 作者邮箱
url="www.itheima.com", # 主页
py_modules=["hm_message.send_message",
"hm_message.receive_message"])

setup()这个函数接收的参数是多值字典参数,即**attrs。`

setup()中有包的名称,版本,作者等等信息。最后一行是py_modules信息,这是一个列表,列表里面是这个包的不同模块名称。

第二步:构建模块

现在通过命令行进入模块所在的目录,输入python setup.py build,运行后的结果如下所示:

注意:这是在Windows下的cmd命令行运行的,不是在Linux环境下。

1
2
3
4
5
6
7
8
9
D:\netdisk\bioinfo.notes\Python\黑马教程笔记\12_发布模块>python setup.py build
running build
running build_py
creating build
creating build\lib
creating build\lib\hm_message
copying hm_message\__init__.py -> build\lib\hm_message
copying hm_message\send_message.py -> build\lib\hm_message
copying hm_message\receive_message.py -> build\lib\hm_message

输入tree命名,查看文件目录,如下所示:

1
2
3
4
5
6
7
8
9
10
11
D:\netdisk\bioinfo.notes\Python\黑马教程笔记\12_发布模块>tree
Folder PATH listing for volume 新加卷
Volume serial number is C0C6-2E4F
D:.
├───.idea
│ └───dictionaries
├───build
│ └───lib
│ └───hm_message
└───hm_message
└───__pycache__

第三步:生成发布压缩包

执行python setup.py sdist命名,回车,所示信息如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
D:\netdisk\bioinfo.notes\Python\黑马教程笔记\12_发布模块>python setup.py sdist
running sdist
running check
warning: sdist: manifest template 'MANIFEST.in' does not exist (using default file list)
warning: sdist: standard file not found: should have one of README, README.txt
writing manifest file 'MANIFEST'
creating hm_message-1.0
creating hm_message-1.0\hm_message
making hard links in hm_message-1.0...
hard linking setup.py -> hm_message-1.0
hard linking hm_message\__init__.py -> hm_message-1.0\hm_message
hard linking hm_message\receive_message.py -> hm_message-1.0\hm_message
hard linking hm_message\send_message.py -> hm_message-1.0\hm_message
creating dist
Creating tar archive
removing 'hm_message-1.0' (and everything under it)

输入tree命名,再次查看目录信息,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
D:\netdisk\bioinfo.notes\Python\黑马教程笔记\12_发布模块>tree
Folder PATH listing for volume 新加卷
Volume serial number is C0C6-2E4F
D:.
├───.idea
│ └───dictionaries
├───build
│ └───lib
│ └───hm_message
├───dist
└───hm_message
└───__pycache__

可以发现,又出现了一个dist目录,输入tree /f命令查看整个目录下的内容,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
D:\netdisk\bioinfo.notes\Python\黑马教程笔记\12_发布模块>tree /f
Folder PATH listing for volume 新加卷
Volume serial number is 000001E9 C0C6:2E4F
D:.
│ MANIFEST
│ setup.py
├───.idea
│ │ 12_发布模块.iml
│ │ misc.xml
│ │ modules.xml
│ │ workspace.xml
│ │
│ └───dictionaries
20161111.xml
├───build
│ └───lib
│ └───hm_message
│ receive_message.py
│ send_message.py
│ __init__.py
├───dist
│ hm_message-1.0.tar.gz
└───hm_message
│ receive_message.py
│ send_message.py
│ __init__.py
└───__pycache__
receive_message.cpython-36.pyc
send_message.cpython-36.pyc
__init__.cpython-36.pyc

我们可以发现,dist目录下面有一个压缩文件,也就是hm_message-1.0.tar.gz,至此,发布包的任务完成。

安装模块

视频中的安装过程是在Linux环境下的,下面只输入安装包的命令,如下所示:

1
2
3
4
5
cp 12_发布模块/dist/*. # 复制dist目录下所有的压缩文件到当前目录
tar zxvf hm_message-1.0.tar.gz # 解压
# 解压后的文件中有一个PKG-INFO文件,这个文件里面记录了Python包的一些信息,其实就是package-information的缩写
sudo python setup.py install # 安装模块
# 这一步的包会被安装的python的系统目录

卸载模块

进入模块所在的目录,使用rm删除即可。

这个时候,如果要查看这个模块所有的上当,就需要查看一下__file__文件即可。

pip安装第三方模块

第三方椟通常是指由知名的第三方团队开发的并且被程序员广泛使用的Python包/模块。例如pygame就是一套非常成熟的游戏开发模块。

pip是一个通过的Python包管理工作,它提供到了对Python包的查找、下载、安装、卸载等功能。

pip安装和卸载命令如下:

1
2
3
4
5
6
7
8
9
# Ubuntu环境
# 安装python3环境的pip
sudo pip3 install pygame
#卸载pip
sudo pip3 uninstall pygame
# Linux环境安装ipython
sudo apt install ipython3