抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

前面介绍递归函数的时候用到了sys模块,介绍文件操作函数的时候用到了os模块,之前只是简单说了这两个模块下部分函数的用法,这里详细介绍一下对于模块、包和库的概念,以及一些常见的模块用法。

不需要记住每个模块下所有函数用法,但是平常看到python文件导入模块操作的时候,要大概知道这几个模块有什么作用。

1. 概念

1.1 模块(module)

函数可以理解为完成特定功能的一段程序,类是包含一组数据及操作这些数据或传递消息的函数的集合,而模块(module)是在函数和类的基础上,将一系列相关代码组织到一起的集合体

在python中,扩展名为.py的源程序文件就是一个模块,这个和C语言的头文件以及JAVA的包是类似的。

python官方网站上可以查看当前标准库中的所有模块,点击这里

1.2 包(package)

为了方便调用将一些功能相近的模块组织在一起,或是将一个较为复杂的模块拆分为多个组成部分,可以将 .py 源程序文件放在同一个文件夹下,按照 Python 的规则进行管理,这样的文件夹和其中的文件就称为包(package)

包的目录下需要创建__init__.py 模块,可以是一个空文件,可以写一些初始化代码,其作用就是告诉 Python 要将该目录当成包来处理,让python认为你这是一个包而不是单纯的一个目录(否则会显示找不到包)。有的博客说python3.3版本之后不需要空的__init__.py 模块来声明这是一个包了,但是我在vscode和jupyter运行python3.10的时候发现还是需要__init__.py 模块声明的,这里先存疑,我保留自己的观点。

  • 2022.12.3更新:准确来说,从包里导入模块需要__init__.py 声明;直接导入同目录下的模块不需要(3.3版本以后)

简单来说,包就是有层次地文件目录结构,里面装着各种扩展名.py的python源程序文件,包中也可以含有包。

1.3 库

库顾名思义则是功能相关联的包的集合。python的三大特色之一:强大的标准库,第三方库以及自定义模块。

2. 常用模块/库

python的三大特色对应三种类型的模块,标准库的内置模块,第三方库开源模块和自定义的模块,这里简单记录一下常用的模块/库。

模块名称 介绍
内置模块
os 普遍的操作系统功能接口,包括前面介绍的文件操作函数
sys 提供了一系列有关Python运行环境的变量和函数,sys.path.append()
random 生成随机数,random() 返回0<n<=1
time 各种提供日期、时间功能的类和函数,time.time() 时间戳
datetime 对time模块的一个高级封装
logging 日志打印到了标准输出中
re 可以直接调用来实现正则匹配,re.split() 分割字符串,格式化列表
pymysql 连接数据库,并实现简单的增删改查
threading 提供了更强大的多线程管理方案
json 用于字符串和数据类型间进行转换json
subprocess 像linux一样创建运行子进程
shutil 对压缩包的处理、对文件和文件夹的高级处理,os的补充
tkinter Python的标准Tk GUI工具包的接口
第三方模块/库
Requsests python最有名的第三方HTTP客户端库
Scrapy 屏幕抓取和web抓取框架,编写爬虫用到(上面的也可以)
Pillow 常用的图像处理库
Matplotlib 绘制二维数据图的库,使用方式对标matlab
NumPy 提供大型矩阵计算公式,在很多领域都用到
Pandas 基于Numpy 和 Matplotlib,和上面两个组成数据分析三剑客
Django 开源的web开发框架
PyTorch 开源的深度学习框架,各种张量操作、梯度计算,方便构建各种动态神经网络
TensorFlow 也是机器学习库,张量的操作和运算,tensorboard可视化数据很强大

第三方库实在太多,这里只列举了我知道的比较常见的库;内置模块可以见1.1章节的官网链接,里面有所有内置模块的具体用法。接下来说说怎么导入模块和制作模块。

3. 导入包和模块

3.1 导入模块

制作模块要注意,自定义的模块名不能和系统内置的模块重名,否则被重名的系统模块无法被导入。

python中用关键字import引入某个模块,在调用模块中的函数时,需要以 模块名.函数名 的方式进行引用。自定义模块名中的函数是可以重名的,因为模块名不会相同(同一层目录下文件名不同),调用的时候可以进行区分,这很好理解。

3.2 导入包

有的时候我们只需要包里的某个模块或者模块里的某个函数,而不需要包或者模块里的全部内容,这个时候我们可以用关键词 from 包名/模块名 import 模块名/函数名 来进行调用。

举个例子,在如下的文件结构中,main.py作为主程序入口,test文件夹相当于一个包,里面有4个.py后缀的模块,分别定义了四则运算的函数,__init__.py 是个空文件(暂时不做处理),声明test文件夹是个python包而不是普通的目录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# add.py文件内容——定义加法运算
def add(a, b):
return a + b

# sub.py文件内容——定义减法运算
def sub(a, b):
return a - b

# mul.py文件内容——定义乘法运算
def mul(a, b):
return a * b

# dev.py文件内容——定义除法运算
def dev(a, b):
return a / b

我现在要做的是,在main.py文件里,导入test包里四个模块,调用各自模块中对应的函数,有以下几种调用方式:

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
# main.py的文件内容
import test.add # 导入test包下的add模块
import test.sub as sb # 导入test包下的sub模块,并重命名为sb
from test import mul # 从test包中导入mul模块
from test.dev import dev # 从test包的dev模块导入dev函数,注意这里导入的是函数

def calculate(x, y, operate):
result = 0
if operate == '+':
result = test.add.add(x, y) # 调用test.add模块中的add函数
elif operate == '-':
result = sb.sub(x, y) # test.sub被重命名为sb,调用sb中的sub函数
elif operate == '*':
result = mul.mul(x, y) # 调用mul模块中的mul函数
else:
result = dev(x, y) # dev函数已经被导入,可以直接调用函数名
return result

print(calculate(100, 100, '+'))
print(calculate(100, 100, '-'))
print(calculate(100, 100, '*'))
print(calculate(100, 100, '/'))

'''
运行结果:
200
0
10000
1.0
'''

4. 包和模块导入的思考

4.1 __init__.py的作用

在上面的例子中__init__.py 是个空文件,是声明test文件夹是python包所必须的(主程序和包的位置在同一个目录下)。然而我们在编写main.py的主程序文件的时候,仍然要在开头导入相当多的模块,比较繁琐,这个时候可以在__init__.py中批量导入我们所需要的模块(导入包其实就是导入__init__.py文件)。

1
2
3
4
5
6
7
8
9
10
# 在__init__.py中添加如下内容
import test.add
import test.sub
import test.mul
import test.dev

add = test.add.add
sub = test.sub.sub
mul = test.mul.mul
dev = test.dev.dev
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
# main.py相应的改为如下内容
from test import * # 导入包相当于执行包下的__init__.py,这个文件已经将包里的四个模块分别导入了

def calculate(x, y, operate):
result = 0
if operate == '+':
result = add(x, y)
elif operate == '-':
result = sub(x, y)
elif operate == '*':
result = mul(x, y)
else:
result = dev(x, y)
return result

print(calculate(100, 100, '+'))
print(calculate(100, 100, '-'))
print(calculate(100, 100, '*'))
print(calculate(100, 100, '/'))

'''
运行结果:
200
0
10000
1.0
'''

可以看到上面的主程序代码量少了很多,起到简化代码的作用。

4.2 if __name__ == ‘__main__‘

首先来看一个现象,如果在add.py文件中不仅仅有定义函数的代码,还有编写代码时做的测试内容,如下:

1
2
3
4
5
# add.py文件中最后一行对这个函数做了测试
def add(a, b):
return a + b

print(add(3, 4))

其他文件全都不变,再次运行main.py,会发现输出结果为:

1
2
3
4
5
7		# add.py中测试内容也被输出
200
0
10000
1.0

这显然不是我们想看到的,我们在导入add模块调用add函数的时候,并不想要其他无关的输出结果。

稍稍改变一下add.py内容

1
2
3
4
5
6
# add.py文件内容
def add(a, b):
return a + b

if __name__ == '__main__':
print(add(3, 4))

此时再次运行main.py则不会输出add.py中的测试内容。

首先要了解一个概念,在每个python文件创建的时候都有一个记录名称的变量__name__,当这个python文件作为脚本直接运行,那么__name__的值为‘”__main__“;当这个文件作为模块被导入其他文件中运行的时候,这个__name__的值为模块的名字,也就是说

  • 当.py文件被直接运行时,if __name__ == ‘__main__‘ 之下的代码块将被运行

  • 当.py文件以模块形式被导入时,if __name__ == ‘__main__‘ 之下的代码块不被运行

在导入的模块中有选择性地执行代码,这在实际开发应用中非常普遍。

4.3 导入模块在主程序的父目录下

前面的导入模块操作,导入模块要么在主程序的子目录下(加入__init__.py 声明这是一个包),要么和主程序在同一个目录(直接import),如果导入模块在主程序的父目录下,应该怎么导入呢?

首先,按照一般流程直接import导入和加入__init__.py声明都会报错找不到这个包,这里就不演示了。

其实这个问题在前面的笔记中有记录,点击这里。 当时是刚用vscode搭建python环境,对python调用一知半解都算不上,现在才有了初步的理解。

1
2
3
4
5
6
7
8
9
10
# 两种解决办法
# 1.在主程序内部临时添加python运行环境路径
import sys
sys.path.append('父目录绝对路径或者相对路径')
import module
# 缺点:只能调用一次(临时加入的环境变量路径),且每个想要导入的自定义模块都要写一次,比较麻烦

# 2.在python安装目录下Libsite-packages中创建扩展名为.pth的文件,添加想要加入的路径。
# python在遍历已知的库文件目录过程中,如果见到一个.pth 文件,就会将文件中所记录的路径加入到sys.path设置中,于是.pth文件指向的地址也就可以被Python运行环境找到了。
# 这个已知的库文件目录可以通过sys.path查看。

4.4 相对导入

前面3.2的例子中,包和模块的导入都是用的绝对导入(absolute import),导入时写明了工作环境中包的具体位置。

还有一种导入方式称为相对导入(relative import),还是用3.2的例子理解一下,在如下的文件结构中,主程序入口main.py和test包在同一层目录下,test包中有__init__.py(空文件),add.py和dev.py两个模块的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
# add.py内容
def add(a, b):
return a + b

# dev.py内容
from .add import add # 相对导入,从当前导入包的目录中找到add模块
def dev(a, b):
return a / b

def func1(a, b):
a = dev(a, b) + add(a, b)
return a

上面的例子意思是,我现在要在test包的dev.py模块中用add.py模块的函数方法(同一个包中的模块相互引用,这在实际工程中很常见)。如果dev.py是主程序,我们可以直接import add;但是我们这里main是主程序,代码如下:

1
2
3
4
# main.py内容
from test import dev

print(dev.func1(1, 2))

主程序main.py功能是导入test包dev模块,打印dev模块的函数func1(1, 2)执行结果。

如果我们在dev.py中直接导入from add import add(没有点,也就是不加当前目录),这个时候再运行main.py会报错找不到模块(因为main.py同目录下没有add.py模块)。这个时候就有两种导入方式,要么完善包名字,使用绝对导入from test.add import add;要么使用相对导入from .add import add

这个相对导入就像是linux的文件操作方式,一个点代表当前目录,两个点代表父目录,还能用三个点表示linux无法做到的祖父目录,依此类推。

相对导入的优点就一目了然:就算改变了包的名字,这个时候调用也不会出错,也就是简化代码,方便迁移。

欢迎小伙伴们留言评论~