Python 复习笔记
python陆续的学了好几次,大学时便开始学,都是学点皮面之后没有坚持来,基本语法,正则,Socket,爬虫都接触过。做java比较多,平常基本不用,所以一直没有一篇系统的笔记来供自己温习,这里整理一篇较基础的,供自己温习。原来资源是小伙伴分享的学习笔记,这里自己稍作整理汇总。生活加油
写在前面
python陆续的学了好几次,大学时便开始学,都是学点皮面之后没有坚持来,基本语法,正则,Socket,爬虫都接触过。做java比较多,平常基本不用,所以一直没有一篇系统的笔记来供自己温习,这里整理一篇较基础的,供自己温习。原来资源是小伙伴分享的学习笔记,这里自己稍作整理汇总。生活加油
Python 基础入门
- Google开源机器学习框架:TensorFlow
- 开源社区主推学习框架:Scikit-learn
- 百度开源深度学习框架:Paddle
Python发展历史:
Python版本
- Python 2.X
- Python 3.X
- Python 3.5
- Python 3.6
- Python 3.7
Python解释器的作用
Python解释器作用:运行文件
Python解释器种类
CPython
,C语言开发的解释器[官方],应用广泛的解释器。IPython
,基于CPython的一种交互式解释器。其他解释器
PyPy
,基于Python语言开发的解释器。Jython
,运行在Java平台的解释器,直接把Python代码编译成Java字节码执行。IronPython
,运行在微软.Net平台上的Python解释器,可以直接把Python代码编译成.Net的字节码。
linxu上编码
1 | [root@liruilong ~]# python3 |
PyCharm的作用
PyCharm是一种Python ==IDE==(集成开发环境),带有一整套可以帮助用户在使用Python语言开发时==提高其效率的工具==,内部集成的功能如下:
PythonCharm分为专业版(professional)和社区版(community)
PyCharm基本使用
新建项目:打开PyCharm – [Create New Project] – 选择项目根目录和解释器版本 – [Create],即可完成新建一个项目。
新建文件并书写代码:项目根目录或根目录内部任意位置 — 右键 – [New] – [Python File] – 输入文件名 – [OK]
如果是将来要上传到服务器的文件,那么文件名切记不能用中文。
双击打开文件,并书写一个最简单的Python代码:
1 | print("hello world") |
- 运行文件:文件打开状态 – 空白位置 — 右键 – Run – 即可调出Pycharm的控制台输出程序结果。
注释的分类及语法
注释分为两类:==单行注释== 和 ==多行注释==。快捷键: ==ctrl + /== ,解释器不执行任何的注释内容。
单行注释
:只能注释一行内容,语法如下:1
2
3
4# 注释内容
# 输出hello world
print('hello world')
print('hello Python') # 输出(简单的说明可以放到一行代码的后面,一般习惯代码后面添加两个空格再书写注释文字)多行注释
:可以注释多行内容,一般用在注释一段代码的情况, 语法如下: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"""
第一行注释
第二行注释
第三行注释
"""
"""
下面三行都是输出的作用,输出内容分别是:
hello Python
hello itcast
hello itheima
"""
print('hello Python')
print('hello itcast')
print('hello itheima')
'''
注释1
注释2
注释3
'''
'''
下面三行都是输出的作用,输出内容分别是:
hello Python
hello itcast
hello itheima
'''
print('hello Python')
print('hello itcast')
print('hello itheima')
变量
举例体验:我们去图书馆读书,怎么样快速找到自己想要的书籍呢?是不是管理员提前将书放到固定位置,并把这个位置进行了编号,我们只需要在图书馆中按照这个编号查找指定的位置就能找到想要的书籍。这个编号其实就是把书籍存放的书架位置起了一个名字,方便后期查找和使用。
程序中,数据都是临时存储在内存中,为了更快速的查找或使用这个数据,通常我们把这个数据在内存中存储之后定义一个名称,这个名称就是变量。
变量就是一个存储数据的的时候当前数据所在的内存地址的名字而已。
定义变量
1 | 变量名 = 值 |
变量名自定义,要满足==标识符==命名规则。
标识符
标识符命名规则是Python中定义各种名字的时候的统一规范,具体如下:
- 由数字、字母、下划线组成
- 不能数字开头
- 不能使用内置关键字
- 严格区分大小写
1 | False None True and as assert break class |
命名习惯
- 见名知义。
- 大驼峰:即每个单词首字母都大写,例如:
MyName
。 - 小驼峰:第二个(含)以后的单词首字母大写,例如:
myName
。 - 下划线:例如:
my_name
。
使用变量
1 | my_name = 'TOM' |
认识bug
所谓bug,就是程序中的错误。如果程序有错误,需要程序员排查问题,纠正错误。
Debug工具
Debug工具是PyCharm IDE中集成的用来调试程序的工具,在这里程序员可以查看程序的执行细节和流程或者调解bug。Debug工具使用步骤:
- 打断点
- Debug调试
打断点
- 断点位置:目标要调试的代码块的第一行代码即可,即一个断点即可。
- 打断点的方法:单击目标代码的行号右侧空白位置。
Debug调试
打成功断点后,在文件内部任意位置 — 右键 – Debug’文件名’ — 即可调出Debug工具面板 – 单击Step Over/F8,即可按步执行代码。
Debug输出面板分类
- Debugger:显示变量和变量的细节
- Console:输出内容
认识数据类型
在 Python 里为了应对不同的业务需求,也把数据分为不同的类型。
检测数据类型的方法:
type()
1 | a = 1 |
输出
作用:程序输出内容给用户
1
2
3
4print('hello Python')
age = 18
print(age)
## 需求:输出“今年我的年龄是18岁”
格式化输出
所谓的格式化输出即按照一定的格式输出内容。
格式化符号
格式符号 | 转换 |
---|---|
==%s== | 字符串 |
==%d== | 有符号的十进制整数 |
==%f== | 浮点数 |
%c | 字符 |
%u | 无符号十进制整数 |
%o | 八进制整数 |
%x | 十六进制整数(小写ox) |
%X | 十六进制整数(大写OX) |
%e | 科学计数法(小写’e’) |
%E | 科学计数法(大写’E’) |
%g | %f和%e的简写 |
%G | %f和%E的简写 |
技巧
%06d,表示输出的整数显示位数,不足以0补全,超出当前位数则原样输出
%.2f,表示小数点后显示的小数位数。
格式化字符串除了%s
,还可以写为f'{表达式}'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16age = 18
name = 'TOM'
weight = 75.5
student_id = 1
## 我的名字是TOM
print('我的名字是%s' % name)
## 我的学号是0001
print('我的学号是%4d' % student_id)
## 我的体重是75.50公斤
print('我的体重是%.2f公斤' % weight)
## 我的名字是TOM,今年18岁了
print('我的名字是%s,今年%d岁了' % (name, age))
## 我的名字是TOM,明年19岁了
print('我的名字是%s,明年%d岁了' % (name, age + 1))
## 我的名字是TOM,明年19岁了
print(f'我的名字是{name}, 明年{age + 1}岁了')
f-格式化字符串是Python3.6中新增的格式化方法
,该方法更简单易读。
转义字符
\n
:换行。\t
:制表符,一个tab键(4个空格)的距离。
结束符
1 | print('输出的内容', end="\n") |
在Python中,print(), 默认自带
end="\n"
这个换行结束符,所以导致每两个
输入
在Python中,程序接收用户输入的数据的功能即是输入。
输入的语法
1 | input("提示信息") |
输入的特点
- 当程序执行到
input
,等待用户输入,输入完成之后才继续向下执行。 - 在Python中,
input
接收用户输入后,一般存储到变量,方便使用。 - 在Python中,
input
会把接收到的任意用户输入的数据都当做字符串处理。1
2
3
4password = input('请输入您的密码:')
print(f'您输入的密码是{password}')
## <class 'str'>
print(type(password))
转换数据类型的作用
问:input()接收用户输入的数据都是字符串类型,如果用户输入1,想得到整型该如何操作?
答:转换数据类型即可,即将字符串类型转换成整型。
转换数据类型的函数
函数 | 说明 |
---|---|
==int(x [,base ])== | 将x转换为一个整数 |
==float(x )== | 将x转换为一个浮点数 |
complex(real [,imag ]) | 创建一个复数,real为实部,imag为虚部 |
==str(x )== | 将对象 x 转换为字符串 |
repr(x ) | 将对象 x 转换为表达式字符串 |
==eval(str )== | 用来计算在字符串中的有效Python表达式,并返回一个对象 |
==tuple(s )== | 将序列 s 转换为一个元组 |
==list(s )== | 将序列 s 转换为一个列表 |
chr(x ) | 将一个整数转换为一个Unicode字符 |
ord(x ) | 将一个字符转换为它的ASCII整数值 |
hex(x ) | 将一个整数转换为一个十六进制字符串 |
oct(x ) | 将一个整数转换为一个八进制字符串 |
bin(x ) | 将一个整数转换为一个二进制字符串 |
快速体验
需求:input接收用户输入,用户输入“1”,将这个数据1转换成整型。
1
2
3
4
5
6
7
8 ## 1. 接收用户输入
num = input('请输入您的幸运数字:')
## 2. 打印结果
print(f"您的幸运数字是{num}")
## 3. 检测接收到的用户输入的数据类型 -- str类型
print(type(num))
## 4. 转换数据类型为整型 -- int类型
print(type(int(num)))
实例
1 | ## 1. `float() -- 转换成浮点型` |
运算符的分类
- 算数运算符
- 赋值运算符
- 复合赋值运算符
- 比较运算符
- 逻辑运算符
算数运算符
运算符 | 描述 | 实例 |
---|---|---|
+ | 加 | 1 + 1 输出结果为 2 |
- | 减 | 1-1 输出结果为 0 |
* | 乘 | 2 * 2 输出结果为 4 |
/ | 除 | 10 / 2 输出结果为 5 |
// | 整除 | 9 // 4 输出结果为2 |
% | 取余 | 9 % 4 输出结果为 1 |
** | 指数 | 2 ** 4 输出结果为 16,即 2 * 2 * 2 * 2 |
() | 小括号 | 小括号用来提高运算优先级,即 (1 + 2) * 3 输出结果为 9 |
注意:- 混合运算优先级顺序:
()
高于**
高于*
/
//
%
高于+
-
赋值运算符
运算符 | 描述 | 实例 |
---|---|---|
= | 赋值 | 将= 右侧的结果赋值给等号左侧的变量 |
单个变量赋值
1
2num = 1
print(num)多个变量赋值
1
2
3
4num1, float1, str1 = 10, 0.5, 'hello world'
print(num1)
print(float1)
print(str1)多变量赋相同值
1
2
3a = b = 10
print(a)
print(b)
复合赋值运算符
运算符 | 描述 | 实例 | ||
---|---|---|---|---|
+= | 加法赋值运算符 | c += a 等价于 c = c + a | ||
-= | 减法赋值运算符 | c -= a 等价于 c = c- a | ||
*= | 乘法赋值运算符 | c *= a 等价于 c = c * a | ||
/= | 除法赋值运算符 | c /= a 等价于 c = c / a | ||
//= | 整除赋值运算符 | c //= a 等价于 c = c // a | ||
%= | 取余赋值运算符 | c %= a 等价于 c = c % a | ||
**= | 幂赋值运算符 | c ** = a 等价于 c = c ** a | ||
|
比较运算符
比较运算符也叫关系运算符, 通常用来判断。
运算符 | 描述 | 实例 | ||
---|---|---|---|---|
== | 判断相等。如果两个操作数的结果相等,则条件结果为真(True),否则条件结果为假(False) | 如a=3,b=3,则(a == b) 为 True | ||
!= | 不等于 。如果两个操作数的结果不相等,则条件为真(True),否则条件结果为假(False) | 如a=3,b=3,则(a == b) 为 True如a=1,b=3,则(a != b) 为 True | ||
> | 运算符左侧操作数结果是否大于右侧操作数结果,如果大于,则条件为真,否则为假 | 如a=7,b=3,则(a > b) 为 True | ||
< | 运算符左侧操作数结果是否小于右侧操作数结果,如果小于,则条件为真,否则为假 | 如a=7,b=3,则(a < b) 为 False | ||
>= | 运算符左侧操作数结果是否大于等于右侧操作数结果,如果大于,则条件为真,否则为假 | 如a=7,b=3,则(a < b) 为 False如a=3,b=3,则(a >= b) 为 True | ||
<= | 运算符左侧操作数结果是否小于等于右侧操作数结果,如果小于,则条件为真,否则为假 | 如a=3,b=3,则(a <= b) 为 True | ||
|
逻辑运算符
运算符 | 逻辑表达式 | 描述 | 实例 | ||
---|---|---|---|---|---|
and | x and y | 布尔”与”:如果 x 为 False,x and y 返回 False,否则它返回 y 的值。 | True and False, 返回 False。 | ||
or | x or y | 布尔”或”:如果 x 是 True,它返回 True,否则它返回 y 的值。 | False or True, 返回 True。 | ||
not | not x | 布尔”非”:如果 x 为 True,返回 False 。如果 x 为 False,它返回 True。 | not True 返回 False, not False 返回 True | ||
|
拓展
数字之间的逻辑运算
1
2
3
4
5
6
7
8
9
10
11
12
13
14a = 0
b = 1
c = 2
## and运算符,二进制位只要有一个值为0,则结果为0,否则结果为最后一个非0数字
print(a and b) # 0
print(b and a) # 0
print(a and c) # 0
print(c and a) # 0
print(b and c) # 2
print(c and b) # 1
## or运算符,只有所有值为0结果才为0,否则结果为第一个非0数字
print(a or b) # 1
print(a or c) # 2
print(b or c) # 1
条件语句
if 语法
语法
1 | if 条件: |
1 | if True: |
实例:上网
需求分析:如果用户年龄大于等于18岁,即成年,输出”已经成年,可以上网”。
1
2
3
4
5age = 20
if age >= 18:
print('已经成年,可以上网')
print('系统关闭')
新增需求:用户可以输出自己的年龄,然后系统进行判断是否成年,成年则输出”您的年龄是’用户输入的年龄’,已经成年,可以上网”。
1
2
3
4
5
6 ## input接受用户输入的数据是字符串类型,条件是age和整型18做判断,所以这里要int转换数据类型
age = int(input('请输入您的年龄:'))
if age >= 18:
print(f'您的年龄是{age},已经成年,可以上网')
print('系统关闭')
if…else…
作用:条件成立执行if下方的代码; 条件不成立执行else下方的代码。
思考:网吧上网的实例,如果成年,允许上网,如果不成年呢?是不是应该回复用户不能上网?
语法
1 | if 条件: |
实用版:网吧上网
1 | age = int(input('请输入您的年龄:')) |
注意:如果条件成立执行了某些代码,那么其他的情况的代码将不会执行。
多重判断
思考:中国合法工作年龄为18-60岁,即如果年龄小于18的情况为童工,不合法;如果年龄在18-60岁之间为合法工龄;大于60岁为法定退休年龄。
1 | if 条件1: |
多重判断也可以和else配合使用。一般else放到整个if语句的最后,表示以上条件都不成立的时候执行的代码。
实例:工龄判断
1 | age = int(input('请输入您的年龄:')) |
拓展:
age >= 18 and age <= 60
可以化简为18 <= age <= 60
。
if嵌套
思考:坐公交:如果有钱可以上车,没钱不能上车;上车后如果有空座,则可以坐下;如果没空座,就要站着。怎么书写程序?
语法
1 | if 条件1: |
注意:条件2的if也是处于条件1成立执行的代码的缩进关系内部。
实例:坐公交
判断是否能上车
1 | """ |
判断是否能坐下
1 | """ |
应用:猜拳游戏
需求分析:
- 参与游戏的角色
- 玩家:手动出拳
- 电脑:随机出拳
- 判断输赢
- 玩家获胜
- 平局 : 玩家出拳 和 电脑出拳相同
- 电脑获胜
随机做法:
- 导出random模块
- random.randint(开始,结束)
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"""
提示:0-石头,1-剪刀,2-布
1. 出拳
玩家输入出拳
电脑随机出拳
2. 判断输赢
玩家获胜
平局
电脑获胜
"""
## 导入random模块
import random
## 计算电脑出拳的随机数字
computer = random.randint(0, 2)
print(computer)
player = int(input('请出拳:0-石头,1-剪刀,2-布:'))
## 玩家胜利 p0:c1 或 p1:c2 或 p2:c0
if (player == 0 and computer == 1) or (player == 1 and computer == 2) or (player == 2 and computer == 0):
print('玩家获胜')
## 平局:玩家 == 电脑
elif player == computer:
print('平局')
else:
print('电脑获胜')
三目运算符
三目运算符也叫三元运算符。语法如下:
1
值1 if 条件 else 值2
快速体验:
1
2
3
4
5a = 1
b = 2
c = a if a > b else b
print(c)
循环简介
循环的作用
思考:假如我有个女朋友,有一天我们闹矛盾生气了,女朋友说:道歉,说100遍“媳妇儿,我错了”。这个时候程序员会怎么做?
答:100遍
print('媳妇儿,我错了')
思考:复制粘贴100次吗?答:重复执行100次一样的代码,程序中循环即可
循环的作用:让代码更高效的重复执行。
循环的分类
在Python中,循环分为while
和for
两种,最终实现效果相同。
while的语法
1 | while 条件: |
需求:复现重复执行100次print('媳妇儿,我错了')
(输出更简洁一些,我们这里设置5次)。
分析:初始值是0次,终点是5次,重复做的事情输出“媳妇儿, 我错了”。
1
2
3
4
5
6 ## 循环的计数器
i = 0
while i < 5:
print('媳妇儿,我错了')
i += 1
print('任务结束')
while的应用
应用一:计算1-100累加和
分析:1-100的累加和,即1 + 2 + 3 + 4 +….,即前两个数字的相加结果 + 下一个数字( 前一个数字 + 1)。
1
2
3
4
5
6
7i = 1
result = 0
while i <= 100:
result += i
i += 1
## 输出5050
print(result)
注意:为了验证程序的准确性,可以先改小数值,验证结果正确后,再改成1-100做累加。
应用二:计算1-100偶数累加和
分析:1-100的偶数和,即 2 + 4 + 6 + 8….,得到偶数的方法如下:
- 偶数即是和2取余结果为0的数字,可以加入条件语句判断是否为偶数,为偶数则累加
- 初始值为0 / 2 , 计数器每次累加2
方法一:条件判断和2取余数则累加
1 | ## 方法一:条件判断和2取余数为0则累加计算 |
方法二:计数器控制
1 | ## 方法二:计数器控制增量为2 |
break和continue
break和continue是循环中满足一定条件退出循环的两种不同方式。
理解
举例:一共吃5个苹果,吃完第一个,吃第二个…,这里”吃苹果”的动作是不是重复执行?
情况一:如果吃的过程中,吃完第三个吃饱了,则不需要再吃第4个和第五个苹果,即是吃苹果的动作停止,这里就是break控制循环流程,即==终止此循环==。
情况二:如果吃的过程中,吃到第三个吃出一个大虫子…,是不是这个苹果就不吃了,开始吃第四个苹果,这里就是continue控制循环流程,即==退出当前一次循环继而执行下一次循环代码==。
情况一:break
1 | i = 1 |
情况二:continue
1 | i = 1 |
while循环嵌套
应用场景
故事梗概:有天女朋友又生气了,惩罚:说3遍“媳妇儿, 我错了”,这个程序是不是循环即可?但如果女朋友说:还要刷今天晚饭的碗,这个程序怎么书写?
1
2
3while 条件:
print('媳妇儿, 我错了')
print('刷晚饭的碗')
但如果女朋友还是生气,把这套惩罚要连续3天都执行,有如何书写程序?
1
2
3
4while 条件:
while 条件:
print('媳妇儿, 我错了')
print('刷晚饭的碗')
语法
1 | while 条件1: |
总结:所谓while循环嵌套,就是一个while里面嵌套一个while的写法,每个while和之前的基础语法是相同的。
快速体验:复现场景
代码
1 | j = 0 |
理解执行流程
当内部循环执行完成之后,再执行下一次外部循环的条件判断。
while循环嵌套应用
应用一:打印星号(正方形)
需求
1 | ***** |
代码
1 | ## 重复打印5行星星 |
应用二:打印星号(三角形)
需求
1 | * |
代码
分析:==一行输出星星的个数和行号是相等的==,每行:重复打印行号数字个星号,将打印行星号的命令重复执行5次实现打印5行。
1
2
3
4
5
6
7
8
9
10
11
12 ## 重复打印5行星星
## j表示行号
j = 0
while j <= 4:
# 一行星星的打印
i = 0
# i表示每行里面星星的个数,这个数字要和行号相等所以i要和j联动
while i <= j:
print('*', end='')
i += 1
print()
j += 1
九九乘法表
代码
1 | ## 重复打印9行表达式 |
for循环
语法
1 | for 临时变量 in 序列: |
快速体验
1 | str1 = 'itheima' |
break
1 | str1 = 'itheima' |
continue
1 | str1 = 'itheima' |
else
循环可以和else配合使用,else下方缩进的代码指的是==当循环正常结束之后要执行的代码==。
while…else
需求:女朋友生气了,要惩罚:连续说5遍“媳妇儿,我错了”,如果道歉正常完毕女朋友就原谅我了,这个程序怎么写?
1
2
3
4
5i = 1
while i <= 5:
print('媳妇儿,我错了')
i += 1
print('媳妇儿原谅我了...')
思考: 这个print是不是没有循环也能执行?
语法
1 | while 条件: |
示例
1 | i = 1 |
退出循环的方式
需求:女朋友生气,要求道歉5遍:媳妇儿,我错了。道歉到第三遍的时候,媳妇埋怨这一遍说的不真诚,是不是就是要退出循环了?这个退出有两种可能性:
- 更生气,不打算原谅,也不需要道歉了,程序如何书写?
- 只一遍不真诚,可以忍受,继续下一遍道歉,程序如何书写?
1. break
1
2
3
4
5
6
7
8
9i = 1
while i <= 5:
if i == 3:
print('这遍说的不真诚')
break
print('媳妇儿,我错了')
i += 1
else:
print('媳妇原谅我了,真开心,哈哈哈哈')
所谓else指的是循环正常结束之后要执行的代码,即如果是break终止循环的情况,else下方缩进的代码将不执行。
continue
1
2
3
4
5
6
7
8
9
10i = 1
while i <= 5:
if i == 3:
print('这遍说的不真诚')
i += 1
continue
print('媳妇儿,我错了')
i += 1
else:
print('媳妇原谅我了,真开心,哈哈哈哈')
因为continue是退出当前一次循环,继续下一次循环,所以该循环在continue控制下是可以正常结束的,当循环结束后,则执行了else缩进的代码。
for…else
语法
1 | for 临时变量 in 序列: |
所谓else指的是循环正常结束之后要执行的代码,即如果是break终止循环的情况,else下方缩进的代码将不执行。
示例
1 | str1 = 'itheima' |
退出循环的方式
- break终止循环
1
2
3
4
5
6
7
8str1 = 'itheima'
for i in str1:
if i == 'e':
print('遇到e不打印')
break
print(i)
else:
print('循环正常结束之后执行的代码')
没有执行else缩进的代码。
- continue控制循环
1
2
3
4
5
6
7
8str1 = 'itheima'
for i in str1:
if i == 'e':
print('遇到e不打印')
continue
print(i)
else:
print('循环正常结束之后执行的代码')因为continue是退出当前一次循环,继续下一次循环,所以该循环在continue控制下是可以正常结束的,当循环结束后,则执行了else缩进的代码。
数据序列
认识字符串
字符串是 Python 中最常用的数据类型。我们一般使用引号来创建字符串。创建字符串很简单,只要为变量分配一个值即可。
1
2
3
4a = 'hello world'
b = "abcdefg"
print(type(a))
print(type(b))
注意:控制台显示结果为
<class 'str'>
, 即数据类型为str(字符串)。
字符串特征
- 一对引号字符串
1
2name1 = 'Tom'
name2 = "Rose" - 三引号字符串
1
2
3
4
5
6
7name3 = ''' Tom '''
name4 = """ Rose """
a = ''' i am Tom,
nice to meet you! '''
b = """ i am Rose,
nice to meet you! """注意:三引号形式的字符串支持换行。
思考:如果创建一个字符串I'm Tom
?1
2c = "I'm Tom"
d = 'I\'m Tom'
字符串输出
1 | print('hello world') |
字符串输入
在Python中,使用input()
接收用户输入。
- 代码
1
2
3
4
5
6
7name = input('请输入您的名字:')
print(f'您输入的名字是{name}')
print(type(name))
password = input('请输入您的密码:')
print(f'您输入的密码是{password}')
print(type(password))
下标
“下标”
又叫“索引”
,就是编号。比如火车座位号,座位号的作用:按照编号快速找到对应的座位。同理,下标的作用即是通过下标快速找到对应的数据。
快速体验
需求:字符串name = "abcdef"
,取到不同下标对应的数据。
- 代码
1
2
3
4name = "abcdef"
print(name[1])
print(name[0])
print(name[2]) - 输出结果
注意:下标从==0==开始。
切片
切片是指对操作的对象截取其中一部分的操作。字符串、列表、元组都支持切片操作。
语法
1 | 序列[开始位置下标:结束位置下标:步长] |
注意
1. 不包含结束位置下标对应的数据, 正负整数均可;
2. 步长是选取间隔,正负整数均可,默认步长为1。
体验
1 | name = "abcdefg" |
常用操作方法
字符串的常用操作方法有
查找、修改和判断
三大类。
查找
所谓字符串查找方法即是查找子串在字符串中的位置或出现的次数。
- find():检测某个子串是否包含在这个字符串中,如果在返回这个子串开始的位置下标,否则则返回-1。 语法:
1
字符串序列.find(子串, 开始位置下标, 结束位置下标)
注意:开始和结束位置下标可以省略,表示在整个字符串序列中查找。
1
2
3
4
5mystr = "hello world and itcast and itheima and Python"
print(mystr.find('and')) # 12
print(mystr.find('and', 15, 30)) # 23
print(mystr.find('ands')) # -1 - index():检测某个子串是否包含在这个字符串中,如果在返回这个子串开始的位置下标,否则则报异常。语法:
1
字符串序列.index(子串, 开始位置下标, 结束位置下标)
注意:开始和结束位置下标可以省略,表示在整个字符串序列中查找。
1 | mystr = "hello world and itcast and itheima and Python" |
- rfind(): 和find()功能相同,但查找方向为==右侧==开始。
- rindex():和index()功能相同,但查找方向为==右侧==开始。
- count():返回某个子串在字符串中出现的次数, 语法:
1
字符串序列.count(子串, 开始位置下标, 结束位置下标)
注意:开始和结束位置下标可以省略,表示在整个字符串序列中查找。
1
2
3
4
5mystr = "hello world and itcast and itheima and Python"
print(mystr.count('and')) # 3
print(mystr.count('ands')) # 0
print(mystr.count('and', 0, 20)) # 1
修改
所谓修改字符串,指的就是通过函数的形式修改字符串中的数据。
- replace():替换, 语法:
1
字符串序列.replace(旧子串, 新子串, 替换次数)
注意:替换次数如果查出子串出现次数,则替换次数为该子串出现次数。
1
2
3
4
5
6
7mystr = "hello world and itcast and itheima and Python"
## 结果:hello world he itcast he itheima he Python
print(mystr.replace('and', 'he'))
## 结果:hello world he itcast he itheima he Python
print(mystr.replace('and', 'he', 10))
## 结果:hello world and itcast and itheima and Python
print(mystr)注意:数据按照是否能直接修改分为==可变类型==和==不可变类型==两种。
字符串类型的数据修改的时候不能改变原有字符串,属于不能直接修改数据的类型即是不可变类型。
- split():按照指定字符分割字符串。 语法:
1
字符串序列.split(分割字符, num)
注意:num表示的是分割字符出现的次数,即将来返回数据个数为num+1个。
1 | mystr = "hello world and itcast and itheima and Python" |
注意:如果分割字符是原有字符串中的子串,分割后则丢失该子串。
- join():用一个字符或子串合并字符串,即是将多个字符串合并为一个新的字符串。 语法:
1
字符或子串.join(多字符串组成的序列)
1
2
3
4
5
6list1 = ['chuan', 'zhi', 'bo', 'ke']
t1 = ('aa', 'b', 'cc', 'ddd')
## 结果:chuan_zhi_bo_ke
print('_'.join(list1))
## 结果:aa...b...cc...ddd
print('...'.join(t1)) - capitalize():将字符串第一个字符转换成大写。
1
2
3mystr = "hello world and itcast and itheima and Python"
## 结果:Hello world and itcast and itheima and python
print(mystr.capitalize())注意:capitalize()函数转换后,只字符串第一个字符大写,其他的字符全都小写。
- title():将字符串每个单词首字母转换成大写。
1
2
3mystr = "hello world and itcast and itheima and Python"
## 结果:Hello World And Itcast And Itheima And Python
print(mystr.title()) - lower():将字符串中大写转小写。
1
2
3mystr = "hello world and itcast and itheima and Python"
## 结果:hello world and itcast and itheima and python
print(mystr.lower()) - upper():将字符串中小写转大写。
1
2
3mystr = "hello world and itcast and itheima and Python"
## 结果:HELLO WORLD AND ITCAST AND ITHEIMA AND PYTHON
print(mystr.upper()) - lstrip():删除字符串左侧空白字符。
- rstrip():删除字符串右侧空白字符。
- strip():删除字符串两侧空白字符。
1 | >>> print(" 24".lstrip()) |
- ljust():返回一个原字符串左对齐,并使用指定字符(默认空格)填充至对应长度 的新字符串。语法:
- rjust():返回一个原字符串右对齐,并使用指定字符(默认空格)填充至对应长度 的新字符串,语法和ljust()相同。
1
2
3
4
5
6字符串序列.ljust(长度, 填充字符)
print(" 24 ".rjust(50,"0"))
0000000000000000000000000000000000000000000000 24
print(" 24 ".ljust(50,"0"))
24 0000000000000000000000000000000000000000000000 - center():返回一个原字符串居中对齐,并使用指定字符(默认空格)填充至对应长度 的新字符串,语法和ljust()相同。
1
2
3
4print(" 24 ".center(50))
24
print(" 24 ".center(50,"0"))
00000000000000000000000 24 00000000000000000000000
判断
所谓判断即是判断真假,返回的结果是布尔型数据类型:True 或 False。
- startswith():检查字符串是否是以指定子串开头,是则返回 True,否则返回 False。如果设置开始和结束位置下标,则在指定范围内检查。语法:
1
字符串序列.startswith(子串, 开始位置下标, 结束位置下标)
1
2
3
4
5mystr = "hello world and itcast and itheima and Python "
## 结果:True
print(mystr.startswith('hello'))
## 结果False
print(mystr.startswith('hello', 5, 20)) - endswith()::检查字符串是否是以指定子串结尾,是则返回 True,否则返回 False。如果设置开始和结束位置下标,则在指定范围内检查。 语法
1
字符串序列.endswith(子串, 开始位置下标, 结束位置下标)
1
2
3
4
5
6
7mystr = "hello world and itcast and itheima and Python"
## 结果:True
print(mystr.endswith('Python'))
## 结果:False
print(mystr.endswith('python'))
## 结果:False
print(mystr.endswith('Python', 2, 20)) - isalpha():如果字符串
至少有一个字符并且所有字符都是字母
则返回 True, 否则返回 False。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21mystr1 = 'hello'
mystr2 = 'hello12345'
## 结果:True
print(mystr1.isalpha())
## 结果:False
print(mystr2.isalpha())
print("234".isalpha())
False
print("lll".isalpha())
True
print("ty ".isalpha())
False
print("kkkk".isalpha())
True
print(" k".isalpha())
False
print("k".isalpha())
True - isdigit():如果字符串只包含数字则返回 True 否则返回 False。
1
2
3
4
5
6mystr1 = 'aaa12345'
mystr2 = '12345'
## 结果: False
print(mystr1.isdigit())
## 结果:False
print(mystr2.isdigit()) - isalnum():如果字符串至少有一个字符并且所有字符都是字母或数字则返 回 True,否则返回 False。
1
2
3
4
5
6mystr1 = 'aaa12345'
mystr2 = '12345-'
## 结果:True
print(mystr1.isalnum())
## 结果:False
print(mystr2.isalnum()) - isspace():如果字符串中只包含空白,则返回 True,否则返回 False。
1
2
3
4
5
6mystr1 = '1 2 3 4 5'
mystr2 = ' '
## 结果:False
print(mystr1.isspace())
## 结果:True
print(mystr2.isspace())
列表
列表的应用场景
思考:有一个人的姓名(TOM)怎么书写存储程序?答:变量。
思考:如果一个班级100位学生,每个人的姓名都要存储,应该如何书写程序?声明100个变量吗?答:列表即可, 列表一次性可以存储多个数据。
列表的格式
1 | [数据1, 数据2, 数据3, 数据4......] |
列表可以一次性存储多个数据,且可以为不同数据类型。
列表的常用操作
列表的作用是一次性存储多个数据,程序员可以对这些数据进行的操作有:增、删、改、查。
查找
下标
1 | name_list = ['Tom', 'Lily', 'Rose'] |
函数
- index():返回指定数据所在
位置的下标
。 语法1
列表序列.index(数据, 开始位置下标, 结束位置下标)
1 | name_list = ['Tom', 'Lily', 'Rose'] |
注意:如果查找的数据不存在则报错。
- count():统计指定数据在当前列表中出现的次数。
1
2
3name_list = ['Tom', 'Lily', 'Rose']
print(name_list.count('Lily')) ## 1 - len():访问列表长度,即列表中数据的个数。
1
2
3name_list = ['Tom', 'Lily', 'Rose']
print(len(name_list)) ## 3
判断是否存在
- in:判断指定数据在某个列表序列,如果在返回True,否则返回False
1
2
3
4
5name_list = ['Tom', 'Lily', 'Rose']
## 结果:True
print('Lily' in name_list)
## 结果:False
print('Lilys' in name_list) - not in:判断指定数据不在某个列表序列,如果不在返回True,否则返回False
1
2
3
4
5name_list = ['Tom', 'Lily', 'Rose']
## 结果:False
print('Lily' not in name_list)
## 结果:True
print('Lilys' not in name_list)
需求:查找用户输入的名字是否已经存在。
1 | name_list = ['Tom', 'Lily', 'Rose'] |
增加
作用:增加指定数据到列表中。
- append():列表结尾追加数据。语法:
1
列表序列.append(数据)
1 | name_list = ['Tom', 'Lily', 'Rose'] |
列表追加数据的时候,直接在原列表里面追加了指定数据,即修改了原列表,
故列表为可变类型数据。
==如果append()追加的数据是一个序列,则追加整个序列到列表==
1 | name_list = ['Tom', 'Lily', 'Rose'] |
- extend():列表结尾追加数据,如果数据是一个序列,
则将这个序列的数据逐一添加到列表。
. 语法1
列表序列.extend(数据)
1 | name_list = ['Tom', 'Lily', 'Rose'] |
1 | name_list = ['Tom', 'Lily', 'Rose'] |
- insert():指定位置新增数据。. 语法
1
列表序列.insert(位置下标, 数据)
1
2
3
4
5name_list = ['Tom', 'Lily', 'Rose']
name_list.insert(1, 'xiaoming')
## 结果:['Tom', 'xiaoming', 'Lily', 'Rose']
print(name_list)
删除
- del 语法:
1
del 目标
- 删除列表
1
2
3
4name_list = ['Tom', 'Lily', 'Rose']
## 结果:报错提示:name 'name_list' is not defined
del name_list
print(name_list) - 删除指定数据
1
2
3
4
5name_list = ['Tom', 'Lily', 'Rose']
del name_list[0]
## 结果:['Lily', 'Rose']
print(name_list)
- pop():删除指定下标的数据(默认为最后一个),并返回该数据。. 语法
1
列表序列.pop(下标)
1
2
3
4
5
6
7name_list = ['Tom', 'Lily', 'Rose']
del_name = name_list.pop(1)
## 结果:Lily
print(del_name)
## 结果:['Tom', 'Rose']
print(name_list) - remove():移除列表中某个数据的第一个匹配项。 语法
1
列表序列.remove(数据)
1 | name_list = ['Tom', 'Lily', 'Rose'] |
- clear():清空列表
1
2
3
4name_list = ['Tom', 'Lily', 'Rose']
name_list.clear()
print(name_list) ## 结果: []
修改
- 修改指定下标数据
1
2
3
4
5name_list = ['Tom', 'Lily', 'Rose']
name_list[0] = 'aaa'
## 结果:['aaa', 'Lily', 'Rose']
print(name_list) - 逆置:reverse()
1
2
3
4
5num_list = [1, 5, 2, 3, 6, 8]
num_list.reverse()
## 结果:[8, 6, 3, 2, 5, 1]
print(num_list) - 排序:sort() 语法
1
列表序列.sort( key=None, reverse=False)
注意:reverse表示排序规则,reverse = True 降序, reverse = False 升序(默认)
1 | num_list = [1, 5, 2, 3, 6, 8] |
复制
函数:copy()
1 | name_list = ['Tom', 'Lily', 'Rose'] |
列表的循环遍历
需求:依次打印列表中的各个数据。
while
1 | name_list = ['Tom', 'Lily', 'Rose'] |
for
1 | name_list = ['Tom', 'Lily', 'Rose'] |
列表嵌套
所谓列表嵌套指的就是一个列表里面包含了其他的子列表。
应用场景:要存储班级一、二、三三个班级学生姓名,且每个班级的学生姓名在一个列表。
1 | name_list = [['小明', '小红', '小绿'], ['Tom', 'Lily', 'Rose'], ['张三', '李四', '王五']] |
思考: 如何查找到数据”李四”?
1 | ## 第一步:按下标查找到李四所在的列表 |
元组
元组的应用场景
思考:如果想要存储多个数据,但是这些数据是不能修改的数据
,怎么做?答:列表?列表可以一次性存储多个数据,但是列表中的数据允许更改。
1 | num_list = [10, 20, 30] |
==一个元组可以存储多个数据,元组内的数据是不能修改的。==
定义元组
元组特点:定义元组使用==小括号==,且==逗号==隔开各个数据,数据可以是不同的数据类型。
1 | ## 多个数据元组 |
注意:
如果定义的元组只有一个数据,那么这个数据后面也好添加逗号
,否则数据类型为唯一的这个数据的数据类型
1 | t2 = (10,) |
元组的常见操作
元组数据不支持修改,只支持查找,具体如下:
- 按下标查找数据
1
2tuple1 = ('aa', 'bb', 'cc', 'bb')
print(tuple1[0]) ## aa - index():查找某个数据,如果数据存在返回对应的下标,否则报错,语法和列表、字符串的index方法相同。
1
2tuple1 = ('aa', 'bb', 'cc', 'bb')
print(tuple1.index('aa')) ## 0 - count():统计某个数据在当前元组出现的次数。
1
2tuple1 = ('aa', 'bb', 'cc', 'bb')
print(tuple1.count('bb')) ## 2 - len():统计元组中数据的个数。
1
2tuple1 = ('aa', 'bb', 'cc', 'bb')
print(len(tuple1)) ## 4注意:
元组内的直接数据如果修改则立即报错
1
2tuple1 = ('aa', 'bb', 'cc', 'bb')
tuple1[0] = 'aaa'但是如果
元组里面有列表,修改列表里面的数据则是支持的
,故自觉很重要。1
2
3
4
5tuple2 = (10, 20, ['aa', 'bb', 'cc'], 50, 30)
print(tuple2[2]) ## 访问到列表
## 结果:(10, 20, ['aaaaa', 'bb', 'cc'], 50, 30)
tuple2[2][0] = 'aaaaa'
print(tuple2)
字典
字典的应用场景
思考1: 如果有多个数据,例如:’Tom’, ‘男’, 20,如何快速存储?答:列表
1 | list1 = ['Tom', '男', 20] |
思考2:如何查找到数据’Tom’?答:查找到下标为0的数据即可。
1 | list1[0] |
思考3:如果将来数据顺序发生变化,如下所示,还能用list1[0]
访问到数据’Tom’吗?。
1 | list1 = ['男', 20, 'Tom'] |
答:不能,数据’Tom’此时下标为2。
思考4:数据顺序发生变化,每个数据的下标也会随之变化,如何保证数据顺序变化前后能使用同一的标准查找数据呢?
答:字典,字典里面的数据是以==键值对==形式出现,字典数据和数据顺序没有关系,即字典不支持下标,后期无论数据如何变化,只需要按照对应的键的名字查找数据即可。
创建字典的语法
字典特点:
- 符号为==大括号==
- 数据为==键值对==形式出现
- 各个键值对之间用==逗号==隔开
1 | ## 有数据字典 |
注意:一般称冒号前面的为键(key),简称k;冒号后面的为值(value),简称v。
字典常见操作
增
写法:==字典序列[key] = 值==
注意:如果key存在则修改这个key对应的值;如果key不存在则新增此键值对。
1 | dict1 = {'name': 'Tom', 'age': 20, 'gender': '男'} |
注意:字典为可变类型。
删
- del() / del:删除字典或删除字典中指定键值对。
1 | dict1 = {'name': 'Tom', 'age': 20, 'gender': '男'} |
- clear():清空字典
1 | dict1 = {'name': 'Tom', 'age': 20, 'gender': '男'} |
改
写法:==字典序列[key] = 值==
注意:如果key存在则修改这个key对应的值 ;如果key不存在则新增此键值对。
查
- key值查找
1
2
3dict1 = {'name': 'Tom', 'age': 20, 'gender': '男'}
print(dict1['name']) # Tom
print(dict1['id']) # 报错如果当前查找的key存在,则返回对应的值;否则则报错。
- get() 语法
1 | 字典序列.get(key, 默认值) |
注意:如果当前查找的key不存在则返回第二个参数(默认值),如果省略第二个参数,则返回None。
1 | dict1 = {'name': 'Tom', 'age': 20, 'gender': '男'} |
- keys()
1
2dict1 = {'name': 'Tom', 'age': 20, 'gender': '男'}
print(dict1.keys()) # dict_keys(['name', 'age', 'gender']) - values()
1
2dict1 = {'name': 'Tom', 'age': 20, 'gender': '男'}
print(dict1.values()) # dict_values(['Tom', 20, '男']) - items()
1
2dict1 = {'name': 'Tom', 'age': 20, 'gender': '男'}
print(dict1.items()) # dict_items([('name', 'Tom'), ('age', 20), ('gender', '男')])
字典的循环遍历
遍历字典的key
1 | dict1 = {'name': 'Tom', 'age': 20, 'gender': '男'} |
遍历字典的value
1 | dict1 = {'name': 'Tom', 'age': 20, 'gender': '男'} |
遍历字典的元素
1 | dict1 = {'name': 'Tom', 'age': 20, 'gender': '男'} |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GyUf8MGo-1617796856463)(04-字典.assets/image-20190212104046564.png)]
遍历字典的键值
1 | dict1 = {'name': 'Tom', 'age': 20, 'gender': '男'} |
集合
创建集合
创建集合使用{}
或set()
, 但是如果要创建空集合只能使用set()
,因为{}
用来创建空字典。
1 | s1 = {10, 20, 30, 40, 50} |
特点:
集合可以去掉重复数据;·
集合数据是无序的,故不支持下标·
集合常见操作方法
增加数据
- add()
1
2
3
4s1 = {10, 20}
s1.add(100)
s1.add(10)
print(s1) ## {100, 10, 20}因为集合有去重功能,所以,当向集合内追加的数据是当前集合已有数据的话,则不进行任何操作。
- update(),
追加的数据是序列。
1
2
3
4
5s1 = {10, 20}
## s1.update(100) ## 报错
s1.update([100, 200])
s1.update('abc')
print(s1)
删除数据
- remove(),删除集合中的指定数据,
如果数据不存在则报错。
1
2
3
4
5
6
7s1 = {10, 20}
s1.remove(10)
print(s1)
s1.remove(10) ## 报错
print(s1) - discard(),删除集合中的指定数据,
如果数据不存在也不会报错。
1
2
3
4
5
6
7s1 = {10, 20}
s1.discard(10)
print(s1)
s1.discard(10)
print(s1) - pop(),随机删除集合中的某个数据,
并返回这个数据。
1
2
3
4
5s1 = {10, 20, 30, 40, 50}
del_num = s1.pop()
print(del_num)
print(s1)
查找数据
- in:判断数据在集合序列
- not in:判断数据不在集合序列
1
2
3
4s1 = {10, 20, 30, 40, 50}
print(10 in s1)
print(10 not in s1)
公共操作
一. 运算符
运算符 | 描述 | 支持的容器类型 |
---|---|---|
+ | 合并 | 字符串、列表、元组 |
* | 复制 | 字符串、列表、元组 |
in | 元素是否存在 | 字符串、列表、元组、字典 |
not in | 元素是否不存在 | 字符串、列表、元组、字典 |
+
1 | ## 1. 字符串 |
*
1 | ## 1. 字符串 |
in或not in
1 | ## 1. 字符串 |
公共方法
函数 | 描述 |
---|---|
len() | 计算容器中元素个数 |
del 或 del() | 删除 |
max() | 返回容器中元素最大值 |
min() | 返回容器中元素最小值 |
range(start, end, step) | 生成从start到end的数字,步长为 step,供for循环使用 |
enumerate() | 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。 |
len()
1 | ## 1. 字符串 |
del()
1 | ## 1. 字符串 |
max()
1 | ## 1. 字符串 |
min()
1 | ## 1. 字符串 |
range()
1 | ## 1 2 3 4 5 6 7 8 9 |
注意:
range()生成的序列不包含end数字。
enumerate() 语法
1 | enumerate(可遍历对象, start=0) |
注意:start参数用来设置遍历数据的下标的起始值,默认为0。
1 | list1 = ['a', 'b', 'c', 'd', 'e'] |
容器类型转换
tuple()
作用:将某个序列转换成元组
1 | list1 = [10, 20, 30, 40, 50, 20] |
list()
作用:将某个序列转换成列表
1 | t1 = ('a', 'b', 'c', 'd', 'e') |
set()
作用:将某个序列转换成集合
1 | list1 = [10, 20, 30, 40, 50, 20] |
注意:
1. 集合可以快速完成列表去重
2. 集合不支持下标
推导式
列表推导式
作用:用一个表达式创建一个有规律的列表或控制一个有规律列表。列表推导式又叫列表生成式。
需求:创建一个0-10的列表。
- while循环实现
1
2
3
4
5
6
7
8
9## 1. 准备一个空列表
list1 = []
## 2. 书写循环,依次追加数字到空列表list1中
i = 0
while i < 10:
list1.append(i)
i += 1
print(list1) - for循环实现
1
2
3
4list1 = []
for i in range(10):
list1.append(i)
print(list1) 列表推导式实现
1
2list1 = [i for i in range(10)]
print(list1)
带if的列表推导式
需求:创建0-10的偶数列表
- 方法一:range()步长实现
1
2list1 = [i for i in range(0, 10, 2)]
print(list1) - 方法二:if实现
1
2list1 = [i for i in range(10) if i % 2 == 0]
print(list1)
多个for循环实现列表推导式
需求:创建列表如下:
1 | [(1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)] |
- 代码如下:
1
2list1 = [(i, j) for i in range(1, 3) for j in range(3)]
print(list1)
字典推导式
思考:如果有如下两个列表:
1 | list1 = ['name', 'age', 'gender'] |
如何快速合并为一个字典?答:字典推导式
字典推导式作用:快速合并列表为字典或提取字典中目标数据。
创建一个字典:字典key是1-5数字,value是这个数字的2次方。
1
2dict1 = {i: i**2 for i in range(1, 5)}
print(dict1) ## {1: 1, 2: 4, 3: 9, 4: 16}将两个列表合并为一个字典
1
2
3
4
5list1 = ['name', 'age', 'gender']
list2 = ['Tom', 20, 'man']
dict1 = {list1[i]: list2[i] for i in range(len(list1))}
print(dict1)提取字典中目标数据
1
2
3
4counts = {'MBP': 268, 'HP': 125, 'DELL': 201, 'Lenovo': 199, 'acer': 99}
## 需求:提取上述电脑数量大于等于200的字典数据
count1 = {key: value for key, value in counts.items() if value >= 200}
print(count1) ## {'MBP': 268, 'DELL': 201}
集合推导式
需求:创建一个集合,数据为下方列表的2次方。
1 | list1 = [1, 1, 2] |
代码如下:
1 | list1 = [1, 1, 2] |
注意:集合有数据去重功能。
函数
函数的作用
需求:用户到ATM机取钱:
- 输入密码后显示”选择功能”界面
- 查询余额后显示”选择功能”界面
- 取2000钱后显示”选择功能”界面
特点:显示“选择功能”界面需要重复输出给用户,怎么实现?
函数就是将==一段具有独立功能的代码块== 整合到一个整体并命名,在需要的位置==调用这个名称==即可完成对应的需求。
函数在开发过程中,可以更高效的实现==代码重用==。
函数的使用步骤
定义函数
1 | def 函数名(参数): |
调用函数
1 | 函数名(参数) |
注意:
1. 不同的需求,参数可有可无。
2. 在Python中,函数必须==先定义后使用==。
需求:复现ATM取钱功能。
搭建整体框架(复现需求)
1
2
3
4
5
6
7
8print('密码正确登录成功')
#### 显示"选择功能"界面
print('查询余额完毕')
#### 显示"选择功能"界面
print('取了2000元钱')
#### 显示"选择功能"界面确定“选择功能”界面内容
1
2
3print('查询余额')
print('存款')
print('取款')封装”选择功能”
注意:一定是先定义函数,后调用函数。
1
2
3
4
5
6
7#### 封装ATM机功能选项 -- 定义函数
def select_func():
print('-----请选择功能-----')
print('查询余额')
print('存款')
print('取款')
print('-----请选择功能-----')调用函数
在需要显示“选择功能”函数的位置调用函数。
1 | print('密码正确登录成功') |
函数的参数作用
思考:完成需求如下:一个函数完成两个数1和2的加法运算,如何书写程序?
1 | #### 定义函数 |
思考:上述add_num1函数只能完成数字1和2的加法运算,如果想要这个函数变得更灵活,可以计算任何用户指定的两个数字的和,如何书写程序?
分析:用户要在调用函数的时候指定具体数字,那么在定义函数的时候就需要接收用户指定的数字。函数调用时候指定的数字和定义函数时候接收的数字即是函数的参数。
1 | #### 定义函数时同时定义了接收用户数据的参数a和b,a和b是形参 |
函数的返回值作用
例如:我们去超市购物,比如买烟,给钱之后,是不是售货员会返回给我们烟这个商品,在函数中,如果需要返回结果给用户需要使用函数返回值。
1 | def buy(): |
应用
需求:制作一个计算器,计算任意两数字之和,并保存结果。
1 | def sum_num(a, b): |
函数的说明文档
- 思考:定义一个函数后,程序员如何书写程序能够快速提示这个函数的作用?答:注释
- 思考:如果代码多,我们是不是需要在很多代码中找到这个函数定义的位置才能看到注释?如果想更方便的查看函数的作用怎么办?答:函数的说明文档
函数的说明文档也叫函数的文档说明。
语法
- 定义函数的说明文档
1
2
3
4def 函数名(参数):
""" 说明文档的位置 """
代码
...... - 查看函数的说明文档
1
help(函数名)
1
2
3
4
5
6def sum_num(a, b):
""" 求和函数 """
return a + b
help(sum_num)
函数嵌套调用
所谓函数嵌套调用指的是==一个函数里面又调用了另外一个函数==。
1 | def testB(): |
- 如果函数A中,调用了另外一个函数B,那么先把函数B中的任务都执行完毕之后才会回到上次 函数A执行的位置。
打印图形
- 打印一条横线
1
2
3def print_line():
print('-' * 20)
print_line()1
2
3
4
5
6
7
8
9
10
11
12
13
14
15WARNING! The remote SSH server rejected X11 forwarding request.
Last failed login: Fri Apr 9 17:40:20 CST 2021 from 49.234.64.13 on ssh:notty
There were 27 failed login attempts since the last successful login.
Last login: Thu Apr 8 23:53:27 2021 from 111.194.47.197
Welcome to Alibaba Cloud Elastic Compute Service !
[root@liruilong ~]# python3
Python 3.6.8 (default, Nov 16 2020, 16:55:22)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-44)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print('-' * 20)
--------------------
>>> - 打印多条横线
1
2
3
4
5
6
7
8
9
10def print_line():
print('-' * 20)
def print_lines(num):
i = 0
while i < num:
print_line()
i += 1
print_lines(5)
函数计算
求三个数之和
1
2
3
4
5
6def sum_num(a, b, c):
return a + b + c
result = sum_num(1, 2, 3)
print(result) # 6求三个数平均值
1
2
3
4
5
6def average_num(a, b, c):
sumResult = sum_num(a, b, c)
return sumResult / 3
result = average_num(1, 2, 3)
print(result) # 2.0
函数变量作用域
变量作用域指的是变量生效的范围,主要分为两类:==局部变量==和==全局变量==。
局部变量
:所谓局部变量是定义在函数体内部的变量,即只在函数体内部生效。1
2
3
4
5def testA():
a = 100
print(a)
testA() # 100
print(a) # 报错:name 'a' is not defined变量a是定义在
testA
函数内部的变量,在函数外部访问则立即报错。
局部变量的作用:在函数体内部,临时保存数据,即当函数调用完成后,则销毁局部变量。
全局变量
:所谓全局变量,指的是在函数体内、外都能生效的变量。
思考:如果有一个数据,在函数A和函数B中都要使用,该怎么办?
答:将这个数据存储在一个全局变量里面。
1 | #### 定义全局变量a |
思考:testB
函数需求修改变量a的值为200,如何修改程序?
1 | a = 100 |
思考:在testB
函数内部的a = 200
中的变量a是在修改全局变量a
吗?
答:不是。观察上述代码发现,15行得到a的数据是100,仍然是定义全局变量a时候的值,而没有返回testB
函数内部的200。综上:testB
函数内部的a = 200
是定义了一个局部变量。
思考:如何在函数体内部修改全局变量?
1 | a = 100 |
多函数程序执行流程
一般在实际开发过程中,一个程序往往由多个函数(后面知识中会讲解类)组成,并且多个函数共享某些数据,如下所示:
- 共用全局变量
1
2
3
4
5
6
7
8
9
10
11
12
13#### 1. 定义全局变量
glo_num = 0
def test1():
global glo_num
# 修改全局变量
glo_num = 100
def test2():
# 调用test1函数中修改后的全局变量
print(glo_num)
#### 2. 调用test1函数,执行函数内部代码:声明和修改全局变量
test1()
#### 3. 调用test2函数,执行函数内部代码:打印
test2() # 100 - 返回值作为参数传递
1
2
3
4
5
6
7
8def test1():
return 50
def test2(num):
print(num)
#### 1. 保存函数test1的返回值
result = test1()
#### 2.将函数返回值所在变量作为参数传递到test2函数
test2(result) # 50
函数的返回值
思考:如果一个函数如些两个return (如下所示),程序如何执行?
1 | def return_num(): |
答:只执行了第一个return,原因是因为return可以退出当前函数,导致return下方的代码不执行。
思考:如果一个函数要有多个返回值,该如何书写代码?
1 | def return_num(): |
注意:
return a, b
写法,返回多个数据的时候,默认是元组类型。
- return后面可以连接列表、元组或字典,以返回多个值。
函数的参数
位置参数
位置参数:调用函数时根据函数定义的参数位置来传递参数。
1 | def user_info(name, age, gender): |
注意:传递和定义参数的顺序及个数必须一致。
关键字参数
函数调用,通过“键=值”形式加以指定。可以让函数更加清晰、容易使用,同时也清除了参数的顺序需求。
1 | def user_info(name, age, gender): |
注意:**函数调用时,如果有位置参数时,位置参数必须在关键字参数的前面,但关键字参数之间不存在先后顺序
。**
缺省参数
缺省参数也叫默认参数
,用于定义函数,为参数提供默认值,调用函数时可不传该默认参数的值(注意:所有位置参数必须出现在默认参数前,包括函数定义和调用)。
1 | def user_info(name, age, gender='男'): |
注意:函数调用时,如果为缺省参数传值则修改默认参数值;否则使用这个默认值。
不定长参数
不定长参数也叫可变参数。用于不确定调用的时候会传递多少个参数(不传参也可以)的场景。此时,可用包裹(packing)位置参数,或者包裹关键字参数,来进行参数传递,会显得非常方便。
- 包裹位置传递
1
2
3
4
5
6def user_info(*args):
print(args)
#### ('TOM',)
user_info('TOM')
#### ('TOM', 18)
user_info('TOM', 18)注意:传进的所有参数都会被args变量收集,
它会根据传进参数的位置合并为一个元组(tuple)
,args是元组类型
,这就是包裹位置传递。 - 包裹关键字传递
1
2
3
4def user_info(**kwargs):
print(kwargs)
#### {'name': 'TOM', 'age': 18, 'id': 110}
user_info(name='TOM', age=18, id=110)综上:无论是包裹位置传递还是包裹关键字传递,都是一个组包的过程。
拆包和交换变量值
拆包
- 拆包:元组
1
2
3
4
5def return_num():
return 100, 200
num1, num2 = return_num()
print(num1) # 100
print(num2) # 200 - 拆包:字典
1
2
3
4
5
6
7dict1 = {'name': 'TOM', 'age': 18}
a, b = dict1
#### 对字典进行拆包,取出来的是字典的key
print(a) # name
print(b) # age
print(dict1[a]) # TOM
print(dict1[b]) # 18
交换变量值
需求:有变量a = 10
和b = 20
,交换两个变量的值。
借助第三变量存储数据。
1 | #### 1. 定义中间变量 |
1 | a, b = 1, 2 |
引用
在python中,值是靠引用来传递来的。
我们可以用id()
来判断两个变量是否为同一个值的引用。 我们可以将id值理解为那块内存的地址标识。
int类型为不可变类型
列表为可变类型
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#### 1. int类型
a = 1
b = a
print(b) # 1
print(id(a)) # 140708464157520
print(id(b)) # 140708464157520
a = 2
print(b) # 1,说明int类型为不可变类型
print(id(a)) # 140708464157552,此时得到是的数据2的内存地址
print(id(b)) # 140708464157520
#### 2. 列表
aa = [10, 20]
bb = aa
print(id(aa)) # 2325297783432
print(id(bb)) # 2325297783432
aa.append(30)
print(bb) # [10, 20, 30], 列表为可变类型
print(id(aa)) # 2325297783432
print(id(bb)) # 2325297783432
引用当做实参
代码如下:
1 | def test1(a): |
可变和不可变类型
所谓可变类型与不可变类型是指:数据能够直接进行修改,如果能直接修改那么就是可变,否则是不可变.
– | – |
---|---|
可变类型 | - 列表 - 字典 - 集合 |
不可变类型 | - 整型 - 浮点型 - 字符串 - 元组 |
应用:学员管理系统
系统简介
需求:进入系统显示系统功能界面,功能如下:
- 1、添加学员
- 2、删除学员
- 3、修改学员信息
- 4、查询学员信息
- 5、显示所有学员信息
- 6、退出系统
系统共6个功能,用户根据自己需求选取。
步骤分析
- 显示功能界面
- 用户输入功能序号
- 根据用户输入的功能序号,执行不同的功能(函数)
3.1 定义函数
3.2 调用函数
需求实现
显示功能界面
定义函数print_info
,负责显示系统功能。
1 | def print_info(): |
用户输入序号,选择功能
1 | user_num = input('请选择您需要的功能序号:') |
根据用户选择,执行不同的功能
1 | if user_num == '1': |
工作中,需要根据实际需求调优代码。
- 用户选择系统功能的代码需要循环使用,直到用户主动退出系统。
- 如果用户输入1-6以外的数字,需要提示用户。
1 | while True: |
定义不同功能的函数
所有功能函数都是操作学员信息,所有存储所有学员信息应该是一个==全局变量==,数据类型为==列表==。
1 | info = [] |
添加学员
- 接收用户输入学员信息,并保存
- 判断是否添加学员信息
2.1 如果学员姓名已经存在,则报错提示
2.2 如果学员姓名不存在,则准备空字典,将用户输入的数据追加到字典,再列表追加字典数据 - 对应的if条件成立的位置调用该函数
1 | def add_info(): |
删除学员
按用户输入的学员姓名进行删除
- 用户输入目标学员姓名
- 检查这个学员是否存在
2.1 如果存在,则列表删除这个数据
2.2 如果不存在,则提示“该用户不存在” - 对应的if条件成立的位置调用该函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14#### 删除学员
def del_info():
"""删除学员"""
# 1. 用户输入要删除的学员的姓名
del_name = input('请输入要删除的学员的姓名:')
global info
# 2. 判断学员是否存在:如果输入的姓名存在则删除,否则报错提示
for i in info:
if del_name == i['name']:
info.remove(i)
break
else:
print('该学员不存在')
print(info)
修改学员信息
- 用户输入目标学员姓名
- 检查这个学员是否存在
2.1 如果存在,则修改这位学员的信息,例如手机号
2.2 如果不存在,则报错 - 对应的if条件成立的位置调用该函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14#### 修改函数
def modify_info():
"""修改函数"""
# 1. 用户输入要修改的学员的姓名
modify_name = input('请输入要修改的学员的姓名:')
global info
# 2. 判断学员是否存在:如果输入的姓名存在则修改手机号,否则报错提示
for i in info:
if modify_name == i ['name']:
i['tel'] = input('请输入新的手机号:')
break
else:
print('该学员不存在')
print(info)
查询学员信息
- 需求分析
- 用户输入目标学员姓名
- 检查学员是否存在
2.1 如果存在,则显示这个学员的信息
2.2 如果不存在,则报错提示 - 对应的if条件成立的位置调用该函数
1 | #### 查询学员 |
显示所有学员信息
- 需求分析
打印所有学员信息1
2
3
4
5
6#### 显示所有学员信息
def print_all():
""" 显示所有学员信息 """
print('学号\t姓名\t手机号')
for i in info:
print(f'{i["id"]}\t{i["name"]}\t{i["tel"]}')
退出系统
在用户输入功能序号6
的时候要退出系统,代码如下:
1 | ...... |
递归
递归的应用场景
递归是一种编程思想,应用场景:
- 在我们日常开发中,如果要遍历一个文件夹下面所有的文件,通常会使用递归来实现;
- 在后续的算法课程中,很多算法都离不开递归,例如:快速排序。
递归的特点
- 函数内部自己调用自己
- 必须有出口
应用:3以内数字累加和
- 代码
1
2
3
4
5
6
7
8
9
10
11
12#### 3 + 2 + 1
def sum_numbers(num):
# 1.如果是1,直接返回1 -- 出口
if num == 1:
return 1
# 2.如果不是1,重复执行累加并返回结果
return num + sum_numbers(num-1)
sum_result = sum_numbers(3)
#### 输出结果为6
print(sum_result) - 执行结果
lambda 表达式
lambda的应用场景
如果一个函数有一个返回值,并且只有一句代码,可以使用 lambda简化。
lambda语法
1 | lambda 参数列表 : 表达式 |
注意
- lambda表达式的参数可有可无,函数的参数在lambda表达式中完全适用。
- lambda表达式能接收任何数量的参数但只能返回一个表达式的值。
1
2
3
4
5
6
7
8
9#### 函数
def fn1():
return 200
print(fn1)
print(fn1())
#### lambda表达式
fn2 = lambda: 100
print(fn2)
print(fn2())注意:直接打印lambda表达式,输出的是此lambda的内存地址
示例:计算a + b
函数实现
1 | def add(a, b): |
思考:需求简单,是否代码多?
lambda实现
1 | fn1 = lambda a, b: a + b |
lambda的参数形式
.无参数
1 | fn1 = lambda: 100 |
一个参数
1 | fn1 = lambda a: a |
默认参数
1 | fn1 = lambda a, b, c=100: a + b + c |
可变参数:*args
1 | fn1 = lambda *args: args |
注意:这里的可变参数传入到lambda之后,返回值为元组。
可变参数:**kwargs
1 | fn1 = lambda **kwargs: kwargs |
lambda的应用
带判断的lambda
1 | fn1 = lambda a, b: a if a > b else b |
列表数据按字典key的值排序
1 | students = [ |
高阶函数
==把函数作为参数传入==,这样的函数称为高阶函数,高阶函数是函数式编程的体现。函数式编程就是指这种高度抽象的编程范式。
体验高阶函数
在Python中,abs()
函数可以完成对数字求绝对值计算。
1 | abs(-10) # 10 |
round()
函数可以完成对数字的四舍五入计算。
1 | round(1.2) # 1 |
需求:任意两个数字,按照指定要求整理数字后再进行求和计算。
- 方法1
1
2
3
4
5
6def add_num(a, b):
return abs(a) + abs(b)
result = add_num(-1, 2)
print(result) # 3 方法2
1
2
3
4
5
6def sum_num(a, b, f):
return f(a) + f(b)
result = sum_num(-1, 2, abs)
print(result) # 3注意:两种方法对比之后,发现,方法2的代码会更加简洁,函数灵活性更高。
函数式编程大量使用函数,减少了代码的重复,因此程序比较短,开发速度较快。
内置高阶函数
map()
map(func, lst),将传入的函数变量func作用到lst变量的每个元素中,并将结果组成新的列表(Python2)/迭代器(Python3)返回。
需求:计算list1
序列中各个数字的2次方。
1 | list1 = [1, 2, 3, 4, 5] |
reduce()
reduce(func,lst),其中func必须有两个参数。每次func计算的结果继续和序列的下一个元素做累积计算。
注意:reduce()传入的参数func必须接收2个参数。
需求:计算list1
序列中各个数字的累加和。
1 | import functools |
filter()
filter(func, lst)函数用于过滤序列, 过滤掉不符合条件的元素, 返回一个 filter 对象。如果要转换为列表, 可以使用 list() 来转换。
1 | list1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] |
文件
文件操作
文件操作的作用
思考:什么是文件?思考:文件操作包含什么?
答:打开、关闭、读、写、复制….
思考:文件操作的的作用是什么?
答:读取内容、写入内容、备份内容……
总结:文件操作的作用就是==把一些内容(数据)存储存放起来,可以让程序下一次执行的时候直接使用,而不必重新制作一份,省时省力==。
文件的基本操作
文件操作步骤
- 打开文件
- 读写等操作
- 关闭文件
注意:可以只打开和关闭文件,不进行任何读写操作。
2.1.1 打开
在python,使用open函数,可以打开一个已经存在的文件,或者创建一个新文件,语法如下:
1 | open(name, mode) |
- name:是要打开的目标文件名的字符串(可以包含文件所在的具体路径)。
mode:设置打开文件的模式(访问模式):只读、写入、追加等。
打开文件模式
模式 | 描述 |
---|---|
r | 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。 |
rb | 以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。 |
r+ | 打开一个文件用于读写。文件指针将会放在文件的开头。 |
rb+ | 以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。 |
w | 打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。 |
wb | 以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。 |
w+ | 打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。 |
wb+ | 以二进制格式打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。 |
a | 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
ab | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
a+ | 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。 |
ab+ | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。 |
1 | f = open('test.txt', 'w') |
注意:此时的
f
是open
函数的文件对象。
文件对象方法
写
1 | 对象.write('内容') |
1 | #### 1. 打开文件 |
注意:
w
和a
模式:如果文件不存在则创建该文件;如果文件存在,w
模式先清空再写入,a
模式直接末尾追加。r
模式:如果文件不存在则报错。
读
- read()
1
文件对象.read(num)
num表示要从文件中读取的数据的长度(单位是字节),如果没有传入num,那么就表示读取文件中所有的数据。
- readlines():readlines可以按照行的方式把整个文件中的内容进行一次性读取,并且返回的是一个列表,其中每一行的数据为一个元素。
1
2
3
4
5
6f = open('test.txt')
content = f.readlines()
#### ['hello world\n', 'abcdefg\n', 'aaa\n', 'bbb\n', 'ccc']
print(content)
#### 关闭文件
f.close() - readline():readline()一次读取一行内容。
1
2
3
4
5
6
7f = open('test.txt')
content = f.readline()
print(f'第一行:{content}')
content = f.readline()
print(f'第二行:{content}')
#### 关闭文件
f.close()
seek()
作用:用来移动文件指针。语法如下:
1 | 文件对象.seek(偏移量, 起始位置) |
起始位置:
- 0:文件开头
- 1:当前位置
- 2:文件结尾
关闭
1 | 文件对象.close() |
文件备份
需求:用户输入当前目录下任意文件名,程序完成对该文件的备份功能(备份文件名为xx[备份]后缀,例如:test[备份].txt)。
步骤
- 接收用户输入的文件名
- 规划备份文件名
- 备份文件写入数据
代码实现
- 接收用户输入目标文件名
1
old_name = input('请输入您要备份的文件名:')
- 规划备份文件名
2.1 提取目标文件后缀
2.2 组织备份的文件名,xx[备份]后缀1
2
3
4
5
6
7
8# 2.1 提取文件后缀点的下标
index = old_name.rfind('.')
# print(index) # 后缀中.的下标
# print(old_name[:index]) # 源文件名(无后缀)
## 2.2 组织新文件名 旧文件名 + [备份] + 后缀
new_name = old_name[:index] + '[备份]' + old_name[index:]
# 打印新文件名(带后缀)
# print(new_name) - 备份文件写入数据
3.1 打开源文件 和 备份文件
3.2 将源文件数据写入备份文件
3.3 关闭文件1
2
3
4
5
6
7
8
9
10
11
12#### 3.1 打开文件
old_f = open(old_name, 'rb')
new_f = open(new_name, 'wb')
#### 3.2 将源文件数据写入备份文件
while True:
con = old_f.read(1024)
if len(con) == 0:
break
new_f.write(con)
#### 3.3 关闭文件
old_f.close()
new_f.close()
思考
如果用户输入.txt
,这是一个无效文件,程序如何更改才能限制只有有效的文件名才能备份?
答:添加条件判断即可。
1 | old_name = input('请输入您要备份的文件名:') |
文件和文件夹的操作
在Python中文件和文件夹的操作要借助os模块里面的相关功能,具体步骤如下:
- 导入os模块
1
import os
- 使用
os
模块相关功能1
os.函数名()
文件重命名
1 | os.rename(目标文件名, 新文件名) |
删除文件
1 | os.remove(目标文件名) |
创建文件夹
1 | os.mkdir(文件夹名字) |
删除文件夹
1 | os.rmdir(文件夹名字) |
获取当前目录
1 | os.getcwd() |
改变默认目录
1 | os.chdir(目录) |
获取目录列表
1 | os.listdir(目录) |
应用案例
需求:批量修改文件名,既可添加指定字符串,又能删除指定字符串。
- 步骤
- 设置添加删除字符串的的标识
- 获取指定目录的所有文件
- 将原有文件名添加/删除指定字符串,构造新名字
- os.rename()重命名
1 | import os |
面向对象基础
理解面向对象
面向对象是一种抽象化的编程思想,很多编程语言中都有的一种思想。
例如:洗衣服
思考:几种途径可以完成洗衣服?
答: 手洗 和 机洗。
手洗:找盆 - 放水 - 加洗衣粉 - 浸泡 - 搓洗 - 拧干水 - 倒水 - 漂洗N次 - 拧干 - 晾晒。
机洗:打开洗衣机 - 放衣服 - 加洗衣粉 - 按下开始按钮 - 晾晒。
思考:对比两种洗衣服途径,同学们发现了什么?
答:机洗更简单
思考:机洗,只需要找到一台洗衣机,加入简单操作就可以完成洗衣服的工作,而不需要关心洗衣机内部发生了什么事情。
总结:==面向对象就是将编程当成是一个事物,对外界来说,事物是直接使用的,不用去管他内部的情况。而编程就是设置事物能够做什么事。==
类和对象
思考:洗衣机洗衣服描述过程中,洗衣机其实就是一个事物,即对象,洗衣机对象哪来的呢?
答:洗衣机是由工厂工人制作出来。
思考:工厂工人怎么制作出的洗衣机?
答:工人根据设计师设计的功能图纸制作洗衣机。
总结:图纸 → 洗衣机 → 洗衣服。
在面向对象编程过程中,有两个重要组成部分:==类== 和 ==对象==。
==类和对象的关系:用类去创建一个对象。==
理解类和对象
类
类是对一系列具有相同==特征==和==行为==的事物的统称,是一个==抽象的概念==,不是真实存在的事物。
- 特征即是属性
- 行为即是方法
类比如是制造洗衣机时要用到的图纸,也就是说==类是用来创建对象==。
对象
对象是类创建出来的真实存在的事物,例如:洗衣机。
注意:开发中,先有类,再有对象。
面向对象实现方法
定义类
Python2中类分为:经典类
和 新式类
1 | class 类名(): |
注意:类名要满足标识符命名规则,同时遵循==大驼峰命名习惯==。
1 | class Washer(): |
- 拓展:经典类
不由任意内置类型派生出的类,称之为经典类
1 | class 类名: |
创建对象:对象又名实例。
1 | 对象名 = 类名() |
1 | # 创建对象 |
注意:创建对象的过程也叫实例化对象。
self:self指的是调用该函数的对象。
1 | # 1. 定义类 |
注意:打印对象和self得到的结果是一致的,都是当前对象的内存中存储地址。
添加和获取对象属性
属性即是特征,比如:洗衣机的宽度、高度、重量…对象属性既可以在类外面添加和获取,也能在类里面添加和获取。
类外面添加对象属性
1 | 对象名.属性名 = 值 |
1 | haier1.width = 500 |
类外面获取对象属性
1 | 对象名.属性名 |
1 | print(f'haier1洗衣机的宽度是{haier1.width}') |
类里面获取对象属性
1 | self.属性名 |
- 体验
1
2
3
4
5
6
7
8
9
10
11
12
13
14# 定义类
class Washer():
def print_info(self):
# 类里面获取实例属性
print(f'haier1洗衣机的宽度是{self.width}')
print(f'haier1洗衣机的高度是{self.height}')
# 创建对象
haier1 = Washer()
# 添加实例属性
haier1.width = 500
haier1.height = 800
haier1.print_info()
#haier1洗衣机的宽度是{500}
#haier1洗衣机的高度是{800}
魔法方法
在Python中,__xx__()
的函数叫做魔法方法,指的是具有特殊功能的函数。
__init__()
思考:洗衣机的宽度高度是与生俱来的属性,可不可以在生产过程中就赋予这些属性呢?
答:理应如此。
==__init__()
方法的作用:初始化对象。==
1 | class Washer(): |
注意:
__init__()
方法,在创建一个对象时默认被调用,不需要手动调用__init__(self)
中的self参数,不需要开发者传递,python解释器会自动把当前的对象引用传递过去。
带参数的__init__()
思考:一个类可以创建多个对象,如何对不同的对象设置不同的初始化属性呢?答:传参数。
1 | class Washer(): |
__str__()
当使用print输出对象的时候,默认打印对象的内存地址。如果类定义了__str__
方法,那么就会打印从在这个方法中 return 的数据。
1 | class Washer(): |
__del__()
当删除对象时,python解释器也会默认调用__del__()
方法。
1 | class Washer(): |
综合应用
烤地瓜
需求主线:
- 被烤的时间和对应的地瓜状态:
0-3分钟:生的
3-5分钟:半生不熟
5-8分钟:熟的
超过8分钟:烤糊了 - 添加的调料:
用户可以按自己的意愿添加调料
步骤分析
需求涉及一个事物: 地瓜,故案例涉及一个类:地瓜类。
定义类
- 地瓜的属性
- 被烤的时间
- 地瓜的状态
- 添加的调料
- 地瓜的方法
- 被烤
- 用户根据意愿设定每次烤地瓜的时间
- 判断地瓜被烤的总时间是在哪个区间,修改地瓜状态
- 添加调料
- 用户根据意愿设定添加的调料
- 将用户添加的调料存储
- 被烤
- 显示对象信息
5.1.2.2 创建对象,调用相关实例方法
代码实现
- 地瓜属性
- 定义地瓜初始化属性,后期根据程序推进更新实例属性
1
2
3
4
5
6
7
8class SweetPotato():
def __init__(self):
# 被烤的时间
self.cook_time = 0
# 地瓜的状态
self.cook_static = '生的'
# 调料列表
self.condiments = []
- 定义地瓜初始化属性,后期根据程序推进更新实例属性
- 定义烤地瓜方法
1
2
3
4
5
6
7
8
9
10
11
12
13class SweetPotato():
......
def cook(self, time):
"""烤地瓜的方法"""
self.cook_time += time
if 0 <= self.cook_time < 3:
self.cook_static = '生的'
elif 3 <= self.cook_time < 5:
self.cook_static = '半生不熟'
elif 5 <= self.cook_time < 8:
self.cook_static = '熟了'
elif self.cook_time >= 8:
self.cook_static = '烤糊了' - 书写str魔法方法,用于输出对象状态
1
2
3
4class SweetPotato():
......
def __str__(self):
return f'这个地瓜烤了{self.cook_time}分钟, 状态是{self.cook_static}' - 创建对象,测试实例属性和实例方法
1
2
3
4digua1 = SweetPotato()
print(digua1)
digua1.cook(2)
print(digua1) - 定义添加调料方法,并调用该实例方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class SweetPotato():
......
def add_condiments(self, condiment):
"""添加调料"""
self.condiments.append(condiment)
def __str__(self):
return f'这个地瓜烤了{self.cook_time}分钟, 状态是{self.cook_static}, 添加的调料有{self.condiments}'
digua1 = SweetPotato()
print(digua1)
digua1.cook(2)
digua1.add_condiments('酱油')
print(digua1)
digua1.cook(2)
digua1.add_condiments('辣椒面儿')
print(digua1)
digua1.cook(2)
print(digua1)
digua1.cook(2)
print(digua1)
代码总览
1 | # 定义类 |
搬家具
将小于房子剩余面积的家具摆放到房子中
步骤分析
需求涉及两个事物:房子 和 家具,故被案例涉及两个类:房子类 和 家具类。
定义类
- 房子类
- 实例属性
- 房子地理位置
- 房子占地面积
- 房子剩余面积
- 房子内家具列表
- 实例方法
- 容纳家具
- 显示房屋信息
- 实例属性
- 家具类
- 家具名称
- 家具占地面积
创建对象并调用相关方法
- 家具类
1
2
3
4
5
6class Furniture():
def __init__(self, name, area):
# 家具名字
self.name = name
# 家具占地面积
self.area = area - 房子类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class Home():
def __init__(self, address, area):
# 地理位置
self.address = address
# 房屋面积
self.area = area
# 剩余面积
self.free_area = area
# 家具列表
self.furniture = []
def __str__(self):
return f'房子坐落于{self.address}, 占地面积{self.area}, 剩余面积{self.free_area}, 家具有{self.furniture}'
def add_furniture(self, item):
"""容纳家具"""
if self.free_area >= item.area:
self.furniture.append(item.name)
# 家具搬入后,房屋剩余面积 = 之前剩余面积 - 该家具面积
self.free_area -= item.area
else:
print('家具太大,剩余面积不足,无法容纳')
创建对象并调用实例属性和方法
1 | bed = Furniture('双人床', 6) |
- 魔法方法
__init__()
: 初始化__str__()
:输出对象信息__del__()
:删除对象时调用
面向对象-继承
继承的概念
生活中的继承,一般指的是子女继承父辈的财产。
- 拓展1:经典类或旧式类:不由任意内置类型派生出的类,称之为经典类。
1
2
3class 类名:
代码
...... - 拓展2:新式类Python面向对象的继承指的是多个类之间的所属关系,即子类默认继承父类的所有属性和方法,具体如下:
1
2class 类名(object):
代码1
2
3
4
5
6
7
8
9
10
11# 父类A
class A(object):
def __init__(self):
self.num = 1
def info_print(self):
print(self.num)
# 子类B
class B(A):
pass
result = B()
result.info_print() # 1在Python中,所有类默认继承object类,object类是顶级类或基类;其他子类叫做派生类。
单继承
故事主线:一个煎饼果子老师傅,在煎饼果子界摸爬滚打多年,研发了一套精湛的摊煎饼果子的技术。师父要把这套技术传授给他的唯一的最得意的徒弟。
分析:徒弟是不是要继承师父的所有技术?
1 | # 1. 师父类 |
多继承
故事推进:daqiu是个爱学习的好孩子,想学习更多的煎饼果子技术,于是,在百度搜索到黑马程序员,报班学习煎饼果子技术。
所谓多继承意思就是一个类同时继承了多个父类。
1 | class Master(object): |
注意:当一个
类有多个父类的时候,默认使用第一个父类的同名属性和方法。
子类重写父类同名方法和属性
故事:daqiu掌握了师父和培训的技术后,自己潜心钻研出自己的独门配方的一套全新的煎饼果子技术。
1 | class Master(object): |
子类和父类具有同名属性和方法,默认使用子类的同名属性和方法。
子类调用父类的同名方法和属性
故事:很多顾客都希望也能吃到古法和新的技术的煎饼果子。
1 | class Master(object): |
多层继承
故事:N年后,daqiu老了,想要把所有技术传承给自己的徒弟。
1 | class Master(object): |
super()调用父类方法
1 | class Master(object): |
注意:使用super() 可以自动查找父类。调用顺序遵循
__mro__
类属性的顺序。比较适合单继承使用。
私有权限
定义私有属性和方法
在Python中,可以为实例属性和方法设置私有权限,即设置某个实例属性或实例方法不继承给子类。
故事:daqiu把技术传承给徒弟的同时,不想把自己的钱(2000000个亿)继承给徒弟,这个时候就要为
钱
这个实例属性设置私有权限。
·设置私有权限的方法:在属性名和方法名 前面 加上两个下划线 __。·
1 | class Master(object): |
注意:
私有属性和私有方法只能在类里面访问和修改。
获取和修改私有属性值
在Python中,一般定义函数名get_xx
用来获取私有属性,定义set_xx
用来修改私有属性值。
1 | class Master(object): |
异常
了解异常
当检测到一个错误时,解释器就无法继续执行了,反而出现了一些错误的提示,这就是所谓的”异常”。
例如:以r
方式打开一个不存在的文件。
1 | open('test.txt', 'r') |
异常的写法
1 | try: |
需求:尝试以r
模式打开文件,如果文件不存在,则以w
方式打开。
1 | try: |
捕获指定异常
1 | try: |
1 | try: |
注意:
- 如果尝试执行的代码的异常类型和要捕获的异常类型不一致,则无法捕获异常。
- 一般try下方只放一行尝试执行的代码。
捕获多个指定异常
当捕获多个异常时,可以把要捕获的异常类型的名字,放到except 后,并使用元组的方式进行书写。
1 | try: |
捕获异常描述信息
1 | try: |
捕获所有异常
Exception是所有程序异常类的父类。
1 | try: |
异常的else
else表示的是如果没有异常要执行的代码。
1 | try: |
异常的finally
finally表示的是无论是否异常都要执行的代码,例如关闭文件。
1 | try: |
异常的传递
1. 尝试只读方式打开test.txt文件,如果文件存在则读取文件内容,文件不存在则提示用户即可。
2. 读取内容要求:尝试循环读取内容,读取过程中如果检测到用户意外终止程序,则except
捕获异常并提示用户。
1 | import time |
自定义异常
在Python中,抛出自定义异常的语法为 raise 异常类对象
。
需求:密码长度不足,则报异常(用户输入密码,如果输入的长度不足3位,则报错,即抛出自定义异常,并捕获该异常)。
1 | # 自定义异常类,继承Exception |
模块和包
模块
Python 模块(Module),是一个 Python 文件,以 .py 结尾,包含了 Python 对象定义和Python语句。模块能定义函数,类和变量,模块里也能包含可执行的代码。
导入模块
导入模块的方式
- import 模块名
- from 模块名 import 功能名
- from 模块名 import *
- import 模块名 as 别名
- from 模块名 import 功能名 as 别名
import
1 | # 1. 导入模块 |
1 | import math |
from..import..
1 | from 模块名 import 功能1, 功能2, 功能3... |
1 | from math import sqrt |
from .. import *
1 | from 模块名 import * |
1 | from math import * |
as定义别名
1 | # 模块定义别名 |
1 | # 模块别名 |
制作模块
在Python中,每个Python文件都可以作为一个模块,模块的名字就是文件的名字。也就是说自定义模块名必须要符合标识符命名规则。
定义模块
新建一个Python文件,命名为my_module1.py
,并定义testA
函数。
1 | def testA(a, b): |
测试模块
在实际开中,当一个开发人员编写完一个模块后,为了让模块能够在项目中达到想要的效果,这个开发人员会自行在py文件中添加一些测试信息.,例如,在my_module1.py
文件中添加测试代码。
1 | def testA(a, b): |
此时,无论是当前文件,还是其他已经导入了该模块的文件,在运行的时候都会自动执行testA
函数的调用。
解决办法如下:·main函数·
1 | def testA(a, b): |
调用模块
1 | import my_module1 |
注意事项
如果使用from .. import ..
或from .. import *
导入多个模块的时候,且模块内有同名功能。当调用这个同名功能的时候,调用到的是后面导入的模块的功能。
- 体验
1
2
3
4
5
6
7
8
9
10
11# 模块1代码
def my_test(a, b):
print(a + b)
# 模块2代码
def my_test(a, b):
print(a - b)
# 导入模块和调用功能代码
from my_module1 import my_test
from my_module2 import my_test
# my_test函数是模块2中的函数
my_test(1, 1)
模块定位顺序
当导入一个模块,Python解析器对模块位置的搜索顺序是:
- 当前目录
- 如果不在当前目录,Python则搜索在shell变量PYTHONPATH下的每个目录。
- 如果都找不到,Python会察看默认路径。UNIX下,默认路径一般为/usr/local/lib/python/
模块搜索路径存储在system模块的sys.path变量中。变量里包含当前目录,PYTHONPATH和由安装过程决定的默认目录。
- 注意
- 自己的文件名不要和已有模块名重复,否则导致模块功能无法使用
使用from 模块名 import 功能
的时候,如果功能名字重复,调用到的是最后定义或导入的功能。
__all__
如果一个模块文件中有__all__
变量,当使用from xxx import *
导入时,只能导入这个列表中的元素。
- my_module1模块代码
1
2
3
4
5
6
7
8
9__all__ = ['testA']
def testA():
print('testA')
def testB():
print('testB') - 导入模块的文件代码
1
2
3from my_module1 import *
testA()
testB()
包
包将有联系的模块组织在一起,即放到同一个文件夹下,并且在这个文件夹创建一个名字为__init__.py
文件,那么这个文件夹就称之为包。
制作包
[New] — [Python Package] — 输入包名 — [OK] — 新建功能模块(有联系的模块)。
注意:新建包后,包内部会自动创建__init__.py
文件,这个文件控制着包的导入行为。
- 新建包
mypackage
- 新建包内模块:
my_module1
和my_module2
- 模块内代码如下
1
2
3
4
5
6# my_module1
print(1)
def info_print1():
print('my_module1')1
2
3
4
5
6# my_module2
print(2)
def info_print2():
print('my_module2')
导入包
方法一
1 | import 包名.模块名 |
1 | import my_package.my_module1 |
方法二
注意:必须在__init__.py
文件中添加__all__ = []
,控制允许导入的模块列表。
1 | from 包名 import * |
1 | from my_package import * |
课程:面向对象版学员管理系统
系统需求
使用面向对象编程思想完成学员管理系统的开发,具体如下:
- 系统要求:学员数据存储在文件中
- 系统功能:添加学员、删除学员、修改学员信息、查询学员信息、显示所有学员信息、保存学员信息及退出系统等功能。
准备程序文件
- 角色分析
- 学员
- 管理系统
工作中注意事项
- 为了方便维护代码,一般一个角色一个程序文件;
- 项目要有主程序入口,习惯为
main.py
创建程序文件
创建项目目录,例如:StudentManagerSystem
程序文件如下:
- 程序入口文件:main.py
- 学员文件:student.py
- 管理系统文件:managerSystem.py
student.py
需求:
- 学员信息包含:姓名、性别、手机号;
- 添加
__str__
魔法方法,方便查看学员对象信息1
2
3
4
5
6
7class Student(object):
def __init__(self, name, gender, tel):
self.name = name
self.gender = gender
self.tel = tel
def __str__(self):
return f'{self.name}, {self.gender}, {self.tel}'
managerSystem.py
需求:
- 存储数据的位置:文件(student.data)
- 加载文件数据
- 修改数据后保存到文件
- 存储数据的形式:列表存储学员对象
- 系统功能
- 添加学员
- 删除学员
- 修改学员
- 查询学员信息
- 显示所有学员信息
- 保存学员信息
- 退出系统
定义类
1 | class StudentManager(object): |
管理系统框架
需求:系统功能循环使用,用户输入不同的功能序号执行不同的功能。
- 步骤
- 定义程序入口函数
- 加载数据
- 显示功能菜单
- 用户输入功能序号
- 根据用户输入的功能序号执行不同的功能
- 定义系统功能函数,添加、删除学员等
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68class StudentManager(object):
def __init__(self):
# 存储数据所用的列表
self.student_list = []
# 一. 程序入口函数,启动程序后执行的函数
def run(self):
# 1. 加载学员信息
self.load_student()
while True:
# 2. 显示功能菜单
self.show_menu()
# 3. 用户输入功能序号
menu_num = int(input('请输入您需要的功能序号:'))
# 4 根据用户输入的功能序号执行不同的功能
if menu_num == 1:
# 添加学员
self.add_student()
elif menu_num == 2:
# 删除学员
self.del_student()
elif menu_num == 3:
# 修改学员信息
self.modify_student()
elif menu_num == 4:
# 查询学员信息
self.search_student()
elif menu_num == 5:
# 显示所有学员信息
self.show_student()
elif menu_num == 6:
# 保存学员信息
self.save_student()
elif menu_num == 7:
# 退出系统
break
# 二. 定义功能函数
# 2.1 显示功能菜单
def show_menu():
print('请选择如下功能-----------------')
print('1:添加学员')
print('2:删除学员')
print('3:修改学员信息')
print('4:查询学员信息')
print('5:显示所有学员信息')
print('6:保存学员信息')
print('7:退出系统')
# 2.2 添加学员
def add_student(self):
pass
# 2.3 删除学员
def del_student(self):
pass
# 2.4 修改学员信息
def modify_student(self):
pass
# 2.5 查询学员信息
def search_student(self):
pass
# 2.6 显示所有学员信息
def show_student(self):
pass
# 2.7 保存学员信息
def save_student(self):
pass
# 2.8 加载学员信息
def load_student(self):
pass
- 定义程序入口函数
main.py
1 | # 1. 导入managerSystem模块 |
定义系统功能函数
添加功能
- 需求:用户输入学员姓名、性别、手机号,将学员添加到系统。
- 步骤
- 用户输入姓名、性别、手机号
- 创建该学员对象
- 将该学员对象添加到列表
- 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19# 添加学员函数内部需要创建学员对象,故先导入student模块
from student import *
class StudentManager(object):
......
# 2.2 添加学员
def add_student(self):
# 1. 用户输入姓名、性别、手机号
name = input('请输入您的姓名:')
gender = input('请输入您的性别:')
tel = input('请输入您的手机号:')
# 2. 创建学员对象:先导入学员模块,再创建对象
student = Student(name, gender, tel)
# 3. 将该学员对象添加到列表
self.student_list.append(student)
# 打印信息
print(self.student_list)
print(student)
删除学员
- 需求:用户输入目标学员姓名,如果学员存在则删除该学员。
- 步骤
- 用户输入目标学员姓名
- 遍历学员数据列表,如果用户输入的学员姓名存在则删除,否则则提示该学员不存在。
- 代码
1
2
3
4
5
6
7
8
9
10
11
12
13# 2.3 删除学员:删除指定姓名的学员
def del_student(self):
# 1. 用户输入目标学员姓名
del_name = input('请输入要删除的学员姓名:')
# 2. 如果用户输入的目标学员存在则删除,否则提示学员不存在
for i in self.student_list:
if i.name == del_name:
self.student_list.remove(i)
break
else:
print('查无此人!')
# 打印学员列表,验证删除功能
print(self.student_list)
修改学员信息
- 需求:用户输入目标学员姓名,如果学员存在则修改该学员信息。
- 步骤
- 用户输入目标学员姓名;
- 遍历学员数据列表,如果用户输入的学员姓名存在则修改学员的姓名、性别、手机号数据,否则则提示该学员不存在。
- 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14# 2.4 修改学员信息
def modify_student(self):
# 1. 用户输入目标学员姓名
modify_name = input('请输入要修改的学员的姓名:')
# 2. 如果用户输入的目标学员存在则修改姓名、性别、手机号等数据,否则提示学员不存在
for i in self.student_list:
if i.name == modify_name:
i.name = input('请输入学员姓名:')
i.gender = input('请输入学员性别:')
i.tel = input('请输入学员手机号:')
print(f'修改该学员信息成功,姓名{i.name},性别{i.gender}, 手机号{i.tel}')
break
else:
print('查无此人!')
查询学员信息
- 需求:用户输入目标学员姓名,如果学员存在则打印该学员信息
- 步骤
- 用户输入目标学员姓名
- 遍历学员数据列表,如果用户输入的学员姓名存在则打印学员信息,否则提示该学员不存在。
- 代码
1
2
3
4
5
6
7
8
9
10
11# 2.5 查询学员信息
def search_student(self):
# 1. 用户输入目标学员姓名
search_name = input('请输入要查询的学员的姓名:')
# 2. 如果用户输入的目标学员存在,则打印学员信息,否则提示学员不存在
for i in self.student_list:
if i.name == search_name:
print(f'姓名{i.name},性别{i.gender}, 手机号{i.tel}')
break
else:
print('查无此人!')
显示所有学员信息
- 打印所有学员信息
- 步骤
- 遍历学员数据列表,打印所有学员信息
- 代码
1
2
3
4
5# 2.6 显示所有学员信息
def show_student(self):
print('姓名\t性别\t手机号')
for i in self.student_list:
print(f'{i.name}\t{i.gender}\t{i.tel}')
保存学员信息
- 需求:将修改后的学员数据保存到存储数据的文件。
- 步骤
- 打开文件
- 文件写入数据
- 关闭文件
思考
- 文件写入的数据是学员对象的内存地址吗?
- 文件内数据要求的数据类型是什么?
- 拓展
__dict__
在Python中1
2
3
4
5
6
7
8
9class A(object):
a = 0
def __init__(self):
self.b = 1
aa = A()
# 返回类内部所有属性和方法对应的字典
print(A.__dict__)
# 返回实例属性和值组成的字典
print(aa.__dict__) - 代码
1
2
3
4
5
6
7
8
9
10
11
12
13# 2.7 保存学员信息
def save_student(self):
# 1. 打开文件
f = open('student.data', 'w')
# 2. 文件写入学员数据
# 注意1:文件写入的数据不能是学员对象的内存地址,需要把学员数据转换成列表字典数据再做存储
new_list = [i.__dict__ for i in self.student_list]
# [{'name': 'aa', 'gender': 'nv', 'tel': '111'}]
print(new_list)
# 注意2:文件内数据要求为字符串类型,故需要先转换数据类型为字符串才能文件写入数据
f.write(str(new_list))
# 3. 关闭文件
f.close()
加载学员信息
- 需求:每次进入系统后,修改的数据是文件里面的数据
- 步骤
- 尝试以
"r"
模式打开学员数据文件,如果文件不存在则以"w"
模式打开文件 - 如果文件存在则读取数据并存储数据
- 读取数据
- 转换数据类型为列表并转换列表内的字典为对象
- 存储学员数据到学员列表
- 关闭文件
- 尝试以
- 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16# 2.8 加载学员信息
def load_student(self):
# 尝试以"r"模式打开数据文件,文件不存在则提示用户;文件存在(没有异常)则读取数据
try:
f = open('student.data', 'r')
except:
f = open('student.data', 'w')
else:
# 1. 读取数据
data = f.read()
# 2. 文件中读取的数据都是字符串且字符串内部为字典数据,故需要转换数据类型再转换字典为对象后存储到学员列表
new_list = eval(data)
self.student_list = [Student(i['name'], i['gender'], i['tel']) for i in new_list]
finally:
# 3. 关闭文件
f.close()
多任务的介绍
Python 多进程
导入进程包
1 | #导入进程包 |
Process进程类的说明
1 | Process([group [, target [, name [, args [, kwargs]]]]]) |
- group:指定进程组,目前只能使用None
- target:执行的目标任务名
- name:进程名字
- args:以元组方式给执行任务传参
- kwargs:以字典方式给执行任务传参
Process创建的实例对象的常用方法:
- start():启动子进程实例(创建子进程)
- join():等待子进程执行结束
- terminate():不管任务是否完成,立即终止子进程
Process创建的实例对象的常用属性:
- name:当前进程的别名,默认为Process-N,N为从1开始递增的整数
多进程完成多任务的代码
1 | import multiprocessing |
执行结果:
唱歌中…
跳舞中…
唱歌中…
跳舞中…
唱歌中…
跳舞中…
唱歌中…
跳舞中…
唱歌中…
跳舞中…
获取进程编号
获取进程编号的目的
获取进程编号的目的是验证主进程和子进程的关系,可以得知子进程是由那个主进程创建出来的。获取进程编号的两种操作
- 获取当前进程编号
- 获取当前父进程编号
- 是OS模块
获取当前进程编号
os.getpid() 表示获取当前进程编号
1 | import multiprocessing |
执行结果:
main: 70763
main: <_MainProcess(MainProcess, started)>
dance: 70768
dance: <Process(myprocess1, started)>
跳舞中…
sing: 70769
sing: <Process(Process-2, started)>
唱歌中…
唱歌中…
唱歌中…
唱歌中…
唱歌中…
获取当前父进程编号
os.getppid() 表示获取当前父进程编号
1 | import multiprocessing |
main: 70860
main: <_MainProcess(MainProcess, started)>
dance: 70861
dance: <Process(myprocess1, started)>
dance的父进程编号: 70860
跳舞中…
sing: 70862
sing: <Process(Process-2, started)>
sing的父进程编号: 70860
唱歌中…
唱歌中…
唱歌中…
唱歌中…
唱歌中…
进程执行带有参数的任务
进程执行带有参数的任务的介绍
我们使用进程执行的任务是没有参数的,假如我们使用进程执行的任务带有参数,如何给函数传参呢?
Process类执行任务并给任务传参数有两种方式:
- args 表示以
元组
的方式给执行任务传参 - kwargs 表示以
字典
方式给执行任务传参
args参数的使用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import multiprocessing
import time
# 带有参数的任务
def task(count):
for i in range(count):
print("任务执行中..")
time.sleep(0.2)
else:
print("任务执行完成")
if __name__ == '__main__':
# 创建子进程
# args: 以元组的方式给任务传入参数
sub_process = multiprocessing.Process(target=task, args=(5,))
sub_process.start()任务执行中..
任务执行中..
任务执行中..
任务执行中..
任务执行中..
任务执行完成
kwargs参数的使用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import multiprocessing
import time
# 带有参数的任务
def task(count):
for i in range(count):
print("任务执行中..")
time.sleep(0.2)
else:
print("任务执行完成")
if __name__ == '__main__':
# 创建子进程
# kwargs: 表示以字典方式传入参数
sub_process = multiprocessing.Process(target=task, kwargs={"count": 3})
sub_process.start()任务执行中..
任务执行中..
任务执行中..
任务执行完成
进程的注意点
进程的注意点介绍:
- 进程之间不共享全局变量
- 主进程会等待所有的子进程执行结束再结束
进程之间不共享全局变量:
1 | import multiprocessing |
add: 0
add: 1
add: 2
add: 3
add: 4
add_data: [0, 1, 2, 3, 4]
main: []
read_data []
创建子进程会对主进程资源进行拷贝,也就是说子进程是主进程的一个副本,好比是一对双胞胎,之所以进程之间不共享全局变量,是因为操作的不是同一个进程里面的全局变量,只不过不同进程里面的全局变量名字相同而已。
主进程会等待所有的子进程执行结束再结束
假如我们现在创建一个子进程,这个子进程执行完大概需要2秒钟,现在让主进程执行0.5秒钟就退出程序,查看一下执行结果.
1 | import multiprocessing |
任务执行中…
任务执行中…
任务执行中…
over
任务执行中…
任务执行中…
任务执行中…
任务执行中…
任务执行中…
任务执行中…
任务执行中…
主进程会等待所有的子进程执行结束再结束,假如我们就让主进程执行0.5秒钟,子进程就销毁不再执行,那怎么办呢?
我们可以设置守护主进程 或者 在主进程退出之前 让子进程销毁
- 守护主进程:守护主进程就是主进程退出子进程销毁不再执行
- 子进程销毁:子进程执行结束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21import multiprocessing
import time
# 定义进程所需要执行的任务
def task():
for i in range(10):
print("任务执行中...")
time.sleep(0.2)
if __name__ == '__main__':
# 创建子进程
sub_process = multiprocessing.Process(target=task)
# 设置守护主进程,主进程退出子进程直接销毁,子进程的生命周期依赖与主进程
# sub_process.daemon = True
sub_process.start()
time.sleep(0.5)
print("over")
# 让子进程销毁
sub_process.terminate()
exit()
# 总结: 主进程会等待所有的子进程执行完成以后程序再退出
# 如果想要主进程退出子进程销毁,可以设置守护主进程或者在主进程退出之前让子进程销毁
多线程的使用
导入线程模块
1 | #导入线程模块 |
线程类Thread参数说明
1 | Thread([group [, target [, name [, args [, kwargs]]]]]) |
- group: 线程组,目前只能使用None
- target: 执行的目标任务名
- args: 以元组的方式给执行任务传参
- kwargs: 以字典方式给执行任务传参
- name: 线程名,一般不用设置
启动线程
- 启动线程使用start方法
多线程完成多任务
1 | import threading |
正在唱歌…0
正在跳舞…0
正在唱歌…1
正在跳舞…1
正在唱歌…2
正在跳舞…2
线程执行带有参数的任务
线程执行带有参数的任务的介绍
前面我们使用线程执行的任务是没有参数的,假如我们使用线程执行的任务带有参数,如何给函数传参呢?Thread类执行任务并给任务传参数有两种方式:
- args 表示以元组的方式给执行任务传参
- kwargs 表示以字典方式给执行任务传参
args参数的使用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import threading
import time
# 带有参数的任务
def task(count):
for i in range(count):
print("任务执行中..")
time.sleep(0.2)
else:
print("任务执行完成")
if __name__ == '__main__':
# 创建子线程
# args: 以元组的方式给任务传入参数
sub_thread = threading.Thread(target=task, args=(5,))
sub_thread.start()任务执行中..
任务执行中..
任务执行中..
任务执行中..
任务执行中..
任务执行完成
kwargs参数的使用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import threading
import time
# 带有参数的任务
def task(count):
for i in range(count):
print("任务执行中..")
time.sleep(0.2)
else:
print("任务执行完成")
if __name__ == '__main__':
# 创建子线程
# kwargs: 表示以字典方式传入参数
sub_thread = threading.Thread(target=task, kwargs={"count": 3})
sub_thread.start()任务执行中..
任务执行中..
任务执行中..
任务执行完成
线程的注意点
- 线程之间执行是无序的
- 主线程会等待所有的子线程执行结束再结束
- 线程之间共享全局变量
- 线程之间共享全局变量数据出现错误问题
线程之间执行是无序的
- 线程之间执行是无序的,它是由cpu调度决定的 ,cpu调度哪个线程,哪个线程就先执行,没有调度的线程不能执行。
- 进程之间执行也是无序的,它是由操作系统调度决定的,操作系统调度哪个进程,哪个进程就先执行,没有调度的进程不能执行。
1
2
3
4
5
6
7
8
9
10
11
12
13import threading
import time
def task():
time.sleep(1)
print("当前线程:", threading.current_thread().name)
if __name__ == '__main__':
for _ in range(5):
sub_thread = threading.Thread(target=task)
sub_thread.start()当前线程: Thread-1
当前线程: Thread-2
当前线程: Thread-4
当前线程: Thread-5
当前线程: Thread-3
主线程会等待所有的子线程执行结束再结束
假如我们现在创建一个子线程,这个子线程执行完大概需要2.5秒钟,现在让主线程执行1秒钟就退出程序,查看一下执行结果
1 | import threading |
test: 0
test: 1
over
test: 2
test: 3
test: 4
主线程会等待所有的子线程执行结束再结束,假如我们就让主线程执行1秒钟,子线程就销毁不再执行,那怎么办呢?
我们可以设置守护主线程
守护主线程:守护主线程就是主线程退出子线程销毁不再执行
设置守护主线程有两种方式:
- threading.Thread(target=show_info, daemon=True)
- 线程对象.setDaemon(True)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21import threading
import time
# 测试主线程是否会等待子线程执行完成以后程序再退出
def show_info():
for i in range(5):
print("test:", i)
time.sleep(0.5)
if __name__ == '__main__':
# 创建子线程守护主线程
# daemon=True 守护主线程
# 守护主线程方式1
sub_thread = threading.Thread(target=show_info, daemon=True)
# 设置成为守护主线程,主线程退出后子线程直接销毁不再执行子线程的代码
# 守护主线程方式2
# sub_thread.setDaemon(True)
sub_thread.start()
# 主线程延时1秒
time.sleep(1)
print("over")test: 0
test: 1
over
线程之间共享全局变量
需求:
定义一个列表类型的全局变量
创建两个子线程分别执行向全局变量添加数据的任务和向全局变量读取数据的任务
查看线程之间是否共享全局变量数据
1 | import threading |
write_data: [0, 1, 2, 3, 4]
开始读取数据啦
read_data: [0, 1, 2, 3, 4]
线程之间共享全局变量数据出现错误问题
需求:
- 定义两个函数,实现循环100万次,每循环一次给全局变量加1
- 创建两个子线程执行对应的两个函数,查看计算后的结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25import threading
# 定义全局变量
g_num = 0
# 循环一次给全局变量加1
def sum_num1():
for i in range(1000000):
global g_num
g_num += 1
print("sum1:", g_num)
# 循环一次给全局变量加1
def sum_num2():
for i in range(1000000):
global g_num
g_num += 1
print("sum2:", g_num)
if __name__ == '__main__':
# 创建两个线程
first_thread = threading.Thread(target=sum_num1)
second_thread = threading.Thread(target=sum_num2)
# 启动线程
first_thread.start()
# 启动线程
second_thread.start()sum1: 1210949
sum2: 1496035
多线程同时对全局变量操作数据发生了错误,错误分析:
两个线程first_thread和second_thread都要对全局变量g_num(默认是0)进行加1运算,但是由于是多线程同时操作,有可能出现下面情况:
- 在g_num=0时,first_thread取得g_num=0。此时系统把first_thread调度为”sleeping”状态,把second_thread转换为”running”状态,t2也获得g_num=0
- 然后second_thread对得到的值进行加1并赋给g_num,使得g_num=1
- 然后系统又把second_thread调度为”sleeping”,把first_thread转为”running”。线程t1又把它之前得到的0加1后赋值给g_num。
- 这样导致虽然first_thread和first_thread都对g_num加1,但结果仍然是g_num=1
全局变量数据错误的解决办法:
线程同步: 保证同一时刻只能有一个线程去操作全局变量 同步: 就是协同步调,按预定的先后次序进行运行。如:你说完,我再说, 好比现实生活中的对讲机
线程同步的方式:
- 线程等待(join)
- 互斥锁
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
28import threading
# 定义全局变量
g_num = 0
# 循环1000000次每次给全局变量加1
def sum_num1():
for i in range(1000000):
global g_num
g_num += 1
print("sum1:", g_num)
# 循环1000000次每次给全局变量加1
def sum_num2():
for i in range(1000000):
global g_num
g_num += 1
print("sum2:", g_num)
if __name__ == '__main__':
# 创建两个线程
first_thread = threading.Thread(target=sum_num1)
second_thread = threading.Thread(target=sum_num2)
# 启动线程
first_thread.start()
# 主线程等待第一个线程执行完成以后代码再继续执行,让其执行第二个线程
# 线程同步: 一个任务执行完成以后另外一个任务才能执行,同一个时刻只有一个任务在执行
first_thread.join()
# 启动线程
second_thread.start()sum1: 1000000
sum2: 2000000
互斥锁
互斥锁: 对共享数据进行锁定,保证同一时刻只能有一个线程去操作。
注意:互斥锁是多个线程一起去抢,抢到锁的线程先执行,没有抢到锁的线程需要等待,等互斥锁使用完释放后,其它等待的线程再去抢这个锁。
互斥锁的使用
互斥锁使用步骤:
1 | # 创建锁 |
注意点:
acquire和release方法之间的代码同一时刻只能有一个线程去操作
如果在调用acquire方法的时候 其他线程已经使用了这个互斥锁,那么此时acquire方法会堵塞,直到这个互斥锁释放后才能再次上锁。
使用互斥锁完成2个线程对同一个全局变量各加100万次的操作
1 | import threading |
sum1: 1000000
sum2: 2000000
说明:通过执行结果可以地址互斥锁能够保证多个线程访问共享数据不会出现数据错误问题
死锁:
死锁: 一直等待对方释放锁的情景就是死锁
死锁示例
需求:根据下标在列表中取值, 保证同一时刻只能有一个线程去取值
1 | import threading |
避免死锁
==在合适的地方释放锁-
1 | import threading |
进程和线程的对比的三个方向
- ==关系对比==
- 线程是依附在进程里面的,没有进程就没有线程。
- 一个进程默认提供一条线程,进程可以创建多个线程。
- ==区别对比==
- 进程之间不共享全局变量,
- 线程之间共享全局变量,但是要注意资源竞争的问题,解决办法: 互斥锁或者线程同步
- 创建进程的资源开销要比创建线程的资源开销要大
- 进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位
- 线程不能够独立执行,必须依存在进程中
- 多进程开发比单进程多线程开发稳定性要强
- ==优缺点对比==
- 进程优缺点:
- 优点:可以用多核
- 缺点:资源开销大
- 线程优缺点:
- 优点:资源开销小
- 缺点:不能使用多核
- 进程优缺点:
网络编程
socket 的概念
socket (简称 套接字) 是进程之间通信一个工具,好比现实生活中的插座,所有的家用电器要想工作都是基于插座进行,进程之间想要进行网络通信需要基于这个 socket。
- socket 的作用:负责进程之间的网络数据传输,好比数据的搬运工。
- socket 使用场景:不夸张的说,只要跟网络相关的应用程序或者软件都使用到了 socket 。
TCP 网络应用程序开发流程
TCP 网络应用程序开发流程的介绍
TCP 网络应用程序开发分为:
- TCP 客户端程序开发
- TCP 服务端程序开发
说明:客户端程序是指运行在用户设备上的程序 服务端程序是指运行在服务器设备上的程序,专门为客户端提供数据服务。
TCP 客户端程序开发流程的介绍
步骤说明:
- 创建客户端套接字对象
- 和服务端套接字建立连接
- 发送数据
- 接收数据
- 关闭客户端套接字
TCP 客户端程序开发
导入 socket 模块 import socket
创建客户端 socket 对象 socket.socket(AddressFamily, Type)
==参数说明:==
- AddressFamily 表示IP地址类型, 分为TPv4和IPv6
- Type 表示传输协议类型
==方法说明:== - connect((host, port)) 表示和服务端套接字建立连接, host是服务器ip地址,port是应用程序的端口号
- send(data) 表示发送数据,data是二进制数据
- recv(buffersize) 表示接收数据, buffersize是每次接收数据的长度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import socket
if __name__ == '__main__':
# 创建tcp客户端套接字
# 1. AF_INET:表示ipv4
# 2. SOCK_STREAM: tcp传输协议
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 和服务端应用程序建立连接
tcp_client_socket.connect(("192.168.131.62", 8080))
# 代码执行到此,说明连接建立成功
# 准备发送的数据
send_data = "你好服务端,我是客户端小黑!".encode("gbk")
# 发送数据
tcp_client_socket.send(send_data)
# 接收数据, 这次接收的数据最大字节数是1024
recv_data = tcp_client_socket.recv(1024)
# 返回的直接是服务端程序发送的二进制数据
print(recv_data)
# 对数据进行解码
recv_content = recv_data.decode("gbk")
print("接收服务端的数据为:", recv_content)
# 关闭套接字
tcp_client_socket.close()b’hello’
接收服务端的数据为: hello
TCP 服务端程序开发流程的介绍
步骤说明:
- 创建服务端端套接字对象
- 绑定端口号
- 设置监听
- 等待接受客户端的连接请求
- 接收数据
- 发送数据
- 关闭套接字
TCP服务端程序开发
导入 socket 模块 import socket
创建服务端 socket 对象 socket.socket(AddressFamily, Type)
==参数说明:==
- AddressFamily 表示IP地址类型, 分为TPv4和IPv6
- Type 表示传输协议类型
==方法说明:== - bind((host, port)) 表示绑定端口号, host 是 ip 地址,port 是端口号,ip 地址一般不指定,表示本机的任何一个ip地址都可以。
- listen (backlog) 表示设置监听,backlog参数表示最大等待建立连接的个数。
- accept() 表示等待接受客户端的连接请求
- send(data) 表示发送数据,data 是二进制数据
- crecv(buffersize) 表示接收数据, buffersize 是每次接收数据的长度
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
36import socket
if __name__ == '__main__':
# 创建tcp服务端套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口号复用,让程序退出端口号立即释放
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
# 给程序绑定端口号
tcp_server_socket.bind(("", 8989))
# 设置监听
# 128:最大等待建立连接的个数, 提示: 目前是单任务的服务端,同一时刻只能服务与一个客户端,后续使用多任务能够让服务端同时服务与多个客户端,
# 不需要让客户端进行等待建立连接
# listen后的这个套接字只负责接收客户端连接请求,不能收发消息,收发消息使用返回的这个新套接字来完成
tcp_server_socket.listen(128)
# 等待客户端建立连接的请求, 只有客户端和服务端建立连接成功代码才会解阻塞,代码才能继续往下执行
# 1. 专门和客户端通信的套接字: service_client_socket
# 2. 客户端的ip地址和端口号: ip_port
service_client_socket, ip_port = tcp_server_socket.accept()
# 代码执行到此说明连接建立成功
print("客户端的ip地址和端口号:", ip_port)
# 接收客户端发送的数据, 这次接收数据的最大字节数是1024
recv_data = service_client_socket.recv(1024)
# 获取数据的长度
recv_data_length = len(recv_data)
print("接收数据的长度为:", recv_data_length)
# 对二进制数据进行解码
recv_content = recv_data.decode("gbk")
print("接收客户端的数据为:", recv_content)
# 准备发送的数据
send_data = "ok, 问题正在处理中...".encode("gbk")
# 发送数据给客户端
service_client_socket.send(send_data)
# 关闭服务与客户端的套接字, 终止和客户端通信的服务
service_client_socket.close()
# 关闭服务端的套接字, 终止和客户端提供建立连接请求的服务
tcp_server_socket.close()客户端的ip地址和端口号: (‘172.16.47.209’, 52472)
接收数据的长度为: 5
接收客户端的数据为: hello
说明:当客户端和服务端建立连接后,服务端程序退出后端口号不会立即释放,需要等待大概1-2分钟。
解决办法有两种:
- 更换服务端端口号
- 设置端口号复用(推荐大家使用),也就是说让服务端程序退出后端口号立即释放。
设置端口号复用的代码如下:1
2
3
4# 参数1: 表示当前套接字
# 参数2: 设置端口号复用选项
# 参数3: 设置端口号复用选项对应的值
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
TCP网络应用程序的注意点
- 当 TCP 客户端程序想要和 TCP 服务端程序进行通信的时候必须要先建立连接
- TCP 客户端程序一般不需要绑定端口号,因为客户端是主动发起建立连接的。
- TCP 服务端程序必须绑定端口号,否则客户端找不到这个 TCP 服务端程序。
- listen 后的套接字是被动套接字,只负责接收新的客户端的连接请求,不能收发消息。
- 当 TCP 客户端程序和 TCP 服务端程序连接成功后, TCP 服务器端程序会产生一个新的套接字,收发客户端消息使用该套接字。
- 关闭 accept 返回的套接字意味着和这个客户端已经通信完毕。
- 关闭 listen 后的套接字意味着服务端的套接字关闭了,会导致新的客户端不能连接服务端,但是之前已经接成功的客户端还能正常通信。
- 当客户端的套接字调用 close 后,服务器端的 recv 会解阻塞,返回的数据长度为0,服务端可以通过返回数据的长度来判断客户端是否已+ 经下线,反之服务端关闭套接字,客户端的 recv 也会解阻塞,返回的数据长度也为0。
案例-多任务版TCP服务端程序开发
具体实现步骤
- 编写一个TCP服务端程序,循环等待接受客户端的连接请求
- 当客户端和服务端建立连接成功,创建子线程,使用子线程专门处理客户端的请求,防止主线程阻塞
- 把创建的子线程设置成为守护主线程,防止主线程无法退出。
1 | import socket |
客户端连接成功: (‘172.16.47.209’, 51528)
客户端连接成功: (‘172.16.47.209’, 51714)
hello1 (‘172.16.47.209’, 51528)
hello2 (‘172.16.47.209’, 51714)
socket之send和recv原理剖析
1. 认识TCP socket的发送和接收缓冲区
当创建一个TCP socket对象的时候会有一个发送缓冲区和一个接收缓冲区,这个发送和接收缓冲区指的就是内存中的一片空间。
2. send原理剖析
send是不是直接把数据发给服务端?
不是,要想发数据,必须得通过网卡发送数据,应用程序是无法直接通过网卡发送数据的,它需要调用操作系统接口,也就是说,应用程序把发送的数据先写入到发送缓冲区(内存中的一片空间),再由操作系统控制网卡把发送缓冲区的数据发送给服务端网卡 。
3. recv原理剖析
recv是不是直接从客户端接收数据?
不是,应用软件是无法直接通过网卡接收数据的,它需要调用操作系统接口,由操作系统通过网卡接收数据,把接收的数据写入到接收缓冲区(内存中的一片空间),应用程序再从接收缓存区获取客户端发送的数据。
send和recv原理剖析图
说明:
- 发送数据是发送到发送缓冲区
- 接收数据是从接收缓冲区 获取
Python静态Web服务器
搭建Python自带静态Web服务器
如何搭建Python自带的静态Web服务器
搭建Python自带的静态Web服务器使用 python3 -m http.server 端口号
, 端口号不指定默认是8000
访问搭建的静态Web服务器 IP:端口
返回固定页面数据
开发自己的静态Web服务器
实现步骤:
- 编写一个TCP服务端程序
1
2
3
4tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 循环接受客户端的连接请求
while True:
conn_socket, ip_port = tcp_server_socket.accept() - 获取浏览器发送的http请求报文数据
1
client_request_data = conn_socket.recv(4096)
- 读取固定页面数据,把页面数据组装成HTTP响应报文数据发送给浏览器。
1
2response_data = (response_line + response_header + "\r\n").encode("utf-8") + response_body
conn_socket.send(response_data)
HTTP响应报文数据发送完成以后,关闭服务于客户端的套接字。
1 | conn_socket.close() |
1 | import socket |
根据不同映射返回固定页面数据
目前的Web服务器,不管用户访问什么页面,返回的都是固定页面的数据,接下来需要根据用户的请求返回指定页面的数据
返回指定页面数据的实现步骤:
- 获取用户请求资源的路径
1
2request_list = client_request_conent.split(” ”, maxsplit=2)
request_path = request_list[1] - 根据请求资源的路径,读取指定文件的数据
1
2with open("static" + request_path, "rb") as file:
file_data = file.read() - 组装指定文件数据的响应报文,发送给浏览器
1
2response_data = (response_line + response_header + "\r\n").encode("utf-8") + response_body
conn_socket.send(response_data) - 判断请求的文件在服务端不存在,组装404状态的响应报文,发送给浏览器
1
2
3
4try:
# 打开指定文件,代码省略...
except Exception as e:
conn_socket.send(404响应报文数据)
1 | import socket |
静态Web服务器-多任务版
目前的Web服务器,不能支持多用户同时访问,只能一个一个的处理客户端的请求,那么如何开发多任务版的web服务器同时处理 多个客户端的请求?
可以使用多线程,比进程更加节省内存资源。
多任务版web服务器程序的实现步骤:
- 当客户端和服务端建立连接成功,创建子线程,使用子线程专门处理客户端的请求,防止主线程阻塞。
1
2
3
4while True:
conn_socket, ip_port = tcp_server_socket.accept()
# 开辟子线程并执行对应的任务
sub_thread = threading.Thread(target=handle_client_request, args=(conn_socket,)) - 把创建的子线程设置成为守护主线程,防止主线程无法退出。
1
2
3
4# 开辟子线程并执行对应的任务
sub_thread = threading.Thread(target=handle_client_request, args=(conn_socket,))
sub_thread.setDaemon(True) # 设置守护主线程
sub_thread.start()
1 | import socket |
静态Web服务器-多任务版
静态Web服务器的问题
目前的Web服务器,不能支持多用户同时访问,只能一个一个的处理客户端的请求,那么如何开发多任务版的web服务器同时处理 多个客户端的请求?
- 可以使用多线程,比进程更加节省内存资源。多任务版web服务器程序的实现步骤:
- 当客户端和服务端建立连接成功,创建子线程,使用子线程专门处理客户端的请求,防止主线程阻塞。
1
2
3
4while True:
conn_socket, ip_port = tcp_server_socket.accept()
# 开辟子线程并执行对应的任务
sub_thread = threading.Thread(target=handle_client_request, args=(conn_socket,)) - 把创建的
子线程设置成为守护主线程,防止主线程无法退出。
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90import socket
import threading
# 处理客户端的请求
def handle_client_request(new_socket):
# 代码执行到此,说明连接建立成功
recv_client_data = new_socket.recv(4096)
if len(recv_client_data) == 0:
print("关闭浏览器了")
new_socket.close()
return
# 对二进制数据进行解码
recv_client_content = recv_client_data.decode("utf-8")
print(recv_client_content)
# 根据指定字符串进行分割, 最大分割次数指定2
request_list = recv_client_content.split(" ", maxsplit=2)
# 获取请求资源路径
request_path = request_list[1]
print(request_path)
# 判断请求的是否是根目录,如果条件成立,指定首页数据返回
if request_path == "/":
request_path = "/index.html"
try:
# 动态打开指定文件
with open("static" + request_path, "rb") as file:
# 读取文件数据
file_data = file.read()
except Exception as e:
# 请求资源不存在,返回404数据
# 响应行
response_line = "HTTP/1.1 404 Not Found\r\n"
# 响应头
response_header = "Server: PWS1.0\r\n"
with open("static/error.html", "rb") as file:
file_data = file.read()
# 响应体
response_body = file_data
# 拼接响应报文
response_data = (response_line + response_header + "\r\n").encode("utf-8") + response_body
# 发送数据
new_socket.send(response_data)
else:
# 响应行
response_line = "HTTP/1.1 200 OK\r\n"
# 响应头
response_header = "Server: PWS1.0\r\n"
# 响应体
response_body = file_data
# 拼接响应报文
response_data = (response_line + response_header + "\r\n").encode("utf-8") + response_body
# 发送数据
new_socket.send(response_data)
finally:
# 关闭服务与客户端的套接字
new_socket.close()
# 程序入口函数
def main():
# 创建tcp服务端套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口号复用, 程序退出端口立即释放
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
# 绑定端口号
tcp_server_socket.bind(("", 9000))
# 设置监听
tcp_server_socket.listen(128)
while True:
# 等待接受客户端的连接请求
new_socket, ip_port = tcp_server_socket.accept()
print(ip_port)
# 当客户端和服务器建立连接程,创建子线程
sub_thread = threading.Thread(target=handle_client_request, args=(new_socket,))
# 设置守护主线程
sub_thread.setDaemon(True)
# 启动子线程执行对应的任务
sub_thread.start()
if __name__ == '__main__':
main()
静态Web服务器-面向对象开发
实现步骤:
- 把提供服务的Web服务器抽象成一个类(HTTPWebServer)
1
class HttpWebServer(object):
- 提供Web服务器的初始化方法,在初始化方法里面创建socket对象
1
2def __init__(self):
# 初始化服务端套接字,设置监听,代码省略.. - 提供一个开启Web服务器的方法,让Web服务器处理客户端请求操作。
1
2
3
4
5
6def start(self):
while True:
service_client_socket, ip_port = self.tcp_server_socket.accept()
# 连接建立成功,开辟子线程处理客户端的请求
sub_thread = threading.Thread(target=self.handle_client_request, args=(service_client_socket,))
sub_thread.start()
以面向对象的方式开发静态Web服务器
1 | import socket |
命令行启动动态绑定端口号
- 开发命令行启动动态绑定端口号的静态web服务器
实现步骤:
- 获取执行python程序的终端命令行参数
1
sys.argv
- 判断参数的类型,设置端口号必须是整型
1
2
3
4if not sys.argv[1].isdigit():
print("启动命令如下: python3 xxx.py 9090")
return
port = int(sys.argv[1]) - 给Web服务器类的初始化方法添加一个端口号参数,用于绑定端口号
1
2def __init__(self, port):
self.tcp_server_socket.bind((“”, port))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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114import socket
import threading
import sys
# 定义web服务器类
class HttpWebServer(object):
def __init__(self, port):
# 创建tcp服务端套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口号复用, 程序退出端口立即释放
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
# 绑定端口号
tcp_server_socket.bind(("", port))
# 设置监听
tcp_server_socket.listen(128)
# 保存创建成功的服务器套接字
self.tcp_server_socket = tcp_server_socket
# 处理客户端的请求
def handle_client_request(new_socket):
# 代码执行到此,说明连接建立成功
recv_client_data = new_socket.recv(4096)
if len(recv_client_data) == 0:
print("关闭浏览器了")
new_socket.close()
return
# 对二进制数据进行解码
recv_client_content = recv_client_data.decode("utf-8")
print(recv_client_content)
# 根据指定字符串进行分割, 最大分割次数指定2
request_list = recv_client_content.split(" ", maxsplit=2)
# 获取请求资源路径
request_path = request_list[1]
print(request_path)
# 判断请求的是否是根目录,如果条件成立,指定首页数据返回
if request_path == "/":
request_path = "/index.html"
try:
# 动态打开指定文件
with open("static" + request_path, "rb") as file:
# 读取文件数据
file_data = file.read()
except Exception as e:
# 请求资源不存在,返回404数据
# 响应行
response_line = "HTTP/1.1 404 Not Found\r\n"
# 响应头
response_header = "Server: PWS1.0\r\n"
with open("static/error.html", "rb") as file:
file_data = file.read()
# 响应体
response_body = file_data
# 拼接响应报文
response_data = (response_line + response_header + "\r\n").encode("utf-8") + response_body
# 发送数据
new_socket.send(response_data)
else:
# 响应行
response_line = "HTTP/1.1 200 OK\r\n"
# 响应头
response_header = "Server: PWS1.0\r\n"
# 响应体
response_body = file_data
# 拼接响应报文
response_data = (response_line + response_header + "\r\n").encode("utf-8") + response_body
# 发送数据
new_socket.send(response_data)
finally:
# 关闭服务与客户端的套接字
new_socket.close()
# 启动web服务器进行工作
def start(self):
while True:
# 等待接受客户端的连接请求
new_socket, ip_port = self.tcp_server_socket.accept()
# 当客户端和服务器建立连接程,创建子线程
sub_thread = threading.Thread(target=self.handle_client_request, args=(new_socket,))
# 设置守护主线程
sub_thread.setDaemon(True)
# 启动子线程执行对应的任务
sub_thread.start()
# 程序入口函数
def main():
print(sys.argv)
# 判断命令行参数是否等于2,
if len(sys.argv) != 2:
print("执行命令如下: python3 xxx.py 8000")
return
# 判断字符串是否都是数字组成
if not sys.argv[1].isdigit():
print("执行命令如下: python3 xxx.py 8000")
return
# 获取终端命令行参数
port = int(sys.argv[1])
# 创建web服务器对象
web_server = HttpWebServer(port)
# 启动web服务器进行工作
web_server.start()
if __name__ == '__main__':
main()
Python程序操作MySQL数据库
安装pymysql第三方包:
1 | [root@liruilong ~]# yum -y install pymysql |
pymysql的使用:
- 导入 pymysql 包 : import pymysql
- 创建连接对象:调用pymysql模块中的connect()函数来创建连接对象
conn=connect(参数列表)
- 参数host:连接的mysql主机,如果本机是’localhost’
- 参数port:连接的mysql主机的端口,默认是3306
- 参数user:连接的用户名
- 参数password:连接的密码
- 参数database:数据库的名称
- 参数charset:通信采用的编码方式,推荐使用utf8
连接对象操作说明:
- 关闭连接 conn.close()
- 提交数据 conn.commit()
- 撤销数据 conn.rollback()
获取游标对象:
获取游标对象的目标就是要执行sql语句,完成对数据库的增、删、改、查操作。
1 | # 调用连接对象的cursor()方法获取游标对象 |
游标操作说明:
- 使用游标执行SQL语句: execute(operation [parameters ]) 执行SQL语句,返回受影响的行数,主要用于执行insert、update、delete、select等语句
- 获取查询结果集中的一条数据:cur.fetchone()返回一个元组, 如 (1,’张三’)
- 获取查询结果集中的所有数据: cur.fetchall()返回一个元组,如((1,’张三’),(2,’李四’))
- 关闭游标: cur.close(),表示和数据库操作完成
pymysql完成数据的查询操作
1 | import pymysql |
pymysql完成对数据的增删改
1 | import pymysql |
- conn.commit() 表示将修改操作提交到数据库
- conn.rollback() 表示回滚数据
防止SQL注入
什么是SQL注入?用户提交带有恶意的数据与SQL语句进行字符串方式的拼接,从而影响了SQL语句的语义,最终产生数据泄露的现象。
·如何防止SQL注入?SQL语句参数化
SQL语言中的参数使用%s来占位,此处不是python中的字符串格式化操作,将SQL语句中%s占位所需要的参数存在一个列表中,把参数列表传递给execute方法中第二个参数
防止SQL注入的示例代码:
1 | from pymysql import connect |
事务的介绍
事务就是用户定义的一系列执行SQL语句的操作, 这些操作要么完全地执行,要么完全地都不执行, 它是一个不可分割的工作执行单元。
事务的使用场景:在日常生活中,有时我们需要进行银行转账,这个银行转账操作背后就是需要执行多个SQL语句,假如这些SQL执行到一半突然停电了,那么就会导致这个功能只完成了一半,这种情况是不允许出现,要想解决这个问题就需要通过事务来完成。
事务的四大特性
原子性Atomicity
:一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性一致性Consistency
:数据库总是从一个一致性的状态转换到另一个一致性的状态。(在前面的例子中,一致性确保了,即使在转账过程中系统崩溃,支票账户中也不会损失200美元,因为事务最终没有提交,所以事务中所做的修改也不会保存到数据库中。)隔离性Isolation
:通常来说,一个事务所做的修改操作在提交事务之前,对于其他事务来说是不可见的。(在前面的例子中,当执行完第三条语句、第四条语句还未开始时,此时有另外的一个账户汇总程序开始运行,则其看到支票帐户的余额并没有被减去200美元。)持久性Durability
:一旦事务提交,则其所做的修改会永久保存到数据库。
事务的使用:
- 在使用事务之前,先要确保表的存储引擎是 InnoDB 类型, 只有这个类型才可以使用事务,MySQL数据库中表的存储引擎默认是 InnoDB 类型。
- 表的存储引擎说明:表的存储引擎就是提供存储数据一种机制,不同表的存储引擎提供不同的存储机制。
- 开启事务后执行修改命令,变更数据会保存到MySQL服务端的缓存文件中,而不维护到物理表中
- MySQL数据库默认采用自动提交(autocommit)模式,如果没有显示的开启一个事务,那么每条sql语句都会被当作一个事务执行提交的操作当设置autocommit=0就是取消了自动提交事务模式,直到显示的执行commit和rollback表示该事务结束。
- set autocommit = 0 表示取消自动提交事务模式,需要手动执行commit完成事务的提交
索引
索引的介绍
索引在MySQL中也叫做“键”,它是一个特殊的文件,它保存着数据表里所有记录的位置信息,更通俗的来说,数据库索引好比是一本书前面的目录,能加快数据库的查询速度。
应用场景:
当数据库中数据量很大时,查找数据会变得很慢,我们就可以通过索引来提高数据库的查询效率。
索引的使用
- 查看表中已有索引: show index from 表名;
- 主键列会自动创建索引
- 索引名不指定,默认使用字段名
验证索引性能操作:
1 | -- 开启运行时间监测: |
联合索引
联合索引又叫复合索引,即一个索引覆盖表中两个或者多个字段,一般用在多个字段一起查询的时候。
1 | -- 创建teacher表 |
联合索引的好处:减少磁盘空间开销,因为每创建一个索引,其实就是创建了一个索引文件,那么会增加磁盘空间的开销。
联合索引的最左原则
在使用联合索引的时候,我们要遵守一个最左原则,即index(name,age)支持 name 、name 和 age 组合查询,而不支持单独 age 查询,因为没有用到创建的联合索引。
最左原则示例:
1 | -- 下面的查询使用到了联合索引 |
在使用联合索引的查询数据时候一定要保证联合索引的最左侧字段出现在查询条件里面,否则联合索引失效
MySQL中索引的优点和缺点和使用原则
- 优点:. ==加快数据的查询速度==
- 缺点:. ==创建索引会耗费时间和占用磁盘空间,并且随着数据量的增加所耗费的时间也会增加==
使用原则:
- 通过优缺点对比,不是索引越多越好,而是需要自己合理的使用。
- 对经常
更新的表就避免对其进行过多索引的创建
,对经常用于查询的字段应该创建索引
, - 数据量小的表最好不要使用索引,因为由于数据较少,可能
查询全部数据花费的时间比遍历索引的时间还要短
,索引就可能不会产生优化效+ 果。 在一字段上相同值比较多不要建立索引
,比如在学生表的”性别”字段上只有男,女两个不同值。相反的,在一个字段上不同值较多可是建立索引。
闭包&装饰器
闭包的介绍:
我们前面已经学过了函数,我们知道当函数调用完,函数内定义的变量都销毁了,但是我们有时候需要保存函数内的这个变量,每次在这个变量的基础上完成一些列的操作,比如: 每次在这个变量的基础上和其它数字进行求和计算,那怎么办呢?我们就可以通过咱们今天学习的闭包来解决这个需求。闭包的定义:
在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包。
闭包的构成条件
通过闭包的定义,我们可以得知闭包的形成条件:
- 在函数嵌套(函数里面再定义函数)的前提下
- 内部函数使用了外部函数的变量(还包括外部函数的参数)
- 外部函数返回了内部函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# 定义一个外部函数
def func_out(num1):
# 定义一个内部函数
def func_inner(num2):
# 内部函数使用了外部函数的变量(num1)
result = num1 + num2
print("结果是:", result)
# 外部函数返回了内部函数,这里返回的内部函数就是闭包
return func_inner
# 创建闭包实例
f = func_out(1)
# 执行闭包
f(2)
f(3)结果是: 3
结果是: 4
闭包执行结果的说明:通过上面的输出结果可以看出闭包保存了外部函数内的变量num1,每次执行闭包都是在num1 = 1 基础上进行计算。
闭包的作用
闭包可以保存外部函数内的变量,不会随着外部函数调用完而销毁。注意点:
由于闭包引用了外部函数的变量,则外部函数的变量没有及时释放,消耗内存。
闭包的使用
案例
需求: 根据配置信息使用闭包实现不同人的对话信息,例如对话:
张三: 到北京了吗? 李四: 已经到了,放心吧。
- 定义外部函数接收不同的配置信息参数,参数是人名
- 定义内部函数接收对话信息参数
- 在内部函数里面把配置信息和对话信息进行拼接输出
功能代码的实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16# 外部函数
def config_name(name):
# 内部函数
def say_info(info):
print(name + ": " + info)
return say_info
tom = config_name("Tom")
tom("你好!")
tom("你好, 在吗?")
jerry = config_name("jerry")
jerry("不在, 不和玩!")运行结果:
Tom: 你好!
Tom: 你好, 在吗?
jerry: 不在, 不和玩!
闭包案例说明:闭包还可以提高代码的可重用性,不需要再手动定义额外的功能函数。
修改闭包内使用的外部变量
修改闭包内使用的外部变量的错误示例:
1 | # 定义一个外部函数 |
装饰器
装饰器的定义:
就是给已有函数增加额外功能的函数,它本质上就是一个闭包函数。
类似于AOP环绕通知的逆向应用
装饰器的功能特点:不修改已有函数的源代码
不修改已有函数的调用方式
给已有函数增加额外的功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22# 添加一个登录验证的功能
def check(fn):
def inner():
print("请先登录....")
fn()
return inner
def comment():
print("发表评论")
# 使用装饰器来装饰函数
comment = check(comment)
comment()
# 装饰器的基本雏形
# def decorator(fn): # fn:目标函数.
# def inner():
# '''执行函数之前'''
# fn() # 执行被装饰的函数
# '''执行函数之后'''
# return inner执行结果:
请先登录….
发表评论闭包函数有且
只有一个参数,必须是函数类型
,这样定义的函数才是装饰器
。写代码要遵循
开放封闭原则
,它规定已经实现的功能代码不允许被修改,但可以被扩展
。
装饰器的语法糖写法
如果有多个函数都需要添加登录验证的功能,每次都需要编写func = check(func)这样代码对已有函数进行装饰,这种做法还是比较麻烦。
Python给提供了一个装饰函数更加简单的写法,那就是语法糖,语法糖的书写格式是: @装饰器名字,通过语法糖的方式也可以完成对已有函数的装饰.
1 | # 添加一个登录验证的功能 |
装饰器的使用
装饰器的使用场景
- 函数执行时间的统计
- 输出日志信息
装饰器实现已有函数执行时间的统计
1 | import time |
…
99995
99996
99997
99998
99999
函数执行花费0.329066
通用装饰器的使用
装饰带有参数的函数
1 | # 添加输出日志的功能 |
–正在努力计算–
3
装饰带有返回值的函数
1 | # 添加输出日志的功能 |
–正在努力计算–
3
装饰带有不定长参数的函数
1 | # 添加输出日志的功能 |
–正在努力计算–
13
通用装饰器
1 | # 添加输出日志的功能 |
–正在努力计算–
13
–正在努力计算–
2
多个装饰器的使用
多个装饰器的装饰过程是: 离函数最近的装饰器先装饰,然后外面的装饰器再进行装饰,由内到外的装饰过程
1 | def make_div(func): |
带有参数的装饰器
带有参数的装饰器就是使用装饰器装饰函数的时候可以传入指定参数,语法格式: @装饰器(参数,...)
错误写法
:==装饰器只能接收一个参数,并且还是函数类型。==
1 | def decorator(fn, flag): |
Traceback (most recent call last):
File “/home/python/Desktop/test/hho.py”, line 12, in
@decorator(‘+’)
TypeError: decorator() missing 1 required positional argument: ‘flag’
正确写法
:==在装饰器外面再包裹上一个函数,让最外面的函数接收参数,返回的是装饰器,因为@符号后面必须是装饰器实例。==
在装饰器外面再包裹上一个函数,让最外面的函数接收参数,返回的是装饰器,因为@符号后面必须是装饰器实例。
1 | # 添加输出日志的功能 |
类装饰器的使用
装饰器还有一种特殊的用法就是类装饰器,就是通过定义一个类来装饰函数。
1 | class Check(object): |
- @Check 等价于 comment = Check(comment), 所以需要提供一个init方法,并多增加一个fn参数。
- 要想类的实例对象能够像函数一样调用,需要在类里面使用call方法,
把类的实例变成可调用对象(callable),也就是说可以像调用函数一样进行调用。
在call方法里进行对fn函数的装饰,可以添加额外的功能。
执行结果:
请先登陆…
发表评论
mini-web框架
web框架概述
web框架和web服务器的关系介绍:
前面已经学习过web服务器, 我们知道web服务器主要是接收用户的http请求,根据用户的请求返回不同的资源数据,但是之前我们开发的是静态web服务器,返回的都是静态资源数据,假如我们想要web服务器返回动态资源那么该如何进行处理呢?
使用web框架专门负责处理用户的动态资源请求,这个web框架其实就是一个为web服务器提供服务的应用程序,简称web框架。
关系说明:
web服务器接收浏览器发起的请求,如果是动态资源请求找web框架来处理
web框架负责处理浏览器的动态资源请求,把处理的结果发生给web服务器
web服务器再把响应结果发生给浏览器
静态资源:不需要经常变化的资源,这种资源web服务器可以提前准备好,比如: png/jpg/css/js等文件。
动态资源:和静态资源相反, 这种资源会经常变化,比如: 我们在京东浏览商品时经常会根据条件进行筛选,选择不同条件, 浏览的商品就不同,这种资源web服务器无法提前准备好,需要web框架来帮web服务器进行准备,在这里web服务器可以把.html的资源请求认为是动态资源请求交由web框架进行处理。
WSGI协议:Web服务器网关接口(Python Web Server Gateway Interface,缩写为WSGI)是为Python语言定义的Web服务器和Web应用程序或框架之间的一种简单而通用的接口。自从WSGI被开发出来以后,许多其它语言中也出现了类似接口。它是web服务器和web框架之间进行协同工作的一个规则,
WSGI协议规定web服务器把动态资源的请求信息传给web框架处理,web框架把处理好的结果返回给web服务器。
框架程序开发
框架职责介绍
- 接收web服务器的动态资源请求,给web服务器提供处理动态资源请求的服务。
动态资源判断
根据请求资源路径的后缀名进行判断
- 如果请求资源路径的后缀名是.html则是动态资源请求, 让web框架程序进行处理。
- 否则是静态资源请求,让web服务器程序进行处理。
web服务器程序(web.py)代码:
1 | import socket |
处理客户端的动态资源请求
- 创建web框架程序
- 接收web服务器的动态资源请求
- 处理web服务器的动态资源请求并把处理结果返回给web服务器
- web服务器把处理结果组装成响应报文发送给浏览器
web框架程序(framework.py)代码:
1 | """miniweb框架,负责处理动态资源请求""" |
模板替换功能开发,使用模拟数据替换模板变量
framework.py示例代码:
1 | # 获取首页数据 |
路由列表功能开发
- 什么是路由?路由就是请求的URL到处理函数的映射,也就是说提前把请求的URL和处理函数关联好。
- 路由列表:这么多的路由如何管理呢, 可以使用一个路由列表进行管理,通过路由列表保存每一个路由。
- 在路由列表添加路由,根据用户请求遍历路由列表处理用户请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20# 定义路由列表
route_list = [
("/index.html", index),
("/center.html", center)
]
# 处理动态资源请求
def handle_request(env):
# 获取动态请求资源路径
request_path = env["request_path"]
print("接收到的动态资源请求:", request_path)
# 遍历路由列表,选择执行的函数
for path, func in route_list:
if request_path == path:
result = func()
return result
else:
# 没有找到动态资源
result = not_found()
return result
装饰器方式的添加路由
前面我们已经实现了路由列表,但是每次添加路由都需要手动添加来完成,接下来我们想要完成路由的自动添加,可以通过装饰器来实现,在使用装饰器对处理函数进行装饰的时候我们需要知道装饰的函数和那个请求路径进行关联,也就是说装饰器需要接收一个url参数,这样我们定义的装饰器是一个带有参数的装饰器。
1 |
|
显示股票信息页面的开发
数据准备
1 | -- 创建数据库 |
- 根据sql语句查询股票信息
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
# 获取首页数据
def index():
# 响应状态
status = "200 OK";
# 响应头
response_header = [("Server", "PWS2.0")]
# 打开模板文件,读取数据
with open("template/index.html", "r") as file:
file_data = file.read()
# 处理后的数据, 从数据库查询
conn = pymysql.connect(host="localhost",
port=3306,
user="root",
password="mysql",
database="stock_db",
charset="utf8")
# 获取游标
cursor = conn.cursor()
# 查询sql语句
sql = "select * from info;"
# 执行sql
cursor.execute(sql)
# 获取结果集
result = cursor.fetchall()
print(result) - 使用查询数据替换模板变量
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
36
37
38
39
40
41
42
43
44
45
46
47
48# 获取首页数据
def index():
# 响应状态
status = "200 OK";
# 响应头
response_header = [("Server", "PWS2.0")]
# 打开模板文件,读取数据
with open("template/index.html", "r") as file:
file_data = file.read()
# 处理后的数据, 从数据库查询
conn = pymysql.connect(host="localhost",
port=3306,
user="root",
password="mysql",
database="stock_db",
charset="utf8")
# 获取游标
cursor = conn.cursor()
# 查询sql语句
sql = "select * from info;"
# 执行sql
cursor.execute(sql)
# 获取结果集
result = cursor.fetchall()
print(result)
data = ""
for row in result:
data += '''<tr>
<td>%s</td>
<td>%s</td>
<td>%s</td>
<td>%s</td>
<td>%s</td>
<td>%s</td>
<td>%s</td>
<td>%s</td>
<td><input type="button" value="添加" id="toAdd" name="toAdd" systemidvaule="000007"></td>
</tr>''' % row
# 替换模板文件中的模板遍历
result = file_data.replace("{%content%}", data)
return status, response_header, result
个人中心数据接口的开发
根据sql语句查询个人中心数据.将个人中心数据转成json字符串并返回
1 | # 个人中心数据接口开发 |
代码说明:
- json.dumps函数把字典转成json字符串:函数的第一个参数表示要把指定对象转成json字符串,参数的第二个参数ensure_ascii=False表示不使用ascii编码,可以在控制台显示中文。
- 响应头添加Content-Type表示指定数据的编码格式
ajax请求数据渲染个人中心页面
1 | # 获取个人中心数据 |
根据用户请求返回个人中心空模板文件数据,在个人中心模板文件添加ajax请求获取个人中心数据
1 | // 发送ajax请求获取个人中心页面数据 |
logging日志
logging日志的介绍
在现实生活中,记录日志非常重要,比如:银行转账时会有转账记录;飞机飞行过程中,会有个黑盒子(飞行数据记录器)记录着飞机的飞行过程,那在咱们python程序中想要记录程序在运行时所产生的日志信息,怎么做呢?
可以使用 logging 这个包来完成
记录程序日志信息的目的是:
- 可以很方便的了解程序的运行情况
- 可以分析用户的操作行为、喜好等信息
- 方便开发人员检查bug
==logging日志级别介绍==
日志等级可以分为5个,从低到高分别是:DEBUG,INFO,WARNING,ERROR,CRITICAL
日志等级说明:
DEBUG
:程序调试bug时使用INFO
:程序正常运行时使用WARNING
:程序未按预期运行时使用,但并不是错误,如:用户登录密码错误ERROR
:程序出错误时使用,如:IO操作失败CRITICAL
:特别严重的问题,导致程序不能再继续运行时使用,如:磁盘空间为空,一般很少使用
默认的是WARNING等级,当在WARNING或WARNING之上等级的才记录日志信息。
日志等级从低到高的顺序是: DEBUG < INFO < WARNING < ERROR < CRITICAL
logging日志的使用
在 logging 包中记录日志的方式有两种:
- 输出到控制台
- 保存到日志文件
日志信息输出到控制台的示例代码:日志信息只显示了大于等于WARNING级别的日志,这说明1
2
3
4
5
6
7import logging
logging.debug('这是一个debug级别的日志信息')
logging.info('这是一个info级别的日志信息')
logging.warning('这是一个warning级别的日志信息')
logging.error('这是一个error级别的日志信息')
logging.critical('这是一个critical级别的日志信息')·默认的日志级别设置为WARNING·
logging日志等级和输出格式的设置:1
2
3
4
5
6
7
8
9
10
11import logging
# 设置日志等级和输出日志格式
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
logging.debug('这是一个debug级别的日志信息')
logging.info('这是一个info级别的日志信息')
logging.warning('这是一个warning级别的日志信息')
logging.error('这是一个error级别的日志信息')
logging.critical('这是一个critical级别的日志信息') - level 表示设置的日志等级
- format 表示日志的输出格式, 参数说明:
- %(levelname)s: 打印日志级别名称
- %(filename)s: 打印当前执行程序名
- %(lineno)d: 打印日志的当前行号
- %(asctime)s: 打印日志的时间
- %(message)s: 打印日志信息
日志信息保存到日志文件的示例代码:
1 | import logging |
logging日志在mini-web项目中应用
web.py 程序使用logging日志示例:
1.程序入口模块设置logging日志的设置
1 | import socket |
2.INFO级别的日志输出,示例代码:
1 | # 判断是否是动态资源请求 |
3.WARNING级别的日志输出,示例代码:
1 | # 获取命令行参数判断长度 |
framework.py 程序使用logging日志示例:
4.ERROR级别的日志输出,示例代码:
1 | # 处理动态资源请求 |
==logging日志配置信息在程序入口模块设置一次,整个程序都可以生效==。logging.basicConfig
表示 logging 日志配置操作