Topic 12.1 - 模块与包的引用¶
1. 模块与包的区分¶
模块(Module)和包(Package)这两个东西可以统称为“库”(Library),它们都是为了代码的复用而设计的。
这两个概念的区别其实很简单:
-
模块:就是一个以
.py结尾的 Python 源代码文件- 这个我们其实比较熟悉了,因为我们自己也会去创建
.py文件来编写代码,其实我们创建的一个个.py文件就是一个个模块 - 模块中可以定义各种变量、函数、类等功能,这些功能可以带到其他模块中去使用,从而实现代码的复用
- 我们之前引用的其他模块,有些就是以
.py文件形式存在的,当然还有些不是以.py文件编写的,例如math模块就是用 C 语言编写的 - 我们可以在 Python 安装目录中的
lib目录下找到哪些模块是以.py文件形式存在的
- 这个我们其实比较熟悉了,因为我们自己也会去创建
-
包:就是一个包含多个模块,也就是多个
.py文件的文件夹,文件夹中包含一个名为__init__.py的初始化文件- 包当的思想其实就是把多个相关的模块放在一个文件夹中,方便管理和使用
- 当然包文件夹中还可能包括子文件夹,这些子文件夹也可以看作是子包,子包中也可以包含多个模块
-
因此,一句话总结模块和包的关系,那就是:
包 ⊂ 子包 ⊂ 模块 ⊂ 功能
这里我们举例一些常见的模块和包:
- 常用的模块(module)有:
math、random、json、csv - 常用的包(package)有:
numpy、pandas、matplotlib
其实模块和包这两个概念,同学们在后续的学习和工作中,会越来越淡化这二者的区别,因为除非搞专业 Python 开发,否则这两者后续大家就统称为“库”就可以了。
2. 模块的引用¶
(1) 模块引用:使用 import 模块名 的形式¶
我们目前其实已经接触过一些模块了,比如说 math 模块和 random 模块。
我们在使用这些模块时:
- 引用的格式是
import 模块名的形式 - 调用的格式是
模块名.功能名的形式
import math
print(math.sqrt(16))
4.0
import 语句可以将整个模块导入到当前的命名空间中
- 命名空间可以理解为当前的 Python 环境中定义了哪些变量和函数
- 在上述代码中,通过
import语句导入math模块后,math这个名字就会成为当前环境中的一个东西,我们在自己命名变量或函数时就要避免使用math这个名字,否则会发生冲突 - 如果发生命名冲突,并不会报错,而是后定义的会覆盖先定义的(有时候不报错反而问题更大,会让我们忽略问题)
(2) 模块引用:使用 from 与 import 语句引用特定功能¶
还有一种引用的方式是使用 from 与 import 语句来引用模块中的特定功能:
- 引用格式为
from 模块名 import 功能名, - 调用格式为
功能名,注意在这种引用方式下,我们直接使用功能名来调用模块中的功能,而不需要再加上模块名.前缀
from math import sqrt
print(sqrt(16))
4.0
在这段代码中:
- 我们把
sqrt导入到了当前的命名空间中 - 我们自己定义的变量或函数就不能使用
sqrt这个名字,否则就会发生冲突
使用 from 与 import 语句还可以引用模块中的多个功能:
from math import sqrt, exp, pi
# from math import (sqrt, exp, pi) # 也可以使用括号来把所有功能括起来
print(sqrt(16))
print(exp(1))
print(pi)
4.0
2.718281828459045
3.141592653589793
在这段代码中:
- 我们把
sqrt、exp和pi都导入到了当前的命名空间中 - 我们自己定义的变量或函数就不能使用
sqrt、exp和pi这些名字,否则就会发生冲突 - 如果发生命名冲突,后定义的会覆盖先定义的
要注意,如果使用 from 模块名 import 功能名 这种方式导入:
- 模块名并没有被导入到当前命名空间中
- 该模块中的其他功能也没有被导入到当前命名空间中
# 以下代码会报错,请取消注释后尝试运行
# from math import sqrt, exp, pi
# print(math.sqrt(16)) # 这里会报错,因为 math 并没有被导入
# print(log(10)) # 这里会报错,因为 log 并没有被导入
因此,如果我们想使用模块中的其他功能,还是需要再引用:
- 要么再次使用
import语句把整个模块导入,或者再次使用from 模块名 import 功能名的方式把需要的功能都导入 - 所以,我们推荐这两种方式只使用其中的一种,而不是混用,否则会让代码变得混乱,不容易阅读和维护
# 推荐写法1
import math
# 推荐写法2
from math import sqrt, exp, pi
# 不推荐的写法
import math
from math import sqrt, exp, pi
(3) 模块引用:使用 from 与 import 语句引用所有功能¶
我们还可以使用 from 与 import 语句引用模块中的所有功能:
- 引用格式为
from 模块名 import * - 调用格式为
功能名,同样,在这种引用方式下,我们直接使用功能名来调用模块中的功能,而不需要再加上模块名.前缀
我们来尝试引用 math 模块中的所有功能:
from math import *
print(sqrt(16))
print(exp(1))
print(pi)
print(log(10))
4.0
2.718281828459045
3.141592653589793
2.302585092994046
注意:
- 这种方式会把模块中的所有功能都导入到当前的命名空间中,很可能会导致命名冲突
- 除非我们知道模块中所有功能的名字,并且能保证这些名字不会与我们自己定义的变量或函数冲突,否则不建议使用这种方式
我们来看一个命名冲突的例子:
from math import *
def log(x):
return f"这是我学习Python的第{x}天,加油!"
print(log(10)) # 这里会调用我们自己定义的 log 函数,而不是 math 模块中的 log 函数
这是我学习Python的第10天,加油!
在这段代码中:
- 我们使用
from math import *导入了math模块中的所有功能,其中就有一个log函数 - 然后我们又自己定义了一个
log函数 - 由于我们自己定义的
log函数覆盖了math模块中的log函数,所以当我们调用log(10)时,实际上调用的是我们自己定义的函数,而不是math模块中的函数
(4) 模块引用方式的小结¶
目前为止我们讲了以下几种引用模块的方式,我们来做个简单总结:
| 引用方式 | 调用方式 | 导入内容 | 备注 |
|---|---|---|---|
import 模块名 |
模块名.功能名 |
导入整个模块 | 推荐 |
from 模块名 import 功能名 |
功能名 |
只导入指定的功能 | 推荐 |
from 模块名 import 功能名1, 功能名2 |
功能名 |
只导入指定的多个功能 | 推荐 |
from 模块名 import * |
功能名 |
导入模块中的所有功能 | 不推荐,容易导致命名冲突 |
3. 使用 as 关键字起别名¶
在将模块或功能导入到当前命名空间时,我们还可以使用 as 关键字为模块或功能指定一个别名,有如下的具体形式:
import 模块名 as 模块别名:调用时使用模块别名.功能名
import math as m
print(m.sqrt(16))
4.0
from 模块名 import 功能名 as 功能别名:调用时使用功能别名
from math import sqrt as genhao
print(genhao(16))
from math import exp as e_de_cifang
print(e_de_cifang(1))
4.0
2.718281828459045
4. 包的引用¶
(1) 包的层级结构¶
总的来说,包的引用方式和模块的引用方式是类似的:
- 使用
import ...的形式,或者from ... import ...的形式引用 - 使用
as关键字起别名 - 但是由于包当中又包括子包,子包中有包含模块,所以包的引用就稍微复杂一些。
因此,包的引用,其实最重要的就是了解包的层级结构:
- 具体来说,要清楚,我们想要的东西,是包,子包,模块,还是模块中的功能
- 然后根据具体情况选择合适的引用方式即可
我们拿 xml 包来举例:
xml包是 Python 内置的一个包,其功能是处理 XML 数据- 我们这里不讲解
xml包的具体功能,只是用它来说明包与模块的区别,因为xml是 Python 包中结构比较清晰的一个例子
完整的 xml 包的目录结构如下:
xml/ ← 包
│
├── __init__.py ← 包初始化
│
├── dom/ ← 子包:DOM 解析
│ ├── __init__.py ← 子包初始化
│ ├── minidom.py ← 模块:DOM 解析接口
│ └── pulldom.py ← 模块:增量式 DOM 解析
│
├── etree/ ← 子包:ElementTree API 实现
│ ├── __init__.py ← 子包初始化
│ └── ElementTree.py ← 模块:ElementTree
│
├── parsers/ ← 子包:解析器底层绑定
│ ├── __init__.py ← 子包初始化
│ └── expat.py ← 模块:Expat 解析接口
│
└── sax/ ← 子包:SAX 解析器
├── __init__.py ← 子包初始化
├── handler.py ← 模块:SAX 处理器
├── saxutils.py ← 模块:SAX 工具函数
├── xmlreader.py ← 模块:XML 读取器
└── expatreader.py ← 模块:Expat 读取器
这里我们可以清晰地看到包与模块的层级关系,例如:
-
minidom模块位于xml包的dom子包中- 如果要想引用这个模块,要
import xml.dom.minidom - 如果要想引用这个模块中的功能,可以使用
from xml.dom.minidom import 功能名的方式
- 如果要想引用这个模块,要
# 以下代码不理解没关系,你只需要知道 xml.dom.minidom 这个模块调用成功即可:
import xml.dom.minidom
doc = xml.dom.minidom.parseString("<root><a>1</a></root>")
print(doc.documentElement.tagName)
root
# 以下代码不理解没关系,你只需要知道 xml.dom.minidom 这个模块调用成功即可:
from xml.dom.minidom import parseString
doc = parseString("<root><b>2</b></root>")
print(doc.documentElement.tagName)
root
-
ElementTree模块位于xml包的etree子包中- 如果要想引用这个模块,要
import xml.etree.ElementTree - 如果要想引用这个模块中的功能,可以使用
from xml.etree.ElementTree import 功能名的方式
- 如果要想引用这个模块,要
# 以下代码不理解没关系,你只需要知道 xml.etree.ElementTree 这个模块调用成功即可:
import xml.etree.ElementTree as ET
root = ET.fromstring("<root><a>1</a></root>")
print(root.tag)
root
# 以下代码不理解没关系,你只需要知道 xml.etree.ElementTree 这个模块调用成功即可:
from xml.etree.ElementTree import fromstring
root = fromstring("<root><b>2</b></root>")
print(root.tag)
root
(2) 包的引用的注意事项¶
之前我们介绍过包和模块的关系,就是:包 ⊂ 子包 ⊂ 模块 ⊂ 功能。
这里有一个需要注意的点,就是使用 import a.b.c 这种形式引用时,最多只能具体到模块这个层级,不能具体到功能这个层级。
# 以下代码都不会报错,import 可以具体到包、子包、模块这三个层级
import xml # 包
import xml.etree # 子包
import xml.etree.ElementTree # 模块
# 以下代码会报错
# import xml.etree.ElementTree.fromstring # 不能具体到功能层级
# 更常见的错误是以下这种,math 中的 log 也是具体到的功能层级
# import math.log