PYTHON总结

Python基础语法

循环,终止,字符换格式化

while循环

语法:
whlie 条件:
执行代码
执行代码
'''
当条件成立时,代码会一直执行。
当条件不成立时,条件就不会执行。
'''
条件: 1.比较条件 2. 布尔值条件 3.变量条件
最后while循环都会讲条件转换成为布尔值,FalseTrue

循环终止参数

循环终止,只有在循环是才能使用。
分为2种:
1.**break** 作用:帮助终止循环,只要代码执行到break时,不论是后面有多少代码,都不会执行。
2.**continue** 作用:结束本次循环,开始下一次循环,遇到 continue,后面不论多少代码,都会停止到最初代码。

while 和 else使用

while 条件: # 当while的条件不成立时
代码...
代码...
代码...
else: # 执行else中代码
代码....
当遇到break时,代码不会执行,因为break中止循环

字符串格式化

字符串格式化分为:
1.%百分号
%百分号的使用
%s:就是一个占位符号
name = wkx
text = “我叫什么%s”%(“wkx”) 字符串
text = “我叫什么%s”%(name) 变量
text=“我叫什么%(name)”%{“wkx”}
当遇到内容中有百分号%时,又想用%号占位符就需要特殊处理:%%使用两个百分号
2.format
format
text = “我叫什么{}” .format(name) 变量
text = “我叫什么{}” .format123) 字符串或者其他类型
需要复用的情况下需要,设置一个标志性的符号进行复用
其他用法
3.1415926 {:.2f} 3.14 保留小数点后两位
3.1415926 {:+.2f} +3.14 带符号保留小数点后两位
-1 {:-.2f} -1.00 带符号保留小数点后两位
2.71828 {:.0f} 3 不带小数
5 {:0>2d} 05 数字补零 (填充左边, 宽度为2)
5 {:x<4d} 5xxx 数字补x (填充右边, 宽度为4)
10 {:x<4d} 10xx 数字补x (填充右边, 宽度为4)
1000000 {:,} 1,000,000 以逗号分隔的数字格式
0.25 {:.2%} 25.00% 百分比格式
1000000000 {:.2e} 1.00e+09 指数记法
13 {:>10d} 13 右对齐 (默认, 宽度为10)
13 {:<10d} 13 左对齐 (宽度为10)
13 {:^10d} 13 中间对齐 (宽度为10)
3.f
f
text = f“我叫什么{‘name’}” 变量
text = f“我叫什么{‘123’}” 字符串或者其他类型
text = f“我叫什么{19+2=}” 可以在内部进行计算

for循环

for循环 对每个元素进行遍历(已知的)
语法:
for i in 元素或者长度:
print(i) 打印
元素或者长度值 赋值给了i ,通过i打印获取每次迭代的内容

运算符

运算符 + - / * % ** //
+ 两个数相加,字符串也可以
- 两个数向减
/ 两个数向除
* 两个数相乘,字符串也可以相乘的数必须是整数
% 取两个数的余数
** 取次方
// 取两个数的 整数部分
比较运算符
== != <> > < >= <=
== 比较两个对象是否相等
!= 判断两个对象不等于
> 大于
< 小于
>= 大于等于
<= 小于等于
赋值运算
= += -= *= /= %= **= //=
= 赋值
+= 加法赋值
-= 减法赋值
*= 乘法赋值
/= 除法赋值
%= 取余赋值
**= 次方赋值
//= 取整赋值
成员运算符
in not in
not in 判断一个对象在没在另一个对象当中 存在False ,不存在True
in 判断一个对象在没在另一个对象当中 返回True/False
逻辑运算符
and or not
and
a and d 当a d 都正确的情况下 返回True ,有一个不存在 就返回False
or
a or d 当a d 一个存在就会返回 True
not
not a 当a 存在返回False 不存在返回True

运算符的优先级

加减乘除>比较>not >and >or

运算符补充

print(10//3) # 整除只去 整数部分
print(10%6) # 整除后 取小数部分
print(2**2) # 开平方 指数
print((1*1)+1) # 先计算括号内的
+ 合并 字符串 列表 元组 (两个值进行合并一起)
* 复制 字符串 列表 元组(将元组*整数 获取的值)
in 判断存在 字符串 列表 元组 字典 集合
not in 判断不存在 字符串 列表 元组 字典 集合
max() 获取数据的最大值
min() 获取数据的最小值
enumeratre() 作用只针对可迭代的类型 循环获取对应的下标索引已经值

循环与判断高级用法

三元运算(if)

如果程序中有简单的if else判断条件 就可以使用三元运算
做简单的逻辑运算
结果 = 条件成立结果 if 条件 else不成立结果:
条件成立:
name = 666 if 1==1 else 999
name 输出 666
条件不成立
name = 666 if 1==2 else 999
name输出 999

推导式(for)

推导式 就是 for 循环写到一行中
示例:
[接受值 for i in 循环内容例如:(range10)) 可以写入条件 if ]
列表中的推导式案例
name = [i for i in range(10)]
同时列表推导式中可以写上条件
name = [i for i in range(10) if i > 6]
集合和字典都是相同
特殊情况:
如果是元组推导式就会变成一个生成器
data = (i for i in range10))
# data 是一个生成器可以用 next()取值,也可以用for循环取值
for i in data:
print(i)
列表推导式:在列表内的作用域,只能在自己的作用域运行
列表推导式和在外部的for循环一样,当遇到函数时,
for i in range10):
print(i) 结果就是 9
for循环内部打印是1....9
而在for 循环外部打印的结果 9 获取的是for循环最后的一个值
匿名函数
[lambda x :x+i for i in range(10)]
这个i 就是 9 和上面在为外部的循环一样。
for循环内部有自己的作用域,而获取的值就是for循环最后的一个值。9

条件判断和输出

条件

单条件语句
if "条件" :
"条件成立后执行的代码"
"条件成立后执行的代码"
"条件成立后执行的代码"
else:
"条件不成立执行的代码"
"条件不成立执行的代码"
"条件不成立执行的代码"
多条件语句
if 条件A:
A 成立 执行代码
elif 条件B:
B成立 执行代码
elif 条件C:
C成立 执行代码
......
else:
都不成立就执行else的代码

输出

输出是实现用户和程序之间的交互
在用户输出的内容都是字符串的格式
name = input("输入账户:") 输出的内容是字符串

进制和编码

进制

python代码的运行方式:脚本式(用pycharm)
交互式(用终端)用于测试
计算机底层都是 0101 储存的 被称为2进制
2进制 满2进一位(整数存在)
8进制 满8进一位(字符串存在)
10进制 满10进一位(字符串存在)
16进制 满16进一位 (字符串存在)
10进制 可以转换为 2进制 8进制 16进制
2进制 只能通过10进制转换成其他进制
8进制 只能通过10进制转换成其他进制
16进制 只能通过10进制转换成其他进制
10进制通过转换:
bin() 转换2进制
oct() 转换8进制
hex() 转换16进制
其他进制转换10进制
int(“2进制”,base=2
int(“8进制”,base=8
int(“10进制”,base=16

计算机的单位

计算机单位:
b 位 最小
B 字节 8位一个字节
KB 千字节 1024个字节 = 1KB
M 兆 1024KB = 1M
G 千兆 1024M = 1G
T 万亿字节 1T = 1024G

编码

编码:
文字和2进制的对照表
ascii编码
1 个字节表示字母2进制对对应关系 2**8 = 256
gb-2312编码 由国家信息委员会制作(1980年)
gbk 对gb-2312扩展 包含中日韩等文字(1995年)
gbk 双字节表示对应关系 2**16 =65536
unicode被称为万国码,为每个文字都分配了一个码位(2进制表示)
ucs2 用固定的2个字节表示文字 2**16 =65536
ucs4 用固定的4个字节表示文字 2**32=4294967296
utf-8编码
包含所有文字和2进制对应关系,对unicode的压缩
对应用范围 进行表示
0000-007f 1个字节
0080-07ff 2字节
0800-ffff 3个字节
10000-10ffff 4个字节
中文都是第三个模板 ,都是3个字节
转换为2进制对应的数,按照6位对应模板,不够就补0

数据类型

整形

整形:int 就是数字123
公共功能:+-*/
转换:
布尔转换整形 True = 0 False = 1
字符串转整形 只能时 数字才能转换“666”这种
python 3 只有只有一种 整形(int
python 2 有两种代表:长整型(long无限制) 和整形(int

布尔型

任何数据都可以转换为布尔型
True 只要带有数值 都是true
False 全部的空字符串空字典,0 都是false
在做条件是,自动转换为布尔类型

字符串

1.独有功能:
1.判断开头是什么
字符串变量和字符串.startswith(“判断开头内容”)
返回结果True/False 赋值给新的变量
2.判断以什么结尾
字符串变量和字符串.endswith(“判断结尾内容”)
返回结果True/False 赋值给新的变量
3.判断是否时十进制整数
字符串变量和字符串.isdecimal()
返回结果True/False 赋值给新的变量
4.取两边的空白,制表符,换行符
字符串变量和字符串.strip()
取除左空白
字符串或者字符串变量名赋值.lstrip()
去除右空白
字符串或者字符串变量名赋值.rlstrip()
5.全部将内容大写
不改变原先的值,重新赋值。
字符串或者字符串变量名赋值.upper()
6.全部将内容小写
不改变原先的值,重新赋值。
字符串或者字符串变量名赋值.lower()
7.字符串指定的内容进行替换
不改变原先的值,重新赋值
字符串或者字符串变量名赋值.replace(“字符串原内容”,“替换内容”)
8.字符串切割
不改变原先,重新赋值
输出一个列表类型
根据字符串的指定内容进行切割
字符串或者字符串变量名赋值.split(“切割的符号或者内容”)
split(“切割的符号或者内容”,“加入整数,按照数字进行切割几个(1从左到右第一个)”)
切割完成后输出一个新的(列表类型)
9.字符串拼接
将内容字符串进行拼接成为 新字符串
接受变量 = “拼接的符号(可以空值)”.join(需要拼接的内容)
10.字符串格式化
接受变量 = “{}{}”.format(传入的信息或者时变量)
11.字符串转换字节类型
接受变量 = 转换变量.encode(“utf-8”)
将字符串转换为字节类型
接受变量 = 转换变量.decode(“utf-8”)
将字节类型转换为字符串
12.将字符串居中,居左,居右
接受变量 = 转换变量.center(填补数量,“填补符号”)居中
ljust(填补数量,“填充符号”) 左
rjust(填补数量,“填充符号”) 右
13.自动填充补零
接受变量 = 转换变量.zfill(填充数量)
主要作用处理二进制数据
14. .isalpha()
字符串中最少一个是字母 返回true or false
15. .isdigit()
字符串必须是纯数字 返回true or false
16. .isalnum()
字符串必须是字母或者字符 不能是其他 返回true or false
17. .isspace()
只是空白的情况下 返回true 否则返回false
公共功能:
相加,相乘
相加:字符串+字符串
相乘:字符串+整数
1.len(内容变量)获取长度
2.加索引取字符(只能获取,不能修改,因为字符串时最小单位)
3.切片(取的是字符串的一片的内容(前取后不去))
4.步长(跳着去获取数据[范围开始:范围结束:跳着的范围])(生成一个新的数据)
5.循环while for 循环
6.range(数字)创建一系列的数字
注意:字符串在创建时不可以被修改

列表类型

list []

有序的可变的容器,内部可以放不同的类型数据,有序:先进后出,可变的:内部元素可以修改
独有功能
1.列表追加(默认为最后一个)
列表.append(追加的值或者变量)
默认追加到最后一个
2.批量追加
将一个列表添加到另一个列表中
列表.extend(追加的列表)
追加的必须是列表
3.插入(插入指定的位置)
列表.insert(插如索引位置,插入元素(变量,各种类型))
4.从列表根据值删除
如果删除的值不存在列表会报错
列表.remove(删除的值)
5.根据索引位置删除
不输入索引按照默认最后一个开始删除
获取删除的值 = 列表.pop(索引位置)
pop可以获取到删除的值
6.清空列表
清空成为空列表
列表.clear()
7.根据元素获取索引位置
接受值在列表的索引位置 = 列表.index(元素)
8.列表元素排序
转换为unicode的编码进行排序
列表.sort()
注意:不能有多种数据类型在列表中进行排序
9.列表翻转
列表.reverse()
将列表中的数据进行翻转。
列表的公共功能
相加,相乘
相加:字符串+字符串
相乘:字符串+整数
1.运算符 in 判断元素是否在列表中
2.获取长度 len()
3.索引(可以在获取索引对索引的值进行 读 改 ,删de)
del 只能对列表中的元素删除
4.切片(取前不取后)
5.步长(取前不去后)(生成一个新的数据)
6.for循环
列表是可以嵌套的

元组

tuple()

有序的不可变的容器,可以放不同的元素在里面
不可以修改,可以针对在元组内部的可变元素进行修改
在元组后面加上,(11,)因为这样系统才认为这是元组,如果(11)那么系统会认为是int
元组的公共功能
相加,相乘
相加:字符串+字符串
相乘:字符串+整数
1.运算符 in 判断元素是否在列表中
2.获取长度 len()
3.索引(可以在获取索引对索引的值进行 读 改 ,删de)
del 只能对列表中的元素删除
4.切片(取前不取后)(生成一个新的数据)
5.步长(取前不去后)(生成一个新的数据)
6.for循环
7.tuples.index(0) # 获取元素在当前元组的下标
元组是可以嵌套的

集合

set{}

集合(set
无序可变,不可重复的容器
无序:不可以通过索引取值
可变:可以删除,可以添加
不可以重复
元素必须哈希
可哈希数据:int bool str tuple
不可哈希: listsetdict
查找速度特别快

独有功能

1.add()
作用给集合添加元素
2.删除元素
集合2.discard()
删除集合的元素
3.交集
两个集合相同的值
集合2.intersection(集合1
4.并集
将两个集合重复的剔除
集合2..union(集合1
5.差集
取两个集合没有的值
集合1.difference(集合2
公共功能
- 计算差集
&交集
|计算并集
len()获取长度
for循环

字典

dict{}

无序,键不可重复,元素只能是键值对的可变容器
键必须是可哈希的 int str bool tuple
不可以:set dict list
获取的结果更明显

独有功能

1.通过键获取值
字典.get(“键”)
2.获取所有的键
字典.keys()
获取一个高仿的列表,列表内都是键
可以for循环,可以别in的判断
3.获取所有的值
字典.values()
获取一个高仿的列表,列表内都是值
可以for循环,可以别in的判断
4.获取所有的键值
字典.items()
得到的结果是一个元组[(键,值),(键,值)]列表内部套元组,可以循环获取
5.设置新值
在不存在的情况,可以设置值
字典.setdefault(“键”,值)
6.更新字典键值对
在字典中,如果字典中没有这键值对,就添加,如果有就更新值
字典.update({“键”:值,“键”:值})
7.从字典中移除键值对
通过键移除
赋值变量 = 字典.pop(“键”)
可以将移除的值赋值给 变量
8.移除键值对(后进先出)
按照顺序移除
变量 = 字典.popitem()
获取到的是个元组(“键”,值)
字典的公共功能
并集(重复的剔除)
长度 len()
in 是否包含
通过索引(键)获取值
根据键修改值,添加值,del 删除键值对
for 循环 可以单独循环 键 值

浮点型

float
在转换整数时,只保留整数位
round(变量,保留多少位的整数)
精确小数:
使用decimal模块
decimal.Decimal(小数)

补充点

pass 的做用:确保语言的完整性
is 和 == 的区别 == 用于比较两个值相等 is 表示两个值内存地址是否一直
none 是个空值

关于可变哈希与不可变

哈希(散列计算),可以将任意长度的输出,通过散列算法变为固定长度输出,简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
1.可哈希类型:
数字类型(intfloatbool)字符串str、元组tuple
注意:intfloat类型通过hash计算后还是原来的值,取决于__hash__魔术方法的运算过程
bool:在通过hash运算后为1,0
可以理解为:当前变量值为key,那么hash运算后的值为value
使用str类型举例:
string1 = 'string'
string2 = 'str' + 'ing'
print(string2 == string1) # True
# 使用is判断对比(is内存地址是否相同)
print(string1 is string2) # True 说明内存地址相同
# 查看当前两个值的内存地址[是相同的]
print(id(string1)) # 2243838658608
print(id(string2)) # 2243838658608
# 通过hash函数查看计算后的哈希值
print(hash(string1), hash(string2)) # 1023839165698568095 1023839165698568095
# 如果可哈西变量发生变化,那么内存地址也会进行变化,[string2发生变化,就不会指向string1的内存地址,而是找到一个新的内存地址存储]
可以断定: 当前两个变量指向是同一块内存地址,那么hash值也相同,也满足了hash表的特点,通过变量元素找到key的内存地址
2.不可哈希类型:
list set dict(dict中得必须是可哈希类型的)
对于可变对象而言,比如一个列表,更改列表的值,但是对象的地址本身是不变的,也就是说不同的Key,映射到了相同的Value,这显然是不符合哈希值的特性的,即出现了哈希运算里面的冲突
可以理解为: 不可哈希类型中内部的值是可以变化的,但是占用的内存地址是不会变化的
利用列表为举例:
lis = [11,22,33]
print(id(lis)) # 内存地址为:2295695012736
lis.append('wkxxx') # 添加一个新的元素
print(id(lis)) # 内存地址为:2295695012736
内存地址不会变化,但是里面的值发生了变化
3.为什么dict中得key必须是可哈希的?
字典的哈希表实现使用从键值计算的哈希值来查找键
比如:
d = {[1, 2]: '100'} # 构造一个字典,key是列表[1,2] ,是一个可变对象,是不可哈希的
print(d[[1, 2]]) # 通过key去访问字典的值,TypeError错误
但是由于列表list是可变对象,虽然这两行的列表值一样,但是他们并不是同一个对象,它们的存储地址是不一样的,即id是不一样的,id不一样也导致了根据id计算得到的哈希值是不一样的,自然没有办法找到原来的那一个
'''
因为(不可哈希)可变类型,虽然值是相同的,但是内存地址不同,无法找到。
为什么用可哈希(不可变类型),你是用的值在内存中存储的都是相同的位置,比如:'xxx' ,那么怕10个变量使用这个值,那么也是执行当前这个值得内存地址的。另外字典的哈希表时从key进行计算哈希找到value的。如果是(不可哈希)可变类型,那么永远也找不到对应value
'''

Python文件操作

文件的打开方式

1.文件内容
文件是由于字节和字符串组成的文本。
在图片中是一个个的像素点组成的。
字节可以代表2进制的信息和图片。
2.关于文件的路径
文件路径分为2种:
1.相对路径:程序在运行时,相同文件夹下不同的文件关系。
2.绝对路径:完整的路径(不推荐使用)
3.转换字节
encode(utf-8) 将字符串转换为字节
decode(utf-8) 将字节转换字符串
4.文件的打开方式
模式的分类:
b:字节类型,读取的是二进制数据,字节形式存在
t:文本类型,读取的是二进制数据,内部会根据encoding转换成为字符串类型
5.关于图片和视频
1.图片没办法转换,只能读取二进制字节
2.打开文件 使用rd(只是读取了2进制内容)

文件的模式

1.写入模式

w模式:
w模式:没有文件,创建文件。存在文件清空文件,在进行写入。
wb:写入文件的字节类型
wt:写入文件的文本类型,内部会根据encoding()进行转换 简写w
x模式:
x模式:写入模式,没有文件创建文件,如果文件存在就会报错。
xb:写入文件的字节类型
xt:写入文件的文本类型,内部会根据encoding()进行转换 简写x
a模式:
a模式:写入模式,打开文件,从尾部增加写入,不存在文件,创建在写入。(光标在内容尾部)
ab:写入文件的字节类型
at:写入文件的文本类型,内部会根据encoding()进行转换 简写 a

2.读取模式

r模式:
r模式:读取模式,读取文件的内容。
rb:读取文件的字节类型(主要用于读取图片,视频。因为不需要进行转换)
rt:读取文件的文本类型,内部会根据encoding()进行转换,得到的读取结果就是字符串 键写r

3.带+模式(能读能写)

在+号模式下面需要注意:
1.如果先读在写,不会覆盖之前的内容。因为:已经将内容读取,光标的位置在尾部
2.如果先写在读,就会覆盖文件的原有内容。因为:光标起始位置在起始,就会覆盖原有的内容,读取覆盖内容后面的原始内容。
r+ = rt+ = rb+ 追加
即可读又可以写,写在文件的尾部(文件不会创建)
w+ = wt+= wb+ 重置内容 写入新内容
光标位置都是起始位置(读取时,清空文件。)没有文件创建文件
a+ = at+ = ab+ 追加
光标位置在尾部(读取时,需要现设置光标的位置)

对文件的操作

1.读取:

使用:
接收的对象 = open('文件路径','读取格式',encoding='utf-8')
对象.read()
对象.close()
endcoding 参数不适用与rb模式 二进制模式不接受编码参数,只能在读取时使用decode转换
案例:
1. r 模式
f = open('1.txt','r')
f.read() 读取的是字符串
f.close() 关闭文件
2.rb模式 # 可以读取图片的二进制
f = open('1.txt','rb)
f.read().decode('utf-8') 读取内容字节类型需要转为utf-8
f.close() 关闭文件
3.r+模式
f = open('1.txt','r')
f.read() 读取的是字符串
f.close() 关闭文件
4.rt模式内部转换utf-8
f = open('1.txt','rt')
f.read() 读取的是字符串
f.close() 关闭文件

2.写入文件

使用:
接收的对象 = open('文件路径','打开格式')
对象.write('写入内容'.encdoe('utf-8')) 写入
对象.close() 关闭
在使用w模式时如果文件不存在都可以创建文件
wb模式写入字节,其他的w模式默认写入字符串
案例:
1. r 模式
f = open('1.txt','w')
f.write('写入内容') 写入
f.close() 关闭文件
2.rb模式 # 可以读取图片的二进制
f = open('1.txt','tb')
f.write('写入内容,字节不是字节需要转为字节')写入
f.close() 关闭文件
3.r+模式
f = open('1.txt','w+')
f.write('写入内容') 写入
f.close() 关闭文件
4.rt模式内部转换utf-8
f = open('1.txt','rt')
f.write('写入内容') 写入
f.close() 关闭文件

3.写入或者读取图片和视频

使用b模式,读取rd,写入wd
f_r = open('图片.png', 'rb')
f_w = open('新图片.png','wb')
f_w.write(f_r.read())
f_w.close() 关闭文件
f_r.close() 关闭文件
为什么使用b模式?
因为图片和视频都是二进制进行存储的就需要是用b模式进行打开或者写入。

4.读写的常见功能

1.读功能
1.read()读取文件全部内容或者几个字符(用)
变量 = 对象.read() 读取全部内容
变量 = 对象.read(加上整数值),模式:rb,读取多少字节。模式:rt = r,读取多少字符串
2.readline()读取文件内部一行数据
变量 = 对象.readline() 只读一行数据,取决于模式不同:rb,读取字 节。rt,读取字符串
3.readlines() 读取全部内容,返回一个列表
变量 = 对象.readlines() 读取全部内容,返回一个列表
4.使用for循环读取大型文件 (用)
当读取内容完毕后,自动停止。
不需要进行文件对象的操作,直接进行for循环对象就可以读取内容。
5.写功能
1.write(内容)写入内容。 用于:w模式和a模式
对象.write(内容)
2.flush() 刷到硬盘
对象.flush(),在写入文件时,会将内容写入电脑缓冲区,会出现数据不全 的情况。使用flush()立即存入电脑硬盘中。
3.seek()移动光标位置
无论模式是什么:移动的永远是字节的位置
对象.seek(移动的整数A),这个A指的是移动光标的字节位置。
在a模式下,write写入文件时,seek不会修改光标的位置,a模式永远是先将 光标移动到尾部。
4.tell()获取光标的位置
变量 = 对象.tell() 获取光标位置是按照字节计算

6.文件打开方式上下文管理

with open("文件地址",mode="打开模式",按照b还是t使用字符串(encode(utf-8))) as 新的署名(文件对象):
代码....
当代码执行完毕后,自动关闭文件,不需要手动关闭。
方便快捷。
with 同时支持 多个 上下文管理执行。
with open(路径,打开方式)as 文件对象,open(路 径,打开方式)as 文件对象;
案例:
with open('图片.png', 'rb') as f_r:
with open('新图片.png',"wb") as f_w:
f_w.write(f_r.read())

其他的文件操作

1.csv文件操作

属于在文件中以固定符号进行分开的文件格式。
一般利用字符串的.split("符号")进行分割。
利用.strip()进行清除文件中的换行符。

2.ini文件格式

例如uwgis配置文件,mysql配置文件格式等等
ini格式文件 类似
[节点]
内容....
A键 = b值....
[节点]
内容....
import configparser
1.创建对象
ini = configparser.ConfigParser()
2.导入ini文件路径
ini.read('m.ini',encoding='utf-8') # 读取文件
3.使用方法
ini.sections() # 读取全部的节点
ini.items('mysql') # 获取当前节点下的k v 格式[(k,v)...]
ini.get('mysql','a') # 获取节点mysql下的a对应的val
ini.has_section('mysql') # 判断节点是否存在 返回结果True/False
添加节点
ini.add_section('nginx') # 将节点添加到文件中 如果只执行add_section的话添加内存没有写入文件
ini.write(open('m.ini',mode="w",encoding="utf-8"))
添加节点下的k,v
ini.set('nginx','测试key','测试节点val')
ini.write(open('m.ini',mode="w",encoding="utf-8"))
删除节点
ini.remove_section("mysql")
ini.write(open('m.ini',mode="w",encoding="utf-8"))
删除节点k,v
ini.remove_option('uwgis','b')
ini.write(open('m.ini',mode="w",encoding="utf-8"))

3.xml格式文件操作

与html相似
存储,可用来存放配置文件,例如:java的配置文件。
传输,网络传输时以这种格式存在,例如:早期ajax传输的数据、soap协议等。
存放配置文件 java
传输,网络传输时以这种格式存在
微信公众号时xml格式
结构
<data>
<节点1 属性>
<名称 属性>值 <名称>
</节点>
<节点1>
内容
</节点>
</data>
导包:
from xml.etree import ElementTree
查看:
from xml.etree import ElementTree as et(定义简约名)
1.打开xml文件,创建一个xml文件对象
对象 = et.parse(“xml文件地址”)
2.获取xml下面的跟标签
跟标签变量 = 对象.getroot()
3.获取跟标签下面的全部子标签内容
子标签变量 = 跟标签变量.findall(“子标签名字”)
4.获取些节点的内容
子标签变量 = 跟标签变量.find(“子标签名字”)
5.获取子标签下面的全部内容
变量 = 子标签变量.find(“子标签下面的标签”)
获取详细的标签内容的全部值
变量.tag 子标签下面的标签名称
变量.attrid 子标签下面的标签属性
变量.text 子标签下面的标签值
修改
1.打开xml文件,创建一个xml文件对象
对象 = et.parse(“xml文件地址”)
2.获取xml下面的跟标签
跟标签变量 = 对象.getroot()
3.修改标签内的值
修改了内存的值,没有存储在文件中
变量 = 对象.find("子标签").find("子标签下面的标签名"
变量.text = "修改的值"
存放在文件中
对象 = et. ElementTree(变量)
对象.write(“文件路径”,encoding=“”utf-8”)
4.添加属性
只是修改了内存的值,没有存在文件中
变量 = 对象.find("子标签").find("子标签下面的标签名"
变量.sat(“属性名字”,“属性值”)
存放在文件中
对象 = et. ElementTree(变量)
对象.write(“文件路径”,encoding=“”utf-8”)
5.删除节点中的子标签
变量 = 对象.find(“子标签”)
跟标签对象.remove(变量)
创建对象
1.创建跟标签
跟标签对象 = et.Element(“跟节点名称”)
2.创建子节点
子节点对象 = et.Element(“子标签名字”,标签属性({“键”:“值”}))
3.创建子节点下面的内容节点
内容节点 = et.Element(“内容标签名字”,标签属性({“键”:“值”}))
4.添加将内容节点添加到字节下面
子节点对象.append(内容节点)
5.将子节点添加到跟标签下面
跟标签对象 .append(子节点对象)
6.写入文件
变量 = et.ElementTree(跟标签对象)
变量 .write(“文件地址.xml”,encoding=“utf-8”,short_empty_elements=False
short_empty_elements=False
参数:False 长标签
参数:True 存短标签
通过网络传输获取
可以使用变量进行接受xml标签
变量 = “xml内容”
变量 = et.XML("放入请求的xml变量")
读取节点数据 循环获取 字标签
for i in 变量:
j = i.tag:(获取跟标签下的字标签)
for f in j:(获取字标签内容标签的内容)
j.tag 名字
j.attrib 属性
j.text 值

4.excel文件

1.导入文件
from openpyxl import load_workbook
2.创建excel的 路径对象
对象 = load_workbook("excel路径")
3.sheet的相关操作
1.获取sheet的全部名称
对象 = 对象.sheetnames
2.选择sheet名称获取
sheet对象(名称) = 对象["sheet的名称"]
3.获取sheet对象下面的某行结果内容
变量 = heet对象.cell(“行”,“列”)
4.根据索引位置获取sheet的对象获取
sheet对象(索引) = wb.worksheets[sheet索引位置]
5.循环获取全部的sheet
for i in 对象.sheetnames 获取全部的sheet
sheet对象 = wb[i] 为每一个sheet创建一个对象
变量 = sheet对象.cell(“行”,“类”) 获取sheet和行列结果
变量.value 获取结果
4.读取sheet下面的单元格操作
1.创建excel的 路径对象
对象 = load_workbook("excel路径")
2.根据索引位置获取sheet的对象获取
sheet对象(索引) = wb.worksheets[sheet索引位置]
3.获取sheet行和列单元格信息 ,从1开始
变量 = sheet对象.cell(行,列)
变量.value 获取文本信息
变量.style 获取样式
变量.font 字体
变量.alignment 排列情况
4.根据excel表的位置获取单元格对象内容
变量 = sheet对象["单元格位置:A1...M2"]
5.获取行内全部的单元格对象
变量 = sheet对象["指定的行名"]
6.获取全部行的内容某行者整行
变量 = sheet对象.rows
变量[索引].value 索引 制定一行者某些数据内容
7.获取全部列某列或者整列
变量 = 对象.columns
变量[索引].value 引 制定一行者某些数据内容
5.读取合并单元格
sheet对象.cell[列,行]
当读取合并的内容是正常读取,但是合并的格结果是个none
一行能读出来结果,一行读出来是none
6.写入excel表
原文件:
1.创建excel的 路径对象
对象 = load_workbook("excel路径")
2.根据索引位置获取sheet的对象获取
sheet对象(索引) = 对象.worksheets[sheet索引位置]
3.找到需要写入的表行
变量 = sheet对象.cell(行,列)
变量.value = “更新新值”
4.储存
对象.save(“文件原地址/新”)
新建文件
1.创建一张excel
对象 = workbook.workbook
2.选中sheet
sheet对象(索引) = 对象.worksheets[sheet索引位置]
3.找到需要写入的表行
变量 = sheet对象.cell(行,列)
变量.value = “更新新值”
4.储存
对象.save(“文件原地址/新”)
7.修改sheet名字和添加
1.修改sheet名字
1.获取sheet对象
sheet对象(索引) = 对象.worksheets[sheet索引位置]
2.修改sheet名字
sheet对象(索引).title = “新名字”
3.储存
对象.save(“文件原地址/新”)
2.创建新的sheet
1.创建新sheet
sheet对象 = 对象.create_sheet(新sheet名,存放sheet 位置索引)
2.修改sheet的颜色
sheet对象.sheet_properties.eabColor = "2进制颜色对 照表"
3.默认展开制定的sheet
对象.active = sheet[索引]
4.拷贝sheet
考被对象 = 对象.copy_worksheet(对象['sheet名字'])
5.删除sheet
def 对象[删除的sheet]
6.对单元格部分内容进行修改
对象 = sheet对象["单元格位置":“单元格位置”]

Python常用模块

os模块

# 作用与文件路径创建文件拼接路径等等
import os
1. os.path.abspath(__file__) # 找到当前执行当前代码文件的文件路径
2.os.path.dirname( os.path.abspath(__file__)) # 找到当前执行文件的父级路径
3.os.path.join(path2,'abc.py') # 进行文件路径的拼接
4.os.path.exists("文件路径") # 判断文件是否存在 返回 True False
5.os.makedirs('新创建的文件') # 创建文件夹 最外部文件的子文件夹
6.os.path.isdir("路径") # 判断路径是否是文件夹(文件/文件夹) 返回 True False
7.os.listdir('文件路径') # 查看当前路径下的子文件夹 返回列表
8. os.walk("需要遍历路径") # 查看全部文件夹内的全部文件 返回生成器
9.os.getcwd() # 获取当前脚本执行的路径
10. os.makedirs('dirname/dirname') # 创建文件可以深层创建(文件名称存在报错)
11. print(os.curdir) # 返回当前目录 返回 .
12. print(os.pardir) # 返回父级目录 返回 ..
13. os.removedirs('dirname/dirname') # 可以递归删除
14. print(os.chdir('dirname')) # 改变当前的脚本路径 相当shell中的cd命令
15.os.mkdir('dirname') # 在当前文件下创建单文件夹 相当于shell中的mkdir dirname
16. os.rmdir('dirname') # 删除文件夹 文件不为空删除报错 相当于 shell rkdir dirname
17.其他
print(os.stat('path/filename')) # 获取文件的目录信息
print(os.sep) # 输出特定操作系统下的路径分隔符 win \ linux /
print(os.linesep) # 输入系统下的终止符win '\t\n' linux'\n'
print(os.pathsep) # 系统下的环境变量的分割符 win ; linux :
print(os.name) # 输出字符串 指定当前的系统 win nt linux posix
os.system('dir C:\\Users\\56515\\Desktop\\基本内容复习\\其他') 作用就是在python中执行cmd的命令
print(os.environ) # 查看系统的环境变量(整个电脑的环境变量 全局) key(字符串)=val(字符串) 字典形式规定
18.重点 将路径的\ 转为适合当下系统的斜杠
os.path.normcase('c:/win\\123') # # c:\win\123 win下将所有的斜杠换为左斜杠
19.查看文件下的全部
print(os.path.getsize('../model测试'))
20.判断是不是文件
print(os.path.isfile('bj.txt')) # 判断文件是不是文件 存在返回True 不存在返回False
21.判断是不是绝对路径
print(os.path.isabs(r'C:\b\b\a.txt')) # 如果获取的是绝对路径 返回True 不是返回False
22.print(os.path.getatime('aaa')) # 获取文件和目录最后存取的时间 时间戳
23.print(os.path.getmtime('aaa')) # 获取文件和目录最后修改的时间 时间戳

Path-文件路径

from pathlib import Path
root = Path(__file__) # 获取当前执行文件的绝对路径
ret = root.parent # 获取当前文件的上一级目录 可以无限向后.parent 获取上一级
print(ret) # C:\Users\wkx\Desktop\测试\model测试
res = Path(__file__) / 'a\e.txt' # 进行拼接
print(res) # C:\Users\wkx\Desktop\测试\model测试\测试2.py\a\e.txt
print(res.resolve()) # 左斜杠换为右斜杠 与normcase效果一样

shutil模块

# 用于文件压缩和解压
import shutil
1.删除文件夹或者文件
shutil.rmtree("文件夹或者路径")
2.拷贝文件夹
shutil.copytree("拷贝文件夹路径","拷贝完成后存放的路径")
3.拷贝文件 可以重置文件名称
shutil.copy("拷贝文件路径/没有设置文件名,就用原来的","拷贝完成后存放的路径/文件名")
4.文件夹文件重命名(文件夹移动)
shutil.move("当前文件/文件夹目录","新文件夹/新文件目录/新名字")
5.文件压缩
shutil.make_archive(base_name=r"压缩之后存放的路径",format=r"压缩格式",root_dir=r"需要压缩的文件路径")
6.文件解压
shutil.unpack_archive(filename=r"需要解压的文件路径",extract_dir=r"解压后释放的路径",format=r"解压格式")

sys模块

impot sys
sys.argv # 可以读取解释器后执行的参数
sys.path # 获取模块的搜索路径 初始化时使用pythonpath环境变量的值(在包搜索是先在内存中搜索,在去磁盘中搜索) 打印的是磁盘的包路 可以进行append添加路径
sys.modules # 获取缓存到内存中的包路径(当文件导入时内存没有,从磁盘导入,导入后存放到内存中)
sys.version # 打印当前解释器的版本信息
print(sys.maxsize) # 获取int 最大的值
print(sys.platform) # 获取操作系统平台的名称
sys.exit(0) # 退出程序 当使用这个方法 后面程序不会执行直接退出

random模块

数字模块
import random
print(random.random()) # 获取0-1的随机数小数
random.randint(10,100) # 随机范围获取一个整数 头尾兼顾
print(random.randrange(1, 10)) # 获取1-10之间的整数 顾头不够尾 尾部取不到
random.uniform(10,100) # 获取范围内的值(小数)
lis = [11,22,33,44,55,66,77,88,99]
random.sample(lis,2) # 参数1:列表,元组,集合 参数2:获取随机的数量 根据传入的列表或者其他 根据后面的参数获取某几个值
lis = [11,22,33,44,55,66,77,88,99]
random.choice(lis) # 随机一个元素 列表 元祖 集合
lis = [11,22,33,44,55,66,77,88,99]
print(random.shuffle(lis)) # 打乱原有顺序

hashlib加密模块

import hashlib
md5 = hashlib.md5()
md5.update('66666'.encode('utf-8'))
print(md5.hexdigest()) # ae8b5aa26a3ae31612eec1d1f6ffbce9
密码加盐
# 防止黑客猜出来密码 和 加的其他的干扰信息
# 并且就算知道密码也没有用
import hashlib
md = hashlib.md5()
# 密码
pwd = '123456'
md.update('天王盖地虎'.encode('utf-8')) # 加盐
md.update(pwd.encode('utf-8')) # 实际的密码
md.update('小鸡吃老虎'.encode('utf-8')) # 加盐
print(md.hexdigest()) # 获取的hash内容...
检验文件的完整性
# 推荐 使用将一行一行的进行hash转换
md.update('整个文件')
# 推荐,这样防止内存溢出的问题 文件内容过大:循环的次数特别多速断慢
md.update('文件内的一行一行的数据')
md.update('文件内的一行一行的数据')
md.update('文件内的一行一行的数据')
# 可以这样解决:
with open('文件', mode='rd', encoding='utf-8') as f:
f.seek('随机指定的位置')
f.read() # 读取
md.update('将内容写入到hash中')
# 这样同时的防止了篡改的问题,又校验了文件的内容,速度比读取全部文件要快

moviepy模块

pip install moviepy
from moviepy.editor import VideoFileClip
vid = VideoFileClip("视频路径地址")
print(vid.duration) # 打印视频的时间 按照秒

json模块

跨语言传递数据
import json
dic = {'name':'wkx','age':18}
print(json.dumps(dic)) # 变为json格式的数据
print(json.loads(json.dumps(dic))) # 将json数据转为当前语言使用的数据

time模块

import time
1.获取时间戳 从1970-现在的时间
time.time()
2.获取当前时区
time.timezome
3.睡眠
time.sleep("停止时间按秒计算")
4.获取格林尼标准时间
# Thu Dec 8 20:40:26 2022
print(time.asctime())
5.获取时间格式化
time.localtime() # 可以通过.tm_year 获取内容
# time.struct_time(tm_year=2022, tm_mon=12, tm_mday=8, tm_hour=20, tm_min=40, tm_sec=26, tm_wday=3, tm_yday=342, tm_isdst=0)
6.获取格式化的时间
print(time.strftime('%Y-%m-%d %H:%M:%S %p')) # 2022-12-08 20:40:26 PM
print(time.strftime('%Y-%m-%d %X')) # 2022-12-08 20:40:26

datetime模块

from datetime import datetime
# 对时间的处理 +-
1.获取当前时间
datetime.now() # 获取的datetime 类型
2.获取指定的时间
from datetime import timezone,timedelta
print(timezone(timedelta(hours=8)))
3.两个时间相加
from datetime import datetime ,timedelta
变量1 = datetime.now() 获取当前时间
变量2 = 变量1+timedelta(days=天数 ,minutes=分钟) 相加后的时
4.字符串转换为datetime时间 strftime
变量 = datetime.strptime(需要转换的变量或者字符串,转换格式("%Y-%m-%d"))
获取的变量就是datetime的时间
5.datetime时间转换字符串 strftime
变量1 = datetime.now() # 获取当前的datetime时间
变量2 = 变量1.strftime("转换时间的格式")
%Y年,%m月份,%d天,%H时 %M分 %S秒
datetime.now().strftime('%Y-%m-%d')
7.datetime格式转换时间戳
时间戳 = time.time()
datetime.datetime.fromtimestamp(时间戳) # 直接将时间戳转为时间 1970-01-01 08:55:33
8.获取世界时间
print(datetime.datetime.utcnow()) # 获取世界时间 2022-06-07 12:43:22.664919

格林尼标准时间

from datetime import datetime
1.变为gmt时间格式
GMT_FORMAT = '%a, %d %b %Y %H:%M:%S GMT+0800 (CST)'
print(datetime.utcnow().strftime(GMT_FORMAT)) # Thu, 09 Jun 2022 14:45:30 GMT+0800 (CST)
2.字符串转换
# 变为字符串格式
a = 'Thu, 12 May 2022 23:08:47 GMT+0800 (CST)'
b = datetime.strptime(a, GMT_FORMAT)
print(b)
3.出现格式中缺少GMT+时 进行字符串的替换,在进行转换
a = 'Thu, 12 May 2022 23:08:47 +0800'.replace('+0800', 'GMT+0800 (CST)')
print(datetime.strptime(a, GMT_FORMAT))

时间转换

# 1654436371.0 时间戳
# 1988-03-03 格式化时间
# time.struct_time(tm_year=2022, tm_mon=12, tm_mday=8, tm_hour=20, tm_min=53, tm_sec=10, tm_wday=3, tm_yday=342, tm_isdst=0) 结构化时间
转换 格式化字符串 <-> 结构化时间 <-> 时间戳
1.time时间对象 # 结构化的时间
s_time = time.localtime() # time时间对象 结构化的时间
# time.struct_time(tm_year=2022, tm_mon=12, tm_mday=8, tm_hour=20, tm_min=53, tm_sec=10, tm_wday=3, tm_yday=342, tm_isdst=0)
print(time.mktime(s_time)) # 1654436371.0
2.时间戳转为time时间对象 # 结构化的时间
s_times = time.time() # 1670504092.59126
print(time.localtime(s_times)) # 结构化的时间
# time.struct_time(tm_year=2022, tm_mon=12, tm_mday=8, tm_hour=20, tm_min=54, tm_sec=52, tm_wday=3, tm_yday=342, tm_isdst=0)
3.本地结构化与世界结构化
print(time.localtime()) # 本地时间
print(time.gmtime()) # 世界时间
4.重点:结构化时间对象 转为 格式化时间
s_time = time.localtime() # 结构化
print(time.strftime('%Y-%m-%d', s_time)) # 2022-12-08 格式化
5重点 格式化时间转为结构化时间
print(time.strptime('1988-03-03','%Y-%m-%d'))
# time.struct_time(tm_year=1988, tm_mon=3, tm_mday=3, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=63, tm_isdst=-1)
案例:
目标 累积 +7
例如:读出来的时间为字符串
1988-03-03' 转为结构化时间
strp_time = time.strptime('1988-03-03','%Y-%m-%d')
再将转换为时间戳的形式 +7天
mk_time = time.mktime(strp_time) + 7*86400
转为结构化的时间
time.localtime(mk_time)
转为格式化的时间
time.strftime('%Y-%m-%d', time.localtime(mk_time))

re模块

re的方法

import re
正则表达式的语法
re.natch 从头开始匹配 返回一个对象
从头开始找,第一个字符或者参数必须符合匹配规则,不符合返回none
返回第一个匹配到的元素,无论后面有多少个符合条件的,都不返回(只返回第一个符合条件的)
re.search 全局匹配 返回一个对象
匹配到了就返回,返回当前变量中的全部字 符符合的第一个
如果匹配不到就会返回 none
re.findall 把所有符合规则的全部的放到一个元素列表中 返回的是列表
属于全局匹配,不同的是会将全部符合条件的字符存放到一个列表中
如果匹配不到就会返回一个空列表
re.splitall 匹配到的字符串当做列表的分割符
re.sub 匹配的字符并替换
re.fullmatch 全部匹配
注意:
('规则',匹配的参数)
如果返回的是对象同时又想获取匹配到的值 使用group()
# 只有返回的是对象才能使用
例如:
name = '1aaasdll'
print(re.match('[0-9]',name).group()) 获取的就是从头匹配的第一个值1
例如:
name = 'aaa122sdll'
print(re.search('[0-9]', name).group()) 获取到全部匹配的第一个值 1
# 匹配规则字符
. 默认除/n(换行符以外的全部匹配) 匹配任意一个字符 ..就是两个字符
^ 匹配字符的开头 (^ 跟上字符) 例如^a 那么变量 的开头必须是a 相当于强制的从头匹配
$ 匹配尾部字符 (字符$) 例如 b$ 变量的尾部必须是以b结尾 (match不能使用,因为默认从头开始匹配)
* 匹配*号前面的字符 0次或者多次 如果匹配不到就是'' 例如 a* 那么就会匹配到当前变量中 a后面的a字符(有多少个a那么就会匹配多少个)
ab* 那么会匹配到a 也会匹配到b 也会匹配到ab
+ 匹配前一个字符一次或者多次 例如 a+ 从变量中以a开头的取后面的全部的a
name = 'aaaa2sa122aasd8l'
print(re.search('a+', name)) 获取aaaa
? 匹配前一个字符1次或者0次 相当于只匹配第一个符合条件的,直接返回,剩余的符合的不返回
必须字符以 ? 之前的字符为开头
例如:
a? 那么就会匹配 以a开头的返回第一个字符
name = 'aaaa2sa122aasd8l'
print(re.search('a?', name)) # 返回a
name = '0aaaa2sa122aasd8l'
print(re.search('a?', name)) # 返回 ''
{m} 匹配前一个字符的m次
{整数,填写几} 那么就会匹配前一个字符几次
例如:匹配a 4次 也是就是 aaaa 连着
name = '0aaaa2sa122aasd8l'
print(re.search('a{4}', name)) # 返回aaaa
{n,m} 匹配前一个字符n-m次,对前面的字符进行范围的匹配
{整数,整数} 按照返回进行匹配
例如 a{1,4}
name = '0aaaa2sa122aasd8l'
print(re.findall('a{1,4}', name)) ['aaaa', 'a', 'aa']
| 或者的意思 匹配a字符或者b字符
name = '0baaa2sa122aasd8l'
print(re.search('a|b', name)) # b 意思就是有a 就匹配a 没有a就匹配b
(...) 分组匹配
print(re.search('([a-z]+)([0-9]+)', 'ajax123').groups()) # ('ajax', '123')
将括号内的匹配到的参数 放到一块,可以通过groups() 获取分组匹配的全部的内容
groups 只能分组匹配使用
[] 范围匹配 都包含
print(re.search('[a-z]', 'ajax123')) # a
匹配 a-z 的全部范围的字母
也可以[1-9] 匹配
# 等同于 ^ 与 $
\A 匹配 \相当于一个^ 从头开始匹配
\Z 匹配尾部的字符 同 $
# 匹配全部的数字
\d = [1-9]
\d+ = [1-9]+
# 匹配不是数字的全部字符
\D 匹配一个
\D+ 匹配多个
# 匹配 大写a-z 与小写的a-z 与数字 0-9
\w 跟re.后面的匹配模式 进行匹配一个或者多个
\w+ 匹配多个全部 大写a-z 与小写的a-z 与数字 0-9
# 匹配特殊字符
\W
\W+ 匹配全部的
# 匹配空白字符 \t \n \r 全部的字符
\s
\s 匹配全部
# 分组匹配,相当于给每一个分组匹配的内容起一个单独的名字
print(re.search('(?P<name>\d{3})', '13027630227').groupdict()) # {'name': '130'} 获取一个字典形式
# re.split
# 根据re条件进行切割
# 参数1 匹配规则 参数2 匹配的字符 参数3 maxsplit 切割多少次 默认全部切割
# 为什么['wkx', '', 'wuux', 'ww'] 存在一个空值,是因为\d 只能匹配一个 添加一个+进行贪婪匹配就可以
print(re.split('\d', 'wkx16wuux1ww')) # ['wkx', '', 'wuux', 'ww']
# re.findall
# 根据条件获取全部符合条件的字符
print(re.findall('\d', 'wkx16wuux1ww'))
# re.sub
# 用于替换字符串
# 参数1re匹配规则 参数2替换的参数 参数3 需要替换的字符 参数4 count替换的数量 需要替换几个 默认全部替换
print(re.sub('\d+', '++', 'sasd112a332', count=1))
# re.fullmatch
# 整个字符串全部匹配才会返回成功,否则none
# 例如匹配邮箱的 \w+匹配全部的a-z 0-9 @匹配@ \w+匹配当前@后面的字符 \\.转义. (con|cn|edu) 分组匹配这里面的全部符合条件的(分组先匹配)
print(re.fullmatch('\w+@\w+\\.(com|cn|edu)', '565151759@qq.com'))
# re.compile
# 相当于先写规则,返回对象,在用对象.方法 传入需要认证的字符
res = re.compile('\w+@\w+\\.(com|cn|edu)') # 匹配规则
# 相当于先将规则转换为审核语句,在进行匹配 可以提高效率,先让re模块转为判断语句,在将变量传入到判断语句中
res.fullmatch('565151759@qq.com')
'''
标志符号
re.I b不区分大写小进行匹配(默认re模块区分大小写)
print(re.match('a','Abc',re.I)) A
re.M 多行模式 将换行符无视,视为一行
re.S 将.匹配 到任意的符号
re.X 给匹配规则进行添加注释
'''

re的参数

re符号含义
帮助我们在一段文本中提取数据,或者判断一些数据返回结果。
1.如何编写正则表达式
1.提取固定的内容
变量 = re.findall("固定的内容字符串",匹配的内容)
打印出来生成一个列表
2.不固定的内容
[a,b,c] 提取的内容是 a 或b 或c
变量 = re.findall("正则表达式[固定内容]",匹配的内容)
"显示在外面的内容是固定的必须是这个内容开通[内容是不固定的,只要匹配上就能打印出来结果]"
3.匹配除了一些字符的剩下的字符
[^不匹配的字符]
4.匹配范围内的字符
[a-z] 匹配a-z的全部字符
[1-9] 匹配1-9的全部数字
r[a-z] 匹配开头时r的a-z的全部字符例如ra rz re....
5.特殊字符代表
1.
.代表换行符以外的任意字符(只能代表一个字符) 匹配全部符合条件的
例如:"r.o" 匹配r开头 中间是任意的 o结束的任意字符
rto rbo .只能代表一个字符
.+代表换行符以外的任意字符,+号可以匹配任意个字符 贪恋匹配 匹配全部符合条件的
例如:"r.+o" 匹配r开头 中间可以任意个字符(几个或者多个等等) o为结束符号
ryo rtyuio r.......无数个字符o 代表只要结尾是o中间有几个字符就会匹配一个字符
.+? .代表代表换行符以外的任意字符 +?代表匹配最近的符合条件的第一个字符 非贪恋匹配 匹配全部符合条件的第一个字符
例如:"r.+o" 匹配r开头 o结束
例如:raoraao,只能匹配到 rao
2.
\w 代表字母,数字,下划线,汉字 空格不匹配 匹配全部符合条件的
"r\wo" r 开头o 结束 \w只能匹配一个 字母,数字,下划线,汉字
"r\w+o" r 开头o 结束 \w+ 匹配多个 字母,数字,下划线,汉字 只要是r o 中间的全部字符都会匹配
\d 代表匹配整数数字 匹配全部符合条件的
"m\d" 匹配m开头的 后面的第一位数字 \d 代表一个数字
m1 m2 m3
"m\d+" 匹配m 开头 后面的无数个数字,只要是m开头 后面的全部数字都会匹配
m1364d654641...... m13213464513231.....
3.
\s 匹配任意的空白和空格字符
"r\d+\sr" 匹配 r 开头 中间是数字和空格(一个空白字符) r结束 的字符
"\s+" 匹配全部的空白字符
4. *代表重复0次或者多次.
代表*号 前面 的字符 可以出现0次或者n次
"mz*2" m开头 2结尾 中间的z可以是 z zz zzzzzzzzzz.....也可以是空白m2
mzzz2 mz2 m2
5.+代表重复1次或者n次
代表+号 前面 的字符 必须出现1次或者n次
"mz+2" m开头 2结尾 中间的z可以是 z zz zzzzzzzzzz.....
mzzz2 mz2
6.?代表出现0次或者1
在?前面的字符最少出现0次,最多出现1
"mz?2" m开头2结尾 z可以使0个或者1
mz2, m2
7.{n}重复n次
{n}在前面的字符必须出现n次
"mz{2}2" {2}前面的z必须出现2
{5}前面的z必须出现5
8.{n,} 必须出现大于等n次
"mz{2,}2" z必须出现2,或者更多
mzz2', mzzzz2, mzzz2, mzzzzzzzzzzz2
9.{n,m} 大于等于n位,小于等于m位的内容
\d{10,15} \d是数字{10,15} 数字要出现最少10位数,最多不能超过15位数
例如 : 1....10 1...11 1...12 1..13 1...14 1..15 这些都能匹配
10.括号()分区
只会提取()内的全部值
"1sd(\d{5})" 只会提取括号内的全部内容
例如:"13027630227, 15896695189"
提取 '02763', '89669'
11.双括号提取
"1(\d)(\d{5})" 提取括号内的全部内容,因为有两个括号索引提取两个内容得到的结果是一个元组
例如:"13027630227, 15896695189"
[('3', '02763'), ('5', '89669')]
括号内套括号
(1(\d{10})) 先提取外部括号内的内容,在提取提取内部括号的内容 两个结果形成一个元组
例如:"13027630227, 15896695189"
[('13027630227', '3027630227'), ('15896695189', '5896695189')]
12| 或者的意思
(2\d{2}|2\w+r) 将两个内容提取出来2\d{2}或者2\w+r的内容
看|前面的正则和后面的正则那个符合条件,如果前面的正则符合条件,只会匹配前面的后面的不匹配
例如:130222r7630227r
['222', '227']
13.re.ASCII 参数代表 不会匹配内部的 中文,只会匹配字母数字下划线
14.起始和结束
起始^
结束$
可以用于 用户校验
15.\
转义利用\进行转义

typing模块

官方文档:https://docs.python.org/3/library/typing.html
声明的模块 将变量进行声明处理
声明方式函数:
def greeting(name: str) -> str:
return 'Hello ' + name
def abc(name: str) -> list[int or str]
变量声明方式
name: str = 'xxxxx'
# 其他方式可以看文档

pickle模块

作用:将变量转为字节内容,将字节转为变量
# 建议只能进行数据存档,不建议跨平台交互
import pickle
# 序列化为字节模式
res = pickle.dumps('667')
print(res)
#b'\x80\x04\x95\x07\x00\x00\x00\x00\x00\x00\x00\x8c\x03667\x94.'
# 反序列化变为python可以使用的格式
r = pickle.loads(res)
print(r) # 667

subprocess模块

import subprocess
# 指定系统命令的 cmd命令
# 运行的系统命令
# dir 查看目录的全部文件夹
# shell=True相当于打开cmd
# 命令执行的结果正确丢入管道中 stdout=subprocess.PIPE
# 执行命令错误 丢到错误的管道中 stderr=subprocess.Popen
obj = subprocess.Popen('dir /C;/C', shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
# obj 不是命令的结果,是一个对象
print(obj) # <Popen: returncode: None args: 'dir /C'>
print(obj.stdout.read().decode('gbk')) # 这个是获取命令的正确的结果 读出来的字节类型
print(obj.stderr.read().decode('gbk')) # 获取的命令的错误结果 win gbk编码 mac utf-8
# 还可以深入的了解

logging

import logging
******** 基本使用 *********
# logging日志配置内容 的basicConfig配置
logging.basicConfig(
# 1.设置日志的位置:1.在终端打印 2.打印在文件中
# 在win中打开的文件编码有问题:这是为什么:因为logging根据当前系统进行设置的编码格式,win 编码是gbk
filename='login/access.log.text', # 设置日志存入的文件名称和路径
# 2.设置日志格式
# asctime 获取时间
# name 获取日志名称
# levelname 日志的等级
# module 那个模块报的日志
# message 日志的具体信息
# - 和 : 是自己拼接的
format='%(asctime)s - %(name)s - %(levelname)s -%(module)s : %(message)s',
# 3.如果不是用自带的格式,可以自己设置格式
datefmt='%Y-%m-%d %H:%M:%S %p',
# 4.日志级别
# debug 10
# info 20
# warning 30
# error 40
# critical 50
level=10
)
'''
配置部分配置信息打印的内容
2022-06-08 22:17:17 PM - root - DEBUG -logging日志模块 : 打印调试日志
时间 - 日志名称 - 日志等级 - 具体模块报的日志- 日志的具体信息
2022-06-08 22:17:17 PM - root - INFO -logging日志模块 : 打印消息
2022-06-08 22:17:17 PM - root - WARNING -logging日志模块 : 打印消息
2022-06-08 22:17:17 PM - root - ERROR -logging日志模块 : 打印报错
2022-06-08 22:17:17 PM - root - CRITICAL -logging日志模块 : 打印严重错误
如果日志打印的是等级10,那么他就是从等10 自下而上的进行获取
'''
logging.debug('打印调试日志') # debug 状态下的日志
logging.info('打印消息') # 正常输入
logging.warning('打印消息') # 警告日志 有可能出现的问题
logging.error('打印报错') # 错误日志 不是致命的问题
logging.critical('打印严重错误') # 致命的问题,造成程序崩溃
# 在没有设置日志等级时,默认等级是30
# 打印的内容:WARNING:root:打印消息 # warning 类型 root日志名字 内容
# ERROR:root:打印报错
# CRITICAL:root:打印严重错误
*********日志配置字典*******
'''
日志轮转
日志记录这系统的非常重要的内容,不可以轻易的删除,可以用来查看当前系统的状态
就是当随着时间的变化日志就会一直写在同一个文件(造成几个G)就会导致打不开的情况
定期的进行文件分割
日志名
设置的日志名会被记录到,日志信息中
日志名称要具有代表性
如果设置的日志名 在
logger = getLogger('log') 名字不存在
那么logging 模块就会找 loggers 字典中的 '' 的日志进行写入
并且将log 当为新的日志名称
'''
'''
日志文件的配置
'''
'''
1.定义三种日志输出的格式,日志中可能用到下面的字符串格式
%(name)s 日志的名字
%(levelname)s 文本形式日志的等级
%(pathname)s 调用日志输出函数的模块的完整路径名 可能没有
%(filename)s 调用日志输出函数的模块的文件名
%(module)s 调用日志输出函数的模块名
%(funcName)s 调用日志输出函数名
%(lineno)d 调用日志输出函数的语句所在的代码行
%(created)f 当前时间 用unix标准的表示时间的浮点数表示
%(relativeCreated)d 输出日志信息时的 自logger创建以来的毫秒数
%(asctime)s 输出字符串当前时间 格式 2003-07-08 22:40:45,89
%(thread)d 线程id 可能没有
%(threadName)s 线程名 可能没有
%(process)d 线程ID 可能没有
%(message)s 用户输出的消息
'''
# 写的日志格式:
test_format = '%(asctime)s - %(name)s - %(levelname)s - %(module)s.py - %(funcName)s - %(lineno)d : %(message)s'
standard_format = '%(asctime)s - %(name)s - %(levelname)s - %(module)s.py - %(funcName)s - %(lineno)d : %(message)s'
simple_format = '%(asctime)s - %(name)s - %(levelname)s - %(module)s.py - %(funcName)s - %(lineno)d : %(message)s'
# 日志字典的配置
LOGGING_DIC = {
# 日志版本控制 可以不用配置
'version': 1,
# 关闭已存在日志 默认设置就可以 可以不用配置
'disable_existing_loggers': False,
# 重点 日志格式的意思 +s 代表有多个日志格式 名字不可固定 系统的
'formatters': {
# standard simple test key 属于自定义的名称用来进行日志保存handlers 中 formatter进行使用的
'standard': {
'format': standard_format # format 是logging规定配置名称(不可以修改) val 是对应的日志输出类型
},
'simple': {
'format': simple_format
},
'test': {
'format': test_format
},
},
'filters': {},
# 重点 控制日志输出的位置 日志的接受者 终端 文件1 文件2 都可以是接受者
'handlers': {
# console default other 相当于对接受日志设置一个名称(自定义) 在产生者loggers中handlers列表进行指定 那个接受者可以进行接受
'console': {
'level': "DEBUG", # 日志的等级 可以是字符串 也可以是整数
'class': "logging.StreamHandler", # 调用logging打印到终端类
'formatter': 'simple', # 打印日志的格式 和formatters key中对应(当将配置字典导入配置logging中后,就会使用自动配置的formatters key 配置日志输出类型)
},
# 按照文件的大小进行轮转
'default': {
'level': "DEBUG", # 日志的等级 可以是字符串 也可以是整数
'class': "logging.handlers.RotatingFileHandler", # 日志的轮转类(由logging模块提供的功能)
# 当文件达到指定字节时,将文件中的内容复制到新文件中,清空老文件 (当文件内容达到5m就会创建新的文件夹)
'maxBytes': 1024 * 1024 * 5, # 默认是字节 当达到5m是就进行轮转(将日志文件切割) 达到指定设置的字节大小就会产生新的文件
'backupCount': 1, # 指定产生日志能产生多少份(备份), 超过备份的数量就会被删除
'formatter': "standard", # 指定的输出种类 使用formatters 中下面的指定自定义的key名的日志类型
'filename': "按照文件大小轮转.txt", # 设置文件路径 放到指定的日志文件下中
'encoding': "utf-8" # 设置字节编码
},
# 按照时间轮转,
'times': {
'level': "DEBUG",
'class': 'logging.handlers.TimedRotatingFileHandler', # 按照时间进行轮转
'when': 'S', # 按照什么形式进行轮转 秒 天 还是月 S,秒。M,分。H,时。D,天。midnight,每天凌晨。W{0-6},每周,记住不是从1-7,而是从0开始的。
'interval': 5, # 等待多少秒后才会进行生成新的文件写入日志
'backupCount': 10, # 备份保留的文件,超出的删除
'encoding': "utf-8", # 设置字节编码
'filename': "login/按照时间轮转.txt", # 设置日志的名字 路径
'formatter': "standard", # 指定的输出种类 使用formatters 中下面的指定自定义的key名的日志类型
},
'other': {
'level': "DEBUG", # 日志的等级 可以是字符串 也可以是整数
'class': "logging.FileHandler", # 调用logging保存到文件中类
'formatter': "test", # 指定的输出种类 使用formatters 中下面的指定自定义的key名的日志类型
'filename': "a2.log.txt", # 设置文件路径 放到指定的日志文件下中
'encoding': "utf-8" # 设置字节编码
},
},
# 重点 负责日志不同级别的日志的产生者 产生的日志传给handlers进行输出
'loggers': {
# kkk 产生的日志名字(kkk 就是产生日志的名字可以进行改变)
'kkk': {
# 指定日志给给那个接受者进行保存或者终端显示(将日志传给 指定的接受者) 当kkk产生的日志就会给到列表中的日志接受者进行文件写入
'handlers': ['other', 'console', "default"],
# 日志级别 loggers(第一层的日志级别限制) handlers(第二层日志级别限制) 第一层日志等级负责过滤掉不属于这个等级的日志,第二层日志等级根据第一次获取对应等级日志写入文件
'level': "DEBUG",
'propagate': False, # 默认是True 向上(更高级别level的logger)传递 设置为False
},
'': {
# 这种日志被成为默认日志,如果在大多的都是使用这个日志,在getLogger('名字')
# 没有找到loggers字典中的日志名,那么就会将 getLogger('名字') 名字作为日志名,这样默认日志名字就为动态的存在
'handlers': ["times"],
'level': "DEBUG",
'propagate': False,
}
}
}
# 怎么拿到 配置文件字典中的日志产生这(loggers) kkk
# 1.需要将日志字典加载到logging模块中
from logging import config, getLogger # logging 加载配置文件
# 2.加载配置
config.dictConfig(LOGGING_DIC) # 将配置文件导入 dict 字典类型的 配置文件
# 3.使用getLogger 将配置文件中的日志产生kkk取出来(kkk定义就是日志的名称)
logger = getLogger('日志名')
# 4.产生日志
logger.debug('这是debug日志')
# 使用的路径一定要使用os进行拼接使用,不能手写,会出现路径错误

PyJWT

作用

作为token进行使用,负责对生成的随机值,进行控制(时间控制,加密控制)
pyjwt文档地址:
https://pyjwt.readthedocs.io/en/latest/

JWT

json web toen
分为
jws 签名 速度快 能反推(敏感的数据不能放到这里) 在使用teon方案中 使用jws 签名
jwe 加密 时间太长 不能反推
1.安装 这个是独立的python 的jwt
pip install PyJWT
# 基本使用
import jwt
# 加密
encode = jwt.encode({"内容": "123"}, "324341", algorithm="HS256") # 加密
'''
参数1: 传入加密的内容,用户的id 名字 或者有效期 字典 载荷
参数2: 设置密钥,密钥随便设置 字符串
参数3:传入算法 HS256 ,也可以不传入,默认就是hs256
获得加密后的密串
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJcdTUxODVcdTViYjkiOiIxMjMifQ.D1py3j6-7JDnmqvyGdPwCauRSUzRkDjZKffc9W61Wi0
'''
# 解密
decode = jwt.decode(encode, "324341", algorithms=["HS256"]) # 解密
'''
参数1.将生成的被加密的内变量 获取被加密之前的载荷字典
参数2:传入密钥
参数3:设置加密算法
获得解密后的字典
{"内容": "123"}
'''

代码封装

import jwt
def general_jwt(payload, expiry, secret=None):
'''
生成jwt
:param payload:载荷,也就是存储的用户信息的字典
:param expiry: 过期的时间
:param secret:设置的密钥
:return: 返回一个jwt
'''
# exp 就是jwt内部设置的过期时间的参数,设置一个utc时间
payload['exp'] = expiry
token = jwt.encode(payload, secret, algorithm='HS256')
return token
from datetime import datetime,timedelta
# datetime.utcnow() + timedelta(hours=2) 当前的utc时间 + 2个小时 有效期在两个小时后过期
xx = general_jwt({'name': 123}, datetime.utcnow() + timedelta(hours=2) , '123')
def verify_jwt(token, secret=None):
'''
验签
:param token: 返回的值
:param secret: 密钥
:return: 返回解密的后的内容
'''
try:
payload = jwt.decode(token, secret, algorithms=['HS256'])
except jwt.PyJWTError: # 验证失败后的异常
payload = None
return payload
m = verify_jwt(xx, '123')
print(m) # {'name': 123, 'exp': 1651502322}

生成token和验证token

生成token :
头部 生成一个base64编码 -> 载荷payload生成一个base64为的编码 ->通过数学计算获取到token值
# 在jwt生成token时会使用secret密钥作为验证
# 载荷部分存储这用户的id等等 和exp设置的过期时间
例如:
是由: head头部. payload载荷部分 .计算部分
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoxMjMsImV4cCI6MTY1MTUwMjMyMn0.ByqnK783wVHWHVqX4umnPN-ic09bJTitODOQnVt1nBU
验签部分:
head头部. payload载荷部分.计算部分 # 进行hs256计算
base64编码 base64编码
# 计算的部分要与secret密钥字符串进行比较
如果相同那么jwt有效,才会将载荷部分的内容返回,在进行查看exp中有有效期是否过期过期那么token值就是none

token刷新机制

# 正常情况下的token请求的步骤
客户端 --------------- 服务器
1.客户端发送请求进行登录
2.服务端验证登录的密码账号,成功返回一个token值
3.客户端(前端保存token)
4.当客户端访问其他的服务器页面时,会在header头部携带token值
5.服务器进行验签如果携带token正确就进行返回正常视图
没有携带返回401
token有问题 返回401/403
# 用户无感知情况下获取token值
# 解决token刷新问题
1. token的有效期: 设置为2个小时,在接口调用携带,每隔2个小时进行刷新一次
2. 提供refresh_token接口 获取新的token,有效期14
3.在接口调用token过期后凭证借refresh_token 获取新的token
4.没有携带token,错误token或者调用接口token过期 返回401状态码
5.refresh_token过期返回403状态码,前端使用refresh_token请求新的token是遇到403状态码则进入用户的新的认证页面
6.token携带方式在请求头的格式
Authorization:jwt jwt码
# 带有自动刷新下的token认证机制 流程
1.当登录时验证密码和账号,返回2个token(1.正常的请求token 2个小时过期,2.刷新的token 14天过期)
第一种情况:
当请求正常条件下
1.token没有问题 正常访问
2.token没有携带 返回401 返回登录页面正常登录
3.token过期 返回403
当非正常情况下
token过期的时候,请求头就会携带这个 '刷新的token' 请求'refresh_token刷新的接口' 服务器进行验证这个 '刷新的token'
不正确的情况下返回 401 重新登录
正确的情况下,这个接口就会返回一个新的'访问token',请求头内就存放着访问的 新的token,获取请求的正常逻辑。

刷新的实现

# post 请求 /authorization
1.登录的接口设计:
1.在登录是需要发送post请求
{ # 将密码和账号发送后后端服务器,以json格式发送
name:'',
paw:''
}
2.在后端接受完成返回错误返回400
正确: 返回状态码,和token,刷新的token内容
{
code:200,
'message':'ok',
data:{
'token':'',
"refresh_token":''
}
}
前端需要将data中的值存储到前端vue中的会话技术中localstorage
2.接口的代码设计:
在flaks api框架获取用户的密码和账号的时候,就直接进行数据库或者其他到的逻辑判断,证明这个用户时可以登录,在登录的时候,需要携带token值返回给前端
# put 请求 /authorization
刷新token的接口也是
put 进行请求,刷新的token也是在请求头中存储的
请求头中:put / Authorization: bearer refres_token
返回结果
错误 返回403 重新登录
正确返回
{
'message':'ok',
'data':{
'token':'请求token'
}
}

jwt的禁用的问题

在服务器中开始保存数据
场景:
1.当用户在 ios 安卓 移动web端登录 在ios端进行 密码修改,
2. 对不良用户禁用
实现的思路:
1.使用redis进行保存
2. 使用redis的list 或者set 优先使用set 比较的速度比list的快
时间复杂度比较快
假设用户修改了密码,但是用户有多个平台登录,需要禁用当前用户的其他平台的token值。
技术栈:
1.redis数据 数据结构为set(o(1))
# 当用户修改密码或者对用户进行禁用代码
key = 'user:{'%s'}:token' % user_id # 存储的键
pl = redis.pipeline() # 事务 管道
pl.sadd(key,new_token) # 修改密码 后的新token值
pl.expire(key,token有效期) # 给这key设置有效期,token的有效期就是新的生成的token的有效期,等待其他平台老token死掉就可以了
pl.execute() # redis 提交事务
# 校验时
1.用户使用token进行请求 时,如果校验token通过,从redis中判断当前用户是否存在记录'user:{'%s'}:token' % user_id
没有记录:放行,进行视图业务
有记录:对本次请求 的token是否存在redis中set
存在:放行
不存在:返回403状态码,不在处理业务逻辑
key = 'user:{'%s'}:token' % user_id
valid_tokens = redis.smembers(key,token) # 取白名单
if valid_tokens and token not in valid_tokens:
# 白名单存在,并且这个token如果不在白名单中返回403
return {"message":'token非法'},403
补充说明:
redis记录设置有效时间长是一个token的有效期,保证旧token过期后,redis的记录也要删除,不占用空间
使用set保存新的token的原因,烤炉用户可能在旧token的有效期内容在多设备登录,需要生成多个新的token,这些token都要保存下来,保存了新token的正常登录,又能保证旧token被禁用

案例

from datetime import datetime, timedelta
import jwt
from flask import Flask, g, request
from flask_restful import Api, Resource # 导入类
from flask_restful.reqparse import RequestParser
app = Flask(__name__)
api = Api(app)
SECRET = '这是一个密钥'
# 1.生成的jwt方法
def generate_jwt(payload, expiry, secret=None):
'''
利用jwt方法生成token值
:param payload: 载荷 也就是需要加密的token值
:param expiry: 有效期 utc时间
:param secret: 密钥
:return: 返回token值
'''
_payload = {'exp': expiry}
_payload.update(payload)
token = jwt.encode(_payload, secret, algorithm='HS256')
return token
# 2.验证jwt方法
def verify_jwt(token, secret):
'''
验签token值
:param token: token值
:param secret: 密钥
:return: 验签后的载荷
'''
try:
payload = jwt.decode(token, secret, algorithms=['HS256'])
except jwt.PyJWTError: # 如果载荷过期,那么就会执行这个异常方法
payload = None
return payload
# 1.创建一个请求 钩子(flask的中间件) 每次请求都会执行这个钩子函数,获取请求头中token值进行判断并且赋值给g对象
@app.before_request
def jwt_authentication():
'''
flask的钩子函数
1.获取请求头中的token,因为存在两个请求头,所以需要进行识别
2.进行判断token值的是否存在
3.验签
4.存入到flask对象中的g对象中
:return:
'''
token = request.headers.get('Authorization') # 获取请求头中的token值
# 设置初始默认值
g.user_id = None
g.is_refresh = None
if token is not None and token.startswith('JWT '): # 一个开头的就是当前的请求token
token = token.split(' ')[1] # 获取
# 验证当前的token值
payload = verify_jwt(token, SECRET)
if payload is not None:
# 当获取的载荷payload不为空,那么没有过期可以使用
g.user_id = payload.get('user_id') # 获取id
g.is_refresh = payload.get('is_refresh', False) # 因为刷线的token中存储了is_refresh值,进行获取,如果没有默认为false,不是刷新token
# 1. 装饰器:作用:在每一次请求进入视图方法中时进行一次判断,判读当前的钩子方法中解析出来的g对象,是请求的token值否
def login_required(func):
def wrapper(*args, **kwargs):
# 进行判断,请求携带的token必须是有效的不能是刷新的token
if g.user_id is not None and g.is_refresh is False:
# user_id 必须有值,并且 is_refresh属性必须为false(false就代表当前是正常的token)
return func(*args, **kwargs)
else:
return {'msg': 'token非法'}, 401
return wrapper
# api类
class IndexLogin(Resource):
# 给当前的视图中的方法装饰装饰器
method_decorators = {
'post': [login_required, ],
}
def __init__(self):
self.expiry = datetime.utcnow() # 因为jwt过期时间使用的utc时间
# 3.生成token值
def _generate_tokens(self, user_id, refresh=False):
'''
:param user_id: 登录后用户的id
:param refresh: 根据这个值,需要请求token还是不需要请求token
:return: 刷新token和请求token
'''
'''这个方法主要是在用户登录时是生成token'''
# 生成请求token 有效期2个小时
expiry = self.expiry + timedelta(hours=2)
token = generate_jwt({'user_id': user_id}, expiry, SECRET)
# 生成刷新token,作用在请求token过期是,需要将刷新token请求刷线接口访问获取新的请求token
if refresh:
# 刷线token需要14天
expiry = self.expiry + timedelta(days=14)
# 'is_refresh': True 代表当前的token是刷新token
refresh_token = generate_jwt({'user_id': user_id, 'is_refresh': True}, expiry, SECRET)
else:
refresh_token = None
return token, refresh_token
def post(self):
'''用户post请求,登录时返回token值,每次访问时,需要在请求头中携带这个token'''
user_id = None
rp = RequestParser() # 获取参数 可以获取请求头中的参数也可以获取查询参数,请求体参数
rp.add_argument('name')
rp.add_argument('pwd')
res = rp.parse_args() # 校验
if res.get('name') == 'wkx' and res.get('pwd') == '123':
user_id = 1
token, refresh_token = self._generate_tokens(user_id, True)
return {
'code': 200, 'data': {
'token': token,
'refresh_token': refresh_token
},
'msg': '登录成功'
}
def put(self):
'''
在请求token过期后,请访问这个接口的put方法,获取新的请求token
而前端需要将刷新的token放在请求头中
:return:
'''
if g.user_id is not None and g.is_refresh is True:
token, refresh_token = self._generate_tokens(g.user_id)
return {'msg': '这是新的token值', 'data': {
'token': token,
}}
return {'msg': '这个接口负责给前端新的token值'}
api.add_resource(IndexLogin, '/api/v1/') # 将视图类与api进行捆绑
if __name__ == '__main__':
app.run()
'''
前端需要在请求头中携带参数
Authorization:
JWT 这个是一个标志
# token值
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
eyJleHAiOjE2NTE2NzU5NTcsInVzZXJfaWQiOm51bGx9.
YkwufLfsOZXSKbfx3-IREENE8InBXrewXZa3ogrOxMM
'''

Faker生成测试数据

# 中文网址:
https://faker.readthedocs.io/en/master/locales/zh_CN.html
# 用于生成测试数据模块
pip install Faker
1.通过Faker设置模拟数据(以终端命令的情况下设置)
import datetime
# 创建一张表 学生
class Student(db.Model):
__tablename__ = 'student'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(15), index=True, comment='学生名称')
age = db.Column(db.SmallInteger, comment='年龄')
sex = db.Column(db.Boolean, default=True, comment='性别')
email = db.Column(db.String(128), comment='邮箱')
created_time = db.Column(db.DateTime, default=datetime.datetime.now, comment='创建时间')
money = db.Column(db.Numeric(10, 2), default=0.0, comment='钱包')
def __repr__(self):
return f'<{self.name} - {self.__class__.__name__}>'
2.自定义终端命令
'''基于faker生成仿真的模拟数据'''
import random, click
from faker import Faker
fakers = Faker(locale='ZH_CN')
@app.cli.command('faker_user')
@click.argument('num', default=10, type=int) # 终端命令
def faker_user_command(num):
st_list = []
for i in range(num):
sex = bool(random.randint(0, 1))
st = Student(
name=fakers.name_male() if sex else fakers.name_female(),
age=random.randint(15, 60),
sex=sex,
email=fakers.free_email(),
money=float(random.randint(100, 100000) / 100),
created_time=fakers.date_time()
)
st_list.append(st)
db.session.add_all(st_list)
db.session.commit()
需要设置 FLASKE_APP=app.py
flask faker_user 在终端启动生成随机数据

案例

进度条打印

import time
# res = ''
# for i in range(50):
# # T
# res += '#'
# time.sleep(0.5)
# print('\r[%-50s]' % res, end='')
# end='' 不换行的意思
# \r 回车的意思从头开始
# 也就是[%s] 普通的占位符
# [%50s] # 是这样的效果[ ########] 这列表的长度为站位50
# [%-s] # [####### ] 从左边开始
# [%-50s] 意思就是 左对齐 宽度50 -左对齐 50是宽度
# 模拟下载显示进度条
recv_size = 0 # 已经下载的字节长度
total_size = 10250 # 模拟下载的长度
while recv_size < total_size:
time.sleep(0.3) # 模拟下载带宽
recv_size += 1024 # 将字节添加,这个字节的大小需要根据剩余下载的数量绝对
# 获取已下载的长度和总长度的百分比
percent = recv_size / total_size # 使用已下载的字节 / 总字节 下载的百分比
if percent > 1:
percent = 1
res = int(percent * 50) * '#' # 获取的下载进度条的长度 的#号 根据百分比来的percent
int_num = percent * 100 # 获取百分比进行进度条片的显示
print('\r[%-50s] %d%%' % (res, int_num), end='') # 显示进度条 与百分比

猴子补丁操作

使用的模块如果不满意某些功能,可以进行对方法进行打补丁,修改为自己的代码
补丁的导入:在首次导入的位置进行打入补丁,后续在进行导入的位置就发生了变化
在入口文件进行导入包后进行打补丁
# 思想:把源代码替换自己功能的操作应该放到文件的入口(启动文件)程序无论其他的位置在导,就会使用到自己的功能操作
不用了:就在入口文件删除了就行,或者替换
例如:
ujson 比json 的效率高
ujson 的dumps 高于json的效率
ujson 的loads 高于json的效率
import json
import ujson
# 方法替换
json.dumps = ujson.dumps # 替换为ujson
json.loads = ujson.loads # 替换为ujson
# 在入口文件进行替换执行
def mokey_patch_json():
'''只是将json名称空间的方法进行替换了'''
json.__name__ = 'ujson' # 将模块名替换
json.dumps = ujson.dumps # 替换为ujson 的方法
json.loads = ujson.loads # 替换为ujson 的方法
mokey_patch_json() # 直接执行
# 如果对哪些包中方法不合适,就可以在入口文件中进行替换为自己的方法或者新的方法
# 打补丁就是将某些方法给替换,不想用可以进行注释掉就行,就会变为原来的方法

Python虚拟环境

pip批量卸载安装

pip uninstall -r requirements.txt
pip install -r requirements.txt

virtualenv包

虚拟环境Linux

linux and 本地:
pip install virtualenv
1.创建
virtualenv -p python3(使用的环境) 环境名称
在linux中需要创建软连接(要不不就是用绝对路径)
find / -name virtualenv
ln -s /usr/local/python3/bin/virtualenv /usr/bin/virtualenv
创建后的虚拟环境在当前所在目录下
2.生效
source 虚拟环境的文件名/bin/activate
例如:
source blogweb/bin/activate
(blogweb) [root@VM-12-16-centos python39] 在当前环境下 全部的pip下载都在当前环境下 直接批量导入就可以
3.启动项目
python3 manage.py startproect 端口与ip
需要修改django中的配置参数才可以外部访问
4.退出
deactivate

虚拟环境win

1.虚拟环境包
pip install virtualenv
# 当前电脑中的D:\python_virtualenv 文件夹中存放虚拟环境 进入指定的目录文件夹,创建虚拟环境
2.创建虚拟环境
virtualenv 虚拟环境名称 --python=python3.9 # 指定python包
virtualenv 虚拟环境名称 --python=python的路径 # 指定python包
3.激活虚拟环境
进入当前目录创建虚拟环境的目录
例如
D:\python_virtualenv\interact\Scripts
scripts文件夹下
执行
进行对虚拟环境的激活 相当于进入虚拟环境
activate
退出虚拟环境
deactivate
4.虚拟环境安装模块
先激活 在安装
进入虚拟环境中进行安装
pip install django==3.2 版本
5.怎么使用虚拟环境
1.在pc中进行新建项目
2.点击使用virtualenv虚拟环境 中的先前配置的解释器,
3.点击添加解释器,找到虚拟环境下的文件夹目录
4.Scripts\python.exe 进行配置虚拟环境
不创建的情况下
1.点击解释器中的文件,中的设置
2.项目下的python解释器
3.添加新的解释器,找到虚拟环境的文件夹
4.Scripts\python.exe 进行配置虚拟环境
需要安装什么包就在终端中进行安装
(interact) C:\Users\56515\Desktop\interact> 默认pc中使用的就是虚拟环境

venv包虚拟环境

1.创建虚拟环境
python -m venv py_venv
# 会创建在当前项目下的相对路径
2.linux环境下激活虚拟环境
source py_venv/bin/activate
3.linux环境下退出虚拟环境
py_venv/bin/deactivate
4.win环境下
py_venv/bin/activate # 进入
py_venv/bin/deactivate # 退出

Python函数

函数概念语法

函数时代码的集合
函数的优势:
1.将重复的代码复用
2.将代码长度减少,将代码分开,维护性高
面向过程的编程思想
也就是在编写程序时,时刻想着程序的执行过程,就是流程
好像就是流水线设计
解决的问题的方式
优点:
将复杂问题流程化,变为简单化
缺点:
扩充性非常差
面向对象的编程思想
也是基于面向过程的编写逻辑的过程
# 计算器底层就是一个流水性的过程
面向过程的编写思想应用场景
1.不是所有的软件都需要平凡的迭代: 脚本
2.及时一个软件需要迭代 ,也不软件全部的部分需要更迭,只是部分
函数式编程
将函数互相调用,实现数学的层面逻辑

1.函数的使用

1.函数的定义
def func(val):
print(val)
func('函数')
1.定义函数接收的参数为形参
2.调用函数传入的参数为实参
2.关键字传入参数
关键字传参,会根据形参的名称传入对应的参数,按号对位
def func(val):
print(val)
func(val='函数')
3.位置传参
根据实参的位置进行传入参数,对应函数形参的位置
def func(val1, val2):
print(val1)
print(val2)
func('函数', '函数2')
# 注意问题:
关键字传参和位置传参组合使用:位置传参在前,关键字在后
def func(val1, val2):
print(val1)
print(val2)
func('函数', val2='函数2')
4.默认参数
形参在定义时,已经存在
如果对形参传值,存在默认参数将会覆盖,如果没有传值使用默认参数
def func(val1, val2='函数2'):
print(val1)
print(val2)
func('函数','函数666')
5.动态参数
分为: *args,**kwargs
args 按照的是位置传参,按照元祖进行接收
**kwages 按照的关键字传参,按照的是字典结构接收
# args
def func(*args):
print(args) # 打印结果('函数', '函数666')
func('函数','函数666')
# kwargs
def func(**kwargs):
print(kwargs) # {'a': '函数', 'b': '函数666'}
func(a='函数',b='函数666')
# 组合使用(规则也是位置传参在前,关键字在后)
def func(*args, **kwargs):
print(kwargs) # {'a': '函数', 'b': '函数666'}
print(args) # ('123', 1111)
func('123', 1111, a='函数', b='函数666')

2.函数的返回值

1.存在返回值 会将返回值返回给调用者,可以赋值给变量也可以直接打印
def func(val):
print(val)
return '666'
val = func('函数')
print(val) # 666
2.不返回值 没有返回值,那么接受者接收的值就是None
def func(val):
print(val)
val = func('函数')
print(val) # None
返回值的作用:
函数中代码处理后一些自己需要用的数据,可以通过返回值获取获取函数的结果
关键字,在函数中return

函数的概念性内容

1.关于函数穿的参数是什么?

函数在传参时默认传入的是什么:传入的是内存地址。
优势:
1.省内存空间
2.函数在内部可以对数值进行处理(处理的是内存地址)
参数需要可变类型才能对参数进行修改:参数可变类型:listdictset
3.其他语言是对参数进行拷贝,对拷贝的进行处理。如果python也想这样需要copy模块进行拷贝,函数就会对拷贝的参数进行处理,不会对原数据进行处理
例如:引用的是当前变量的内存地址,而不是拷贝
name = '666sada'
def func(val):
print(id(val)) # 2337699345904
print(id(name)) # 2337699345904
func(name)

2.函数的返回值是什么

函数的返回值返回的是:内存地址
在函数内部有引用计数器,当使用参数时,计数器加1
函数内部的变量在当函数执行完毕后,变量自动销毁,计数器减1
返回值的特点:
1. 当两个变量接受函数的返回值,数值相同,内存地址是不同的,两个执行函数的变量,相互不会干扰。
每个变量都会进行创建内存地址。
例如:
def foo():
return [123, 45645]
v1 = foo() # 返回的内容都为[123, 45645]
print(id(v1)) # 2508241651968
v2 = foo() # 返回的内容都为[123, 45645]
print(id(v2)) # 2508241460928
2.每次执行函数,如果值时数值和字符串,那么的内存地址不论执行多少次函数都是相同的。
因为python有内存驻留机制。考虑到函数内部使用数值和字符串次数太多,为了优化内存产生。
def foo():
return 123
v1 = foo()
print(id(v1)) # 2114972440752
v2 = foo()
print(id(v2)) # 2114972440752

3.函数的默认值,在初始函数创建默认内存地址。

存在默认值
1.当函数第一次执行时,就会加载一个默认值得存储地址(只会加载一次)。
2.当函数第二次执行时,不会对默认值进行第二次内存地址的创建,只要不传参就一直使用这个默认值内存地址(进行维护)。
3.当对带有默认值函数进行传参时,就会使用传入参数的内存地址。
4.在特殊情况下,默认值时可变类型:list dict set,可以对数据进行增删。
案例1
def func(a, b=[1, 2, 3]):
b.append(a)
return b
val1 = func(12)
print(val1) # [1, 2, 3, 12]
# 当前存在默认参数时,函数会创建对b的内存地址(创建1次),没对对b进行传入参数,那么就会一直使用默认b的内存地址
案例2
def func(a, b=[1, 2, 3]):
b.append(a)
print(id(b)) # 只要不进行覆盖一直使用的b默认值的内存地址,1911771908352
return b
val1 = func(12)
print(val1) # [1, 2, 3, 12]
val2 = func(13)
print(val2) # [1, 2, 3, 12, 13]
val3 = func(14)
print(val3)
val4 = func(15, [16]) # 2799334278464 发生覆盖默认值地址发生变化
print(val4) # [16, 15]
每一次执行函数都不会对默认值进行内存地址的重新创建,从始至终一直使用默认值得初始的内存地址。

4.动态参数高级用法

*args **kwargs
动态参数不仅仅可以当成形参,也可以当成实参进行处理
在执行函数时,可以将传入的列表字典等数据打散传入。
def func(*args,**kwargs):
print(args)
print(kwargs)
func([11,12],{"a":'666'}) # 结果就是 args ([11, 12], {'a': '666'}) kwargs {}
func(*[11,12],**{"a":'666'}) # 结果就是 args (11, 12) kwargs{'a': '666'}
可以对函数传入的参数进行打撒操作,会根据数据的结构传入args和kwargs中
元祖列表集合打撒后传入ages
字典 打撒后传入kwages

5.函数名/函数

1.
函数在执行可以成为元素,或者参数,或者返回值等等执行或者调用
将函当成返回值 or 参数 or 元素都可以
def func():
print(1)
def show():
return func
v1 = show() # v1接收show函数的返回值func(func函数的名称)
v1() # ()执行了func函数
2.
函数要可以被哈希的。所以函数可以被当成列表元素,字典的键,字典的值,集合的元素
['函数1','函数2']
3.
参数个数不同可以使用动态参数进行打散进行传递。
{'函数1':func....}
4.
函数名赋值,当函数名赋值给一个变量,这个变量就可以加上括号执行函数 v1 = func函数名 v1()执行func函数

6.print与return的区别

如果函数中没有return返回值,那么函数执行赋值给变量就会接受到一个none
print作用:帮忙输出内容文本
return作用:将函数的结果,或者其他值,返还给函数,让函数赋值打印,方便调用者的使用。同时还可以当做暂停作用,当执行到return时,不会执行下面的代码,直接结束函数。

7.作用域

分为全局和局部。
每个函数都属于一个局部作用域
全局作用域:谁都可以被调用,局部作用域可以进行读取,但是不能重新赋值,和修改。
局部作用域:只能自己内部的局部代码进行使用,其他的不可以调用,如果数据没有就去,全局中去找
name = '666' # 全局作用域变量
def func():
age = 18 # 局部作用域变量
print(name) # 在局部没有从全局找
print(age) # 报错,直接找全局不会找局部

8.global关键字

可以在函数内部对全局的作用域的值进行修改
1.不是使用global关键字:
name = '666' # 全局作用域变量
def func():
name = '777'
print(name) # 777
func()
print(name) # 666
2.使用global关键字
name = '666' # 全局作用域变量
def func():
global name
name = '777'
print(name) # 777
func()
print(name) # 777

函数高级语法

1.函数的嵌套

1.作用问题
全局作用于函数,在每一次调用都会产生一个局部的作用域(每次调用就会产生一个新的局部作用域),在函数内部还有函数,就会在父级函数中产生一个子级的函数作用域,这就叫嵌套
2.函数执行问题
当函数执行时内部有其他的函数执行,如果内部没有就去全局找并且执行。
当函数的名字相同时,下面的函数会覆盖上面的函数(python从上到下执行)
当函数执行时,确定函数作用域是谁。
函数可以在子作用域中或者更深。
例如:
def foo(): #嵌套在局部作用域
print('开始') # 4.打印
def func():
print('正在执行') # 6.打印
func() # 5.执行foo函数内嵌套的func函数
print('结束') # 7.打印
def out(): #嵌套在全部作用域
print('我在执行out函数') # 2.打印
foo() # 3.调用foo函数
out() # 1.调用当前的out函数执行
函数在寻找时,也是遵循这现在自己作用域(儿子)找,找不到再去全局(爸爸)找。全局不能去局部找
作用域总结:
1.优先找自己的作用域,没有在去上一级找
2.在作用域找值,确保值时什么
3.分析函数的执行过程,确认作用域

2.函数的闭包

是由函数的嵌套组成,将数据封装到一个函数中(一个作用域中)
def 函数名1(形参):
def 函数名2():
print(形参) 打印参数,如果自己的作用域中没有,就去父级作用域找 (函数名1,就是打印函数名1的形参)
return 函数名2
v1 = 函数名1() 将函数名2的 名字赋值给 v1变量
v1() 执行函数名2
在函数名1 全局作用域中,内部创建一个函数名2的局部作用域。
案例:
def func():
print('开始')
def show():
print('结束')
return show
v1 = func()
v1()
# 在函数内部调用函数,外层函数返回内部的函数名称 在进行执行

3.函数装饰器

关键字:@装饰器名字
批量对函数执行之前后做操作使用函数装饰器
在使用装饰器后原函数名变成一个参数传入装饰器函数中
基于@语法和函数闭包,将函数封装到闭包中,将函数赋值给新函数,执行函数时在对新函数中执行闭包中的原函数
可以在不改变原函数的代码,和调用方式前提下,实现函数的执行和扩展
多个函数系统执行前执行后自定义一些功能
实例:
def outer(origin):
def inner(*args,**kwargs):
res = origin(*args,**kwargs)
return res
return inner
@outer
def func():
pass
func()
将func 函数当成参数传入进去,在装饰器内部添加一些自定义的功能,在将主函数func返回给inner函数,在将inner返回给outre函数
结果就是
res = func()
innre = func()
inner = outer
outer = func()
相当于转到最后,在装饰器内部,转一圈下来执行了func函数
例如:
# func返回值是show 也就是执行了show函数
def func(val): # val = inner
print('开始') # 1
def show(*args, **kwargs):
print('666') # 3
res = val(*args, **kwargs)
return res # 返回的是已经执行函数inner
print('结束') # 2
return show
@func # 将被装饰的函数作为参数传入到装饰器中,开始执行装饰器函数
def inner():
print(1) # 4
inner() # 对应被装饰的函数的执行可以理解为执行了装饰func

4.函数的递归

递归是一种编程思想 算法离不开递归(算法使用)
# 特点:
1.函数内部自己调用自己
2.必须有出口
没有出口就会报错:电脑最大递归深度 900 - 1000
与函数链接起来
# 案例
def sum_numbers(num):
# 执行结束后的出口
if num == 1:
return 1
# 函数调用自己
return num + sum_numbers(num - 1)
'''
第一遍执行 num =3
return 3 + 函数(2) # 函数(2) = 结果3
第二遍执行 num = 2
return 2 + 函数(1) # 函数(1) = 1 结果1
第三遍执行 num = 1
return if num == 1 return 1
# 开始将返回值返回给函数
# 最终结果就是 6
# 主要还是return 的返回值起了最关键的作用,当函数执行到return时,函数就会停止,将结果返回给函数
'''
print(sum_numbers(3))

5.二分发查找算法概念

lis = [11, 12, 13, 14, 15, 16, 78, 99, 101, 1104, 1588]
num = 16
# 第一种方式
# 使用enumerate 获取 元素下标 元素本身
# 缺点速度太慢,如果数据量比较大的情况下,几乎需要一个一个的去对比
# for i, k in enumerate(lis):
# if k == num:
# print('end 获取成功,下标%d' % i)
# break
# 二分查找(必须是有序的)
'''
具体的逻辑
lis = [小的值,中间值,大的的值]
select_num = 需要找的值
mid_val = 列表中间的索引
start_num = 列表0索引
end_num = 列表的 -1索引
if select_num > lis[mid_val]:
# 就说明 就要取大的值
在进行重复调用
elif select_num< list[mid_val]
# 就说明 就要取小的部分的值
在进行重复调用
else :
print('打印出来当前所在的值的下标')
'''
# 具体实现的代码
def dichotomy(lis, select_num):
'''
:param lis: 有序列表
:param select_num: 查找的值
:return: 查找的值在列表中的下标索引
'''
if len(lis) == 0:
# 如果查询的值不存在列表中进行判断
print('值不存在当前列表中')
return -1
len_num = len(lis) # 总长度
mid_val = len_num // 2 # 中间索引的长度 取整数
if select_num > lis[mid_val]:
# 在列表的中间 - 尾部的数量 进行列表的切片
lis = lis[mid_val + 1:]
dichotomy(lis, select_num)
elif select_num < lis[mid_val]:
# 起始 - 在列表的中间
lis = lis[:mid_val]
dichotomy(lis, select_num)
else:
print('值存在列表中')
return 1
dichotomy(lis, 15)
# 如果列表是无序的 需要对列表进行排序
nums = [11,12,568,99,66]
nums.sort()
print(nums) # [11, 12, 66, 99, 568]

匿名函数

匿名函数:就是没有名字的函数。
关键子 lambda
格式 : lambda 接受 : 函数体
例如:lambda x:函数体
例如:lambda x1,x2:函数体
例如:lambda *args,**args:函数体
在匿名函数体设置(只是支持单行代码)内部都会有一个return,将值返回给一个接受变量
例如:
f1 = lambda x:x+100
v1 = v1(10
print(v1) 结果:110
匿名函数和三元运算结合使用
name = lambda x :666 if x >10 else 999
n= name(参数)
print(n)
传入的参数成立 就输出 666
传入的参数不成立 就输出 999

内置函数

abs() # 获取绝对值。负数变整数,整数变整数
pow() # 取次方 参数1 参数2
sum() # 求和,参数可以被迭代的 ,可以被循环的
v1,v2 = divmod() # 取商和余数 赋值给2个变量 参数1,参数2
round() #保留小数点后几位,参数1小数,参数2保留几位数的整数
min() # 取列表或元组中最小的值 可以别迭代的
max() # 取列表或者元组中最大的值 比较取到的是 原值
all() # 是否全部是True 转换为布尔值是不是全是True
any() # 是否存在是True 转换为布尔值是不是只要一个是True得到是True
ord() # 获取字符的10进制的代码数字
chr() # 根据数字获取相对的字符
len() # 获取值得长度
open() # 打开文件
type() # 获取数据类型
range() # 获取数值
enumerate() # 维护一个对应的 索引位置,和元素值
help() # 获取python内部的提示信息 使用手册
callable() # 判断是否可以加括号执行 返回True/False
sorted("字典,元组,集合",reverse=True) # 进行排序字典取k进行排序 可以制定字典的值进行排序 True 正序
sorted("字典.itema()",key=lambda x:x["字典的其中一个值"]["某个指的名字"])
# 字符串方法
num = '123' # 可以迭代
print(int(num)) # 123
print(str(num).__repr__()) # '123'
print(bool(num)) # True
print(float(num)) # 123.0 可以转换为字符串10进制整数类型
print(tuple(num)) # ('1', '2', '3') 不可迭代的对象不可以转为元组
# eval() 也就是将字符串类型转换为原有的类型
print(eval(num)) # 转为整形
print(eval('[1,2,3,4,5,6,]')) # 转为列表

内置函数2

dic = {
'siry': 300,
'tom': 7000,
'lili': 10000,
'jack': 2000
}
******* max min *******
max(传入可迭代对象)
取一个值进行比较,取一个值进行比较,比较最大的
min(传入可迭代对象)
取一个值进行比较,取一个值进行比较,比较最小的
print(max(dic)) # 获取的是tom ,因为字典按照key比较
# key指定一个函数
# 工作原理:max 进行迭代,会将传给key指定的函数中,key函数执行的返回值当为比较的值
# 如果使用后有名函数,那么max 就会自动调用这个用名函数,将迭代值传入
print(max(dic,key=lambda key:dic[key]))
******** sorted 排序的原理和参数 ****
# 参数1:可迭代对象 参数2:函数(处理方法)(可以不传):排序方式 reverse=Tuer 或者 Fasle(可以不传)
# 进行传入迭代器对象,进行迭代器对象的之间的对比,进行排序,如果传入的是字典那么排序按照key排序,可以指定方式
# sorted也是排序方法,传入的字典没有指定排序方法会按照key排序
# 字符串key 进行排序
# 可以传入key参数,指定排序方式
print(sorted(dic,key=lambda key:dic[key]))
****了解*****
# map
# 第一个参数传入函数(处理的规则) 第二个参数传入可迭代对象
lis = [11, 12, 13, 14, 15, 16, 78, 99, 101, 1104, 1588]
# 每一次迭代就会将元素传入给第一个参数的函数中进行处理
# 和 (lis*2 for i in lis) 元组生成性质一样 获取一个生成器
print(map(lambda key: key * 2, lis)) # 返回一个生成器
# filter 过滤方式
# 第一个参数传入函数(处理的规则) 第二个参数传入可迭代对象
l = ['xx', 'xx1', 'xx2']
# (i for i in l if i.endswith('1')) 和 filter相同
# 第二个参数做了: for i in l
# 第一个参数做了: 接受循环的参数,进行判断是否符合条件
print(filter(lambda key: key.endswith('1'), l)) # 返回生成器
# reduce 可以作为一个累加的操作
from functools import reduce
# 合并操作
# 第一个参数 一个函数 参数2 可迭代列表 参数3初始的值
# 执行流程就是: 初始值 + 被循环的第一个值 在和函数方法进行处理 获取一个结果
# 这个结果 就会和列表中循环的第二个值相加 以此类推
# 如果不传默认值,那么就是none,就会取出列表的两个值进行相加结果 和后面的值相加
print(reduce(lambda x,y:x+y,[1,2,3],10))
# 取绝对值
print(abs(-100)) # 将负数转为整数
# 四舍五入
print(round(1.3)) # 以0.5位界限进行

作用于关键字

global 关键字可以通过局部作用域对'全局变量'进行修改
nolcal 关键字通过局部作用对'上级作用域变量'进行修改

生成器

作用:生成器是由函数和yield关键字 构造出来的,在特定条件下帮助我们省去内存空间
1.生成器函数
def 函数名():
函数体
yield 数值或者内容,当添加yield关键字时就是生成器函数
2.生成器函数与函数的不同
当执行生成器函数时:不会被执行,会获得一个生成器的对象
例如:
def func()
print123
yield 123
print123
yield 123
v1 = func() 生成器对象
print(v1) 打印到生成器对象
生成器对象执行 只能用next执行
v1 = next(v1)
执行到yield时 就会停止函数的执行将yield的值返回给v1
在下一次执行时
v2= next(v1)
会接着上次v1执行的地方,向下执行,执行到yield值返回给v2
...以此类推
当执行完毕后 会报错 表示生成器函数的代码执行完毕 :stopiteration
生成器对象可以用for 循环,每一次循环都会执行next()
for执行完毕后不会报错误,直接结束。
(执行方法2
data = func()
for i in data: 在每次循环会执行 next(data)
生成器函数执行的小案例 获取4位验证码
import random
def gen_random_num(max_count):
counter = 0
while counter < max_count:
yield random.randint(1000, 9999)
counter += 1
data_list = gen_random_num(3000000)
n1 = next(data_list)
print(n1)

深浅拷贝

深浅拷贝:
说的是可变类型 set list dict 才有意义,不可变类型无意义
一个是只拷贝父对象,一个是拷贝父对象中的子对象完全拷贝
浅拷贝:
a = {1: [1,2,3]}
b = a.copy()
a[1].append(4)
print(a,b) # {1: [1, 2, 3, 4]} {1: [1, 2, 3, 4]}
'''
a 与 b 都是独立的对象,但是都执行同一个内存地址(同一个引用)
只要其中一个改变,那么另一个就会改变
'''
深拷贝
import copy
a = {1: [1, 2, 3]}
b = copy.deepcopy(a)
a[1].append(4)
print(a, b) # {1: [1, 2, 3, 4]} {1: [1, 2, 3]}
'''
a 与 b 两个完全独立的对象,指向的也是不同的内存地址,如果a改变,那么b不会受到影响
'''

Python模块的导入

模块

模块
一系列功能的集合体
1.内置
2.第三方
3.自己写的
为何要存在
1.内置与第三方 :提升自己的开发效率 拿来主义
2.将公共代码提取出来,减少代码的编写,进行共享
import time 时发生了什么
1.执行time.py
2.产生了time.py的名称空间,将time.py运行过程中的产生的名字丢到time的名称空间中
3.在该文件中产生一个名字time,名字执行time.py产生的名称空间
import time # 产生名空间
import time # 直接引用首次导入的名称空间
import time # 直接引用首次导入的名称空间
# 引入
print(time.now) # 这是指明道姓的从一个模块中获取方法
now = 0 # 在当前文件中命名now参数 不会影响正常的引入,因为不存在一个命名空间中
# 无论产看还是修改都是以原模块为基准,与调用关系无关
导入规范
import 内置的
import 第三方
import 自定义
# 模块是第一类对象
模块的可以被赋值为变量

导入

循环导入的问题
也就是 1.py文件 导入了 2.py文件
2.py文件中 导入了 3.py
3.py文件中又导入 1.py 不会在造明细空间
将公用的内容变量方法 放到一个公共的文件夹中
模块的搜索的优先级
1.内存
将包存入内存加载
2.硬盘找
按照sys.path的文件顺序进行查找 导入的包
import sys
print(sys.path) # 文件列表(python从硬盘找到对应包的顺序)
列表的第一个元素 是第一个执行文件的文件夹
列表的第二个元素 是当前执行文件夹的父类 (ied帮助加载的,忽略,在脚本目录中是不存在的)
进行import
就会将文件内存地址存到 内存中 导入后,一直存储到内存中,直到程序结束
print(sys.modules) # 内存中已经加载的导入模块 返回的字典
# 导入的顺序
当导入文件时,如果内存没有就会从 硬盘找,将包缓存到内存中去
当再次到导入后,就会先从内存中找到对应的包
如果在内存中找到不包,同时又在硬盘中找不到就需要进行添加
# 将需要的文件模块向对路径添加到这个 python找到磁盘的列表中
import sys
sys.path.append(r'C:\Users\56515\Desktop\abc.py')
包是什么: 包中含有__init__.py的文件夹
包的本质是模块的一种形式,包是用来当做模块导入
将多个py文件放到包中__init__.py中
环境变量以'执行文件'为准所以被导入的模块或者后续的文件引用的sys.path都是参照执行文件的sys.path为主(谁是执行文件,那么就是以他父级模块文件夹为主)
到的是__init__.py文件 将全部的包中的功能导入到__init__.py文件中
包的文件夹必须要存在执行文件的中的sys.path中(在执行文件夹下的相同文件夹中)
作为模块(包)的使用者 要模块在环境变量中(sys.path.append('路径'))
导入
无论是使用者还是设计者 from.. import.. 还是import.. 凡是使用中带点 点的左边必须是一个包,否者非法(语法规定)
import 顶级包.子包.子模块 必须遵守这个原则(点.左边 必须是一个包 要不然报错)
A包和B下有同名的模块不会冲突,A.a 和B.a 来自于两个命名空间
import 导入文件时名称空间中的名字来源于文件本身 import 包. 产生的名称空间中的名字同样来源于文件本身 就是包中__init__.py 导包就是到文件下的init文件
绝对导入没有限制
# 相对导入(包能之间的导入 推荐使用相对导入)
# . :代表当前的文件夹(包)
# .. :代表上一层文件夹(包的上一层文件名)
# . 不能超出顶层包中(只能在包中使用不能跨出包)

命名空间

'''
将栈区的进行分片处理
a = 10 # 在1区
a = 11 # 在2区
这样就可以命名相同的名字
全局范围:
内置命名空间
全局的名称空间
局部范围
局部命名空间
存放的名字
在调用函数时,运行的函数体代码过程中产生的函数内部的名字
存活期:
在调用函数的存活,调用完毕后销毁
加载
内置命名空间 > 全局命名空间 > 局部命名空间
销毁
局部命名空间 > 全局命名空间 > 内置命名空间
名字查找的顺去(在局部命名空间)
局部命名空间 > 全局命名空间 > 内置命名空间
函数的命名空间是和类的形式一样的
先从自己找在自己的父的东西
'''

Python的Class类

面向对象概念

面向过程

也就是过程,解决过程的流程,相当于流水线一样的
把问题流程化
优点:
将复杂的流程简单化
缺点:
可扩展性差

面向对象

对象:特征与技能的结合体
有点:
可扩展性强
缺点:
编程复杂高
应用场景:
用户需求经常变化,游戏,企业内的应用
类就是一系列对象相似特征与技能的结合体
强调:站在不同的角度中,得到的分离是不同的
在现实世界:现有对象后总结出来类
在编程世界:先定义类,后调用类来产生对象
# 抽象: 抽取相同的地方

一切接对象

站在不同的角度上 定义的类是不同的
现实中的类不等于程序中的类 现实中的公司类,在程序中要拆分到部门,业务
有是为了编程需求 程序中定义的类在现实中是不存在的,比如策略类 显示中是不存在的,但是在程序中非常常见
python 一切皆对象 在python3中的统一类类型的概念
比如:
class list 类 只是继承了 列表相似的功能进去
# 这两对象实例化是一个不同过的对象
# 那么对象中的方法也是不同
l1 = [1,2,3] # l = list([1,2,3])
l2 = [1,2,4] # l = list([1,2,4])
那么 l 变量就是list class类的实例化对象 name l 就可以使用list中的那个方法
比如: l.append()

Class基础语法关键字

class 类名
def 方法名(self,参数1,参数2.....):
pass
执行类:
对象 实例化类
实例对象=类名()
实例对象.方法名(参数1,参数2...)
案例:
class Name(object): # 创建对象
def get_name(self): # 创建对象中的方法
return 'name'
# name 对象 Name() 实例化类
name = Name()
# 利用对象调用内部方法
name.get_name()

1.初始化方法__init__

在实例化对象时,会自动触发__init__方法
class 中的 name = Name() 被称为实例化对象
1.init方法的定义
class 类名
def __init(self):
pass
2.对实例变量进行定义(当前类下的全部方法都可以使用)
class C
def __init(self,val):
# 参数的传递:1.通过实例对象传递 2.通过自定义传递
self.val = val # val是在实例化对象时传入参数123 实例变量
self.a = '777' # 自行定义的实例变量
C('123')
3.__init__的过程
1.执行class()实例化
2.在执行对class进行实例化时 会限制性__new__(cls)方法 创建一个内存区域 cls参数就是当前类本身
3.才会执行__init__(self,)方法self 是内部提供的参数,内部存储的是基于实例化的一个内存区域(默认为空),使用__init__方法可以将数据存储在方法中,通过self进行调用存储的数据
4.self 是个参数接收了class的实例化在new方法执行完毕创建内存区域后,init方法接收并且将方法参数存储到内存区域,使用self进行调用
4.当实例化两个对象时 内存地址时不同的
class Name(object): # 创建对象
def __init__(self):
print('我是init方法') # 后执行
def __new__(cls, *more):
print('我是new方法') # 先执行
return super().__new__(cls, *more)
# name 对象 Name() 实例化类
name = Name()
print(id(name)) # 2140846747360
name2 = Name()
print(id(name2)) # 2140846747120
实例化时 将对象当成参数传入到self中,self内是一个内存区域,存放着类中的方法和数据。

2.class的成员修饰符

1.成员 类变量 实例变量
实例变量:封装到实例化对象中,只有实例化对象才能调用,在类中属于局部变量
类变量: 封装到类中,类可以调用同时实例化对象也可以调用,在类中属于全局变量
class N(object):
age = 18 # 类变量
def __init__(self, name):
self.name = name # 实例变量
n = N('zss') # 实例化对象
print(n.age) # 实例化对象调用age 打印18
print(n.name) # 实例化对象调用name 打印zss
print(N.age) # 类只能调用类变量无法调用实例变量
# 备注:
优先去实例变量中找,找不到,去类变量找
2.对类内部的变量进行添加与修改
class N(object):
age = 18
def __init__(self, name):
self.name = name
n = N('zss')
n.name = '修改实例变量' # 修改
n.type = '添加一个实例变量' # 添加
N.age = 999 # 修改类变量
N.pee = '添加类变量' # 添加类变量
通过 n.__dict__ 查看实例对象中全部的内容(只有__init__的变量)
通过 N.__dict__ 查看类中的全部内容(不包过__init__的变量)
读:如果实例变量中没有,就去类变量中找
写:类添加和实例变量添加,不受影响,使用类名就是类添加变量,使用对象就是实例变量添加
2.成员修饰符
# 区别
公有:任何地方都可以调用
私有:只能在自己类中使用
定义:__双下划线定义
class N(object):
def __init__(self, name, age):
self.name = name # 公有的实例变量,任何地方都可以调用
self.__age = age # 私有的实例变量,只能在内部使用
def __func(self): # 私有方法
pass
def func2(self): # 共有方法
pass
n = N('zss', 18)
# 怎么实现调用类中的私有方法
class N(object):
def __init__(self, name, age):
self.name = name
self.__age = age
def __func(self):
pass
因为共有方法可以被外部调用,私有只能被内部调用,那么就使用外部调用内部
def func2(self): # 使用共有调用私有
self.__func()
print(self.__age)
n = N('zss', 18)
n.func2()
# 关于共有与私有的使用
1.想要外部调用就是公有
2.想要内部成员只是作为辅助其他成员 就用私有
3.成员是否作为独立功能暴露给外部,让外部调用
3.外部硬执行私有方法变量
案例:
class foo
def __init__(self,a1,a2):
self.a1 =a1
self.__a2 =a2
def __f1(self):
pass
def f2(self):
self.__f1
obj = foo() # 实例化
#调用内部私有变量
print(obj._foo__a2) #实例化对象._类名 私有成员
#调用内部私有方法
obj._foo__f1() #实例化对象._类名 私有方法

3.使用类封装分页方法

class Pagination:
def __init__(self, current_page, per_pagr_num=10):
self.per_pagr_num = per_pagr_num #实例化默认值
if current_page.isdecimal():
# 判断起始值,如果输入的不是数字,起始值就等于1
self.current_page = 1
return
current_page = int(current_page) # 转换为init类型
if current_page < 1:
#判断起始值,如果起始值小于1,起始值等于1
self.current_page = 1
return
# 实例化起始值
self.current_page = current_page
# 在__init__ 方法中设置值,并且执行一些代码。经过判断后将起始值进行实例化
@property # 定义为属性 不要加括号进行调用执行
def start(self): # 起始位置
#返回 起始值-1*默认值,起始位置
return (self.current_page - 1) * self.per_pagr_num
@property
def end(self):
# 返会 尾部 起始*默认值
return self.current_page * self.per_pagr_num
# 每次浏览10个数据
list = [1w数据]
if __name__ == "__main__":
while True:
page = input(">>>")
pa_object=Pagination(page,10) # 获取实例化对象,将用户输入的值传进去
list_object = list[pa_object.start:pa_object.end] # 进行切片,实例化对象调用 类中的起始方法和尾部方法
for i in list_object:
print(i) # 将切片的内容进行循环打印

4.class的3大特征封装继承多态

1.封装

特征:
封装,继承,多态
1.封装
# 封装方法:隔离复杂度
# 将类内部的复杂类型给隐藏起来
# 将简单的接口暴露给用户,将复杂的流程隐藏起来
分装的扩展性:
使用者不需要知道内部的逻辑,只需要接口的怎么调用就可以
创建者,只需要将内部的逻辑编写完整,将需要暴露的外部接口暴露就可以
体现在两个方面
1.将同一类方法封装到类中 :西瓜和苹果都是水果类的
2.将数据封装到对象中,在实例化对象时,通过__init__初始化方法封装到实例化的对象中
便于以后使用
封装的体现:
class 信息
def __init__(self,n1)
self.n1 = n1
def 发微信(self):
print(self.n1)
def 发短信(self):
print(self.n1)
def 发微博(self):
print(self.n1)
对象 = 信息(xxxxxxxxx) # 在实例化时将数据封装到类中
对象 = 信息() 将类中的方法 封装到对象中(变量),通过对象进行调用
对象.发短信()... # 公用一个参数实现不同的效果
1.对类进行实例化时
2.执行init方法,init的n1接受传入的值
3.在将数据封装到 init方法中 self.n1 = n1
4.在内存区域开辟一个 存储 n1 = n1的数据
5.对象 会成为参数传入到self的内存中 self内部就存储这信息类的方法和数据
4.总结
总结:
1.封装:将方法,数据封装到对象中,便于以后使用
2.继承:将功能中的公共方法放到父类中,让子类继承使用
3.多态:python参数是多态的,但是内部的方法是有约束的,只有相同的方法,才可以

2.继承

1.继承
继承的作用:增加代码的复用性,类与类之间的关系,解决代码的冗余问题,重用性提高
两个类同时都有一个方法,那么可以将方法重新创建一个类,放另外两个类继承。
子类可以继承父类的方法和类变量(数据)(不属于拷贝,父类的还是父类的,子类可以用)
class Base
def func(self):
pass
class Son(Base): son类继承了 base类
def show(self):
pass
class Func(Base):
pass
son = Son()
son.show()
func = Func()
func.show() # 优先在自己的类中去找,没有就去父级中去找.
# 继承的类被成为 父类和基类
# 被继承的类被成为 派生类的子类
类.__bases__ # 查看当前的继承关系
类名.mro() # 查看继承关系
2.类的查询方法
查找的方式:
深度优先 和 广度优先
只有在python2中 分为 新式类 和 经典类之分
在python2中 子类和父类没有继承object
class A: # 经典类
pass
class B(A):
pass
在python2中继承了object类 被称为新式类
class A(object): # 新式类
pass
class B(A):
pass
在python3 默认基础object 都是新式类
class A:
pass
print(A.__bases__) # (<class 'object'>,)
新式类与经典类查找顺序不同
经典类: 是深度优先
例如:
class A(B,C,D)
pass
先从继承的B类开始一直找到B类的父类的尽头
在去继承的C类开始找找到C类父类的尽头
在去找继承D类父类的尽头
新式类: 是广度优先
最深的继承的类不会先找,而是平行到相邻类中找,到最后在找最深的继承类

3.多态

3.多态
传入的数据类型多种形态的(参数时多种形态,传入的参数都必须要有同样的方法)
方法是有约束的,参数时多态的任意类型,函数内部调用的方法规则
多态性是面向对象编程的又一个重要特征,它是指在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义
值的是可以不在考虑对象的类型下而直接使用对象本身
增加程序的灵活性
增加程序的扩展性
鸭子类型:
根本不用在乎多种类型的使用
例如:
开车,只要学会开车,那么全部的类型车就可以开(奥迪....)
鸭子类型:
比如:
不让子类继承父类(强制的abc.ABCMeta抽象类)
而是让子类模拟父类
代码比喻:
文件类型类 有读 和写文件
class File:
def read(slfe):
pass
def write(self):
pass
# 磁盘类
class Disk:
def read(slfe):
pass
def write(self):
pass
# text 类
class Text:
def read(slfe):
pass
def write(self):
pass
def func(obj):
obj.read()
他们有都是由两个方式 read 和 write 两种方式
text = Text()
disk = Disk()
# 这中就是鸭子类型 有相同的方法或者方式 但是没有什么直接的关系
# Python崇尚的就是 只要像鸭子,走路像,叫声像 就可以
# 不用考虑类像的具体类型
text.read()
disk.read()

5.super的用法

也就是,当自己类中派生的存在,那么就是用自己的,不在使用父类的属性
现在对象中的找对象的命名空间中,在从对象当前类中的找,在去父类中找
class A:
def __init__(self):
pass
def show(self):
print(123)
class Func(A):
name = 123 # 这就是派生属性
def show(self):
print(456)
f = Func()
f.show() 那么他就是还是用自己的类中的show方法
******* 在子类中重用父类的属性 *****
# 指明道姓的方法式进行使用初始化方法 不依赖继承
class A:
def __init__(self, name):
self.name = name
def show(self, age):
print(123)
class B(A):
#def __init__(self,name,age):
#self.name = name
#self.age = age # 子类在初始值中需要多添加一个,那么就是重写init方法
def __init__(self,name,age)
A.__init__(self,name) # 那么就是用指明道姓的方式进行调用父类原有的init方法
self.age = age
def show(self, age):
# 在子类中使用父类的方法和属性
A.show(self, age) # 1.指明道姓的使用,没有使用到继承的概念,而是使用了类名调用内部方法函数
print(4456)
b = B('123')
b.show(666)
******** super() **********
2. 第二种情况子类需要有自己的初始化方法,进行对象的独特的值
使用super可以 对象的方式进行调用父类的中的方法
使用 super() 依赖继承 具体依赖是那个类的实例化对象,他存在的mro继承关系,而不是取决于super() 存在那个类
class A:
def __init__(self, name):
self.name = name
def func(self):
print(123)
# super 是在mro列表中 进行继承 按照mro继承顺序去找方法
# super 是按照当前 实例化对象的mro 列表进行找继承关系,不是super在那个类中,他就是那个类的
class B(A):
def __init__(self, name, age):
# python2需要将自己的类名,与self实例化对象与参数传入
# super(B, self).__init__(name)
# python3 可以省略掉super内的参数
super().__init__(name)
self.age = age
def show(self):
# 子类使用super(自己的类名,当前的实例化对象self)
super(B, self).func()
b = B('HAHA',18)
b.show()

抽象类的使用与概念

python 提供了抽象类的概念,提供了一个接口的方式
模仿java 模仿 interface接口
抽象类:将全部的类抽取比较像的部分,形成一个父类,让子类继承父类必须使用当前的父类的方法,必须让继承父类的子类必须按照父类的规定进行使用
import abc # 1.导入abc模块
# 继承metaclass=abc.ABCMeta ,那么这个子类继承当前的父类必须强制的使用内部的方法名
# 这个类只做规范,不完成具体的效果
# 通过抽象类,实现子类的规范
# 抽象类,只能被继承不能被实例化
# 抽象类,本质上还是一个类,还是可以遵从类属性的查找原则
class Animal(metaclass=abc.ABCMeta): # 2.定义抽象类,继承metaclass=abc.ABCMeta
@abc.abstractmethod # 3.强制规范继承子类的中的方法
def run(self):
pass
@abc.abstractmethod
def eat(self):
pass
# 只要子类继承了当前的父类方法 就必须实现当前内部的方法
class People(Animal):
def run(self):
print('people')
def eat(self):
pass
凡是继承当前抽象类,那么必须定义父类中的定义的方法,那怕不实现也可以

进阶使用

1.类中的3种方法

1.绑定方法 __init__(self)方法 :对象调用
默认参数时self,self代指实例对象,内部存放着类的方法和实例变量/类变量
2.类方法 @classnethod :类名调用
默认参数 cls ,cls就是代指这整个类。作用:在方法内部使用 类 就用类方法
3.静态方法 @staticmethod :类名调用
无默认参数,作用:如果方法内部只是一些简单的逻辑,不适用参数,就可以使用静态方法
使用方式案例:
class foo
def __init__(self):
pass
#绑定方法,用的最多
def show1(self): self 指向 实例对像 a
pass
@classmethod # 类方法
def show2(cls): # cls 指向 类名 foo
pass
@staticmethod #静态方法
def show3():
pass
a = foo() # 实例化对象
a.show1() #绑定方法调用
foo.show2() #类方法调用 当执行时,将类当成参数 传入show2 第一个参数cls
foo.show3() #静态方法
绑定方法的特殊
1.使用类名进行调用内部的方法函数时,需要对应的传入指定的位置参数self
而且类名调用的方法函数,就是一个普通的函数
2.绑定方法是给实例化对象使用,绑定不同的对象效果也是不同的
而对象调用内部的方法函数时,默认自动将对象本身传入到方法函数中的self中
也就是 a1.learn(a1) 将对应本身默认传入参数
总结:对象来调用自己内部的绑定方法时,会将对象本身当为第一个参数(self)传入到方法中
剩余的参数,该怎么传入就怎么传入
类中的方法函数,是给对象使用,那个对象来调用,就将他当为第一个参数传入
如果对象的名称空间中这个参数存在,那么就是用对象的名称空间中的
如果名称空间中的参数不存在,就会使用类中的参数
和函数中的局部和全局的概念是相同的意思
如果对象名称空间(局部)不存在的,那么就使用类空间中的(全局的)
当找不到也不会去类外部找
绑定方法 如果用类名调用内部的方法是不同函数 用对象调用方法是绑定函数 同时他们的内存地址是不同的
# 注意当使用类去调用内部的函数时,需要对应的传入self位置参数 类本身传递进去
# 使用对象调用,那么会自动将对象本身传递到self中

2.类中的三个特殊属性property

属性是由绑定方法和装饰器组成的。
class Func:
@property # 使用最多
def func1(self):
print('执行property')
@func1.setter # 很少 不能使用func2作为装饰对象 因为不存在
def func2(self, val): # val接收赋值
print(val)
@func1.deleter # 很少 不能使用func3作为装饰对象,因为不存在
def func3(self):
print('deleter')
a = Func()
a.func1
a.func2 = 123 # 将123赋值给val 同时执行当前函数
del a.func3 # 不是删除只是执行
方法1
定义规则:@方法名.setter,方法中需要多加一个value参数
使用方式:a.func2 = 123 参数value可以接受 添加的值
方法2
定义规则:在方法上加入:@property
使用方法:a.func 在调用方法时可以不用加括号
方法3
定义规则:@方法名.deleter
使用方式:del a.func3 执行方法

3.类中的其他方法内置1

class Func:
# 初始化方法
# 实例化对象时自动触发,实例化类是时传入的参数会存放在self,self代指对象
def __init__(self, name):
self.name = name
# 构造方法
def __new__(cls, *args, **kwargs):
# 在实例化对象 先执行 __new__,创建一个空的对象,返回给 init 的 self中
return object.__new__(cls)
# 字符串方法 当进行打印类时,就会直行str方法 可以对类进行说明
def __str__(self):
return "这是一个不同的方法类" # 执行str 必须返回一个字符串,或者返回类中的变量,变量必须是字符串类型
# 字典方法 在类中定义后调用返回当前定义的内容
@property
def __dict__(self):
return '7788'
# 如果不在类中使用 那么会将init 初始化的变量以字典形式返回
'''
例如:
class A:
def __init__(self,a1,a2):
self.a1 =a1
self.a2 =a2
A = A(11,12)
print(A.__dict__) # {'a1': 11, 'a2': 12}
'''
a = Func('zzz')

4.类方法其他方法内置2

class Func:
def __call__(self, *args, **kwargs):
print(args, kwargs) # ('666', '777') {}
return '110'
a = Func()
print(a('666', '777')) # 打印返回值 110
call 作用使实例化对象需要加括号进行调用执行的方法
当实例对象('可以传入参数') 就会直行call方法,不会影响类中的其他方法调用

5.类方法其他方法3

字典支持 对象["xx"]取值 对象["xx"] =123 赋值 del 对象["xx"] 删除键值对
__setitem__ __getitem__ __delitem__
class Func:
def __setitem__(self, key, value):
'''字典赋值操作'''
print(key, value)
def __getitem__(self, item):
'''字典get操作'''
print(item)
def __delitem__(self, key):
'''字典的删除操作'''
print(key)
a = Func()
a['666'] = 777 # 执行setitem
a['666'] # 执行getitem
del a['666'] # 执行delitem
当时实例对象按照字典形式执行方式时 会触发当前三个方法

6.类方法其他方法4

让对象支持with 上下文语法 执行
class Func:
def __enter__(self): # 在操作之前执行操作
return '测试执行中'
def __exit__(self,*args,**kwargs): # 在操作之后在执行别的代码
print('执行结束')
a = Func()
with a as e:
print(e)
'''
1.先执行__enter__方法 返回值赋值给e
2.执行完毕__enter__方法才会执行 __exit__方法
'''
让两个实例对象进行加减
#让两个实例对象进行相加
class A():
def__add__(self,other):
return # 想让他返回什么就返回是什么,v3获得返回值
a = A() # 实例化
a1= A() # 实例化
v3 = a+a1 # 当两个对象相加时执行add方法 +号后面的对象当成参数传入 other中
# v3 等于 add 返回的结果

7.类方法其他方法5

迭代器的定义:
1.在定义类 中 必须有__iter__ 和__next__两个方法
2.__iter__ 返回对象本身 就是 self,将self返回
3.__next__ 返回下一个数据,没有数据就执行 stopiteration异常
迭代器的定义:
1.在定义类 中 必须有__iter__ 和__next__两个方法
2.__iter__ 返回对象本身 就是 self,将self返回
3.__next__ 返回下一个数据,没有数据就执行 stopiteration异常
迭代器类
class A(object):
def __init__(self):
self.count = 0
def __iter__(self): # 条件1
return self # 条件3
def __next__(self): # 条件2
self.count +=1
if self.count == 3:
raise stopiteration()
return self.count
# 符合三个条件
a = A() # 迭代器对象
# 方法1:
v1 = next(a) 1
v2 = next(a) 2
v3 = next(a) 抛出异常
# 方法2:
for i in a : # for循环内部自动执行next
print(i)
#先执行 __iter__ 方法 获取返回值 self
#for循环内部自动执行next
#当结束后,抛出异常
#for循环依赖于迭代器一个个取值
#内部先执行 __iter__ 并获取迭代器对象,不断的执行迭代器对象next方法
生成器
生成器属于迭代器的一种
def func():
yield 1
# 创建生成器对象(根据生成器类generator创建对象)
# 内部声明两个方法: __iter__ 和__next__两个方法
# 可以通过 next() 取值 也可以通过for循环取值
#for 循环取值
#内部先执行 __iter__ 并获取迭代器对象,不断的执行迭代器对象next方法
可迭代对象
# 定义:如果类中有 __iter__ 且返回一个迭代对象,就成为这个类是可迭代对象
class A(object):
def __iter__(self):
return #迭代器对象 也可以返回生成器对象
a = A()
#a 被成为可迭代对象
# 可以被for 循环
for i in a :
pass
可迭代对象和迭代器组合
class A(object):
def __init__(self):
self.count = 0
def __iter__(self):
return self
def __next__(self):
self.count +=1
if self.count == 3:
raise stopiteration()
return self.count
class A1(object):
def __iter__(self):
return A()可迭代对象
for i in A1():
print(i)

8.类中的内置方法总结

1.@classmethod 类方法 参数cls
2.@staticmethod 静态方法 无参数
3.@property 在执行类中的方法时不用加括号
4.callable(函数名) 判断执行对象是否后面可以加括号
5.super() 按照mro的方法向上找到父类成员关系(优先取找父级的方法) 如果父类没有,就会报错
6.type ,获取对象的类型
7.isinstance(实例化对象,父类) 判断对象是不是某个类的继承或者子类 返回True/False
8.issubclass() 判断是否是类的子孙类 返回True/False

9.类使用嵌套

class student(object):
#学生
def __init__(self,name,age):
self.name = name
self.aeg = age
def messaage(self):
data = "{},{}".format(self.name ,self.aeg)
print(data)
class classes(object):
#班级
def __init__(self,title):
self.title = title
self.student_list = []
def add_student(self,stu_object): # 传入单个值
self.student_list.append(stu_object)
def add_students(self,stu_object_list): #传入一个列表
for i in stu_object_list:
self.add_student(i)
def show_members(self):
for i in self.student_list:
print(i)
s1 = student("123",12) #s1 是一个学生对象 student类的全部的方法和变量
s2 = student("4565",13) #s1 是一个学生对象 student类的全部的方法和变量
c1 = classes("年级")
c1.add_student(s1) # 获取的结果列表中包含着对象s1,需要进行 点操作出来
c1.add_students([s1,s2])
#A类的对象,传入B类中,在B类中,可以使用A类的方法
class A(object):
# 学生
def __init__(self,name,aeg,class_object):
self.name = name
self.age = aeg
self.class_object = class_object
def message(self):
data = "{},{},{}".format(self.name,self.age,self.class_object)
print(data)
class B(object):
#班级
def __init__(self,title):
self.title = title
b =B("python全站")
b2 = B("linux云计算")
a = A("wkx","18",b.title)
a2 = A("wyx","16",b2.title)
a.message()
a2.message()
# user_list = [
# A("wkx","18",b),
# A("wyx","16",b2)
# ]
# for i in user_list:
# print(i.name,i.age,i.class_object.title)
# 类B内存中存了一个实例化变量,可以取出来 b.title b2.title
# 取出 B 类封装的值
class A(object):
# 学生
def __init__(self,name,aeg,class_object):
self.name = name
self.age = aeg
self.class_object = class_object
def message(self):
data = "{},{},{}".format(self.name,self.age,self.class_object)
print(data)
class B(object):
#班级
def __init__(self,title,C_object):
self.title = title
self.C_object = C_object
class C(object):
#校区
def __init__(self,name):
self.name = name
s1 = C("上海")
s2 = C("北京")
c1 = B("全站",s1)
c2 = B('云计算',s2)
user_list = [
A("123","18",c1),
A("123","18",c2)
]
for i in user_list:
print(i.name,i.age,i.class_object.title,i.class_object.C_object.name)
# 类的嵌套:就是将实例化的对象传入其他类中,其他类的参数接受这个对象,这个参数也具有对象的方法和值(一层一层的找)
#主要将每一个对象传出另一个类中当参数,这个对象内部有自己的变量和方法,需要时将他取出来
# 当嵌套过多时,一层一层的将赋值的变量名找到,并取出,因为每一个对象中都封装的一些数据和方法。
# i.class_object.C_object.name 在A类中取变量,在B类中取到对应的变量,在从C中取到对应的值

10.mro与c3算法确定类的基础关系

子类没有去找父类
利用方法知道父类关系
mro():作用于找到父类关系
类.mro() 返回一个列表 类关系
类.__mro__ 返回一个元组 类关系
底层原理 : c3 算法
规则:
mro(A) = [A]+(b,c)
mro(A) = [A,B,C]
使用方式:
mro(A) = [A]+merge(mro(B,object),mro(C,object,D,object,E,object),[B,C,D,E])
重点
1.从merge(第一个mro(元素1))对比merge(第二个mro(元素2)) 是否存
2.不存在剔除掉
3.在从merge(第一个mro(元素1))对比merge(第二个mro(元素2)) 是否存在
4.存在,保留,从merge(第二个mro(元素1)) 取其他的作为比较,不存在剔除。再从第一个mro第一个元素进行匹配
继承关系:
继承关系:从左到右,深度优先。大小钻石,留住顶端。
钻石继承:
-------D------
B--------------C
-------A-------
A-B-C-D

11.元类

# 知识点:exec
# 参数1 字符串形式的命令
# 参数2 全局作用域(字典形式) ,如果不指定就是global
# 参数3 局部作用域(字典形式),如果不使用就是locars
g = { # 定义的全局作用域的字典
'x': 10,
'y': 20
}
l = {} # 定义的局部作用域字典
# global 相当于声明了全局的变量
# x存在于g这个字典中
# m 不存在,那么就是新声明的
# z 没有声明全局,那么就是局部
exec('''
global x,mxxxx
x=100
mxxxx = 1000
z=30
''', g, l)
# print(globals()) # 输出就是 当前的全局的变量 x被修改为100,mxxx存在于全局中定义
# print(locals()) # 'l': {'z': 30}}
****** python 中一切皆对象 *****
1.都可以被引用 x = obj
2.都可以当成函数的参数传入
3.都可以作为函数返回值
4.都可以当做容器的元素 [obj.....]
# 类的类被成为元类
默认用class定义的类,他们的原类都是type
class A:
pass
a = A()
print(type(a)) # <class '__main__.A'>
print(type(A)) # <class 'type'>
# 创建的类的方式
1.class定义
2.type定义
定义类的三要素
1.类名
2.类继承的父类
3.类的名称空间
class_name = 'A' # 类的名称
class_bases = (object,) # 类的父类
calss_boby = ''' # 声明使用type声明类的中的方法
def __init__(self,name):
self.name = name
def func(self):
pass
'''
class_dic = {}
exec(calss_boby, globals(), class_dic) # 将class_boby 声明为字典形式的局部变量
# {'__init__': <function __init__ at 0x000001E7F4B3C550>, 'func': <function func at 0x000001E7F4DAE430>}
A = type(class_name, class_bases, class_dic)
# 参数1 类名, 参数2 类的父类 参数3 类内部的方法和属性
print(class_dic) # <class '__main__.A'>
print(A.__dict__)
****** 自定义元类 控制类的创建 *******
class Mymeta(type): # 默认还是要继承type元类,因为内部需要我们自己用的方法
# 为什么要传入3个参数 因为在type() 创建类时
# 需要传入 类名, 类的父类 类的属性和方法
def __init__(self, class_name, class_bases, class_dic):
print(class_dic)
print(class_name)
print(class_bases)
if not class_name.istitle(): # 首字母大写,不大写抛异常
raise TypeError('类名首字母大写') # raise TypeError('类名首字母大写') 主动抛出异常
if '__doc__' not in class_dic or not class_dic['__doc__'].strip():
raise TypeError('必须有注释,并且不能为空') # TypeError('必须有注释,并且不能为空')
# 因为继承了原类type,当创建init是会覆盖所有要是用父列的
# 继承原类的 __init__
super().__init__(class_name, class_bases, class_dic)
# 自定义元类方法,控制类的行为
# 默认继承的原类就是metaclass = type
class Chinese(object,metaclass=Mymeta): # 将类的原类设置为自定义的原类
'''000''' # 设置类的注释 会赋值给__doc__
country = 'China'
def __init__(self):
pass
def func(self):
pass
'''
print(class_dic)
print(class_name)
print(class_bases)
打印的内容
{'__module__': '__main__',
'__qualname__': 'A', '__init__': <function A.__init__ at 0x000001B480CFE3A0>,
'func': <function A.func at 0x000001B480D17040>} # 类的方法
A 类名
(object) # 继承的父类类
'''
****** 定于元类 控制类的实例化 ******
补充
# __call__ 方法
# 当对实例化对象加()是就会调用__call__
class F: # f的元类默认是type
# 类中的方法如果存在call,那么对象就变为可别调用的 可以加()的
def __call__(self, *args, **kwargs):
print(self) # <__main__.F object at 0x0000020586886580>
print(args) # (1,)
print(kwargs) # {'a': 12}
f = F() # F() 可调用 说明type内部存在 __call__ 方法 会在调用类是触发执行
# 相当于 F.__call__(F,1,a=12)
f(1,a=12) #当对象加括号就会触发 obj.__call(对象本身,1,a=12) 可调用
自定义原类,并且初始化过程模拟
class Mymeta(type):
def __init__(self, class_name, class_bases, class_dic):
print(class_name)
if not class_name.istitle():
raise TypeError('类名首字母大写')
if '__doc__' not in class_dic or not class_dic['__doc__'].strip():
raise TypeError('必须有注释,并且不能为空')
super().__init__(class_name, class_bases, class_dic)
def __call__(self, *args, **kwargs): # obj =Chinese('wkx', 18)
# print(self) # self = Chinese
# print(args) # ('wkx', 18)
# print(kwargs) # {}
初始化过程
# 1.造空对象
# 创建类时默认会继承object类 内部存在一个new方法 new(obj) 传入的参数就是 一个对象本身自己
obj = object.__new__(self) # 初始化一个空的类对象
# print(obj) # <__main__.Chinese object at 0x0000026693B1AFA0>
# 2.初始化obj对象
self.__init__(obj, *args, **kwargs) # 初始化当前类的本身的__init__方法,将对象本身传入和参数
# 3.返回obj
return obj # 创建的对象返回
class Chinese(object, metaclass=Mymeta):
'''000'''
country = 'China'
def __init__(self, name, age):
self.name = name
self.age = age
def func(self):
pass
# chines.__call__(chines,wkx,18)
# 因为 chines 继承了定制的元类,在进行调用类()是就会执行内部的call方法
# 因为自定义的元类中重写了 call方法,那么就会优先使用自定义元类的call方法,而不是默认元类的type中的call方法
obj = Chinese('wkx', 18)
'''
实例化过程
1.创建空对象
2.初始化obj
3.返回obj
而元类的过程也是一模一样的
'''
print(obj.__dict__) # {'name': 'wkx', 'age': 18}
******* 使用创建的元类进行实例化应用 *******
# 定义单利模式 实例化n次还是一个对象
class M:
__instance = None
def __init__(self):
pass
@classmethod
def singleton(cls):
if not cls.__instance:
obj = cls()
cls.__instance = obj
return cls.__instance
a = M.singleton()
b = M.singleton()
print(a is b) # True 说明使用的是同一个对象
# 元类
# 使用元类 进行单利模式
class Mymeta(type):
def __init__(self, class_name, class_bases, class_dic):
print(class_name)
if not class_name.istitle():
raise TypeError('类名首字母大写')
if '__doc__' not in class_dic or not class_dic['__doc__'].strip():
raise TypeError('必须有注释,并且不能为空')
super().__init__(class_name, class_bases, class_dic)
self.__instance = None # 创建一个变量
def __call__(self, *args, **kwargs): # obj =Chinese('wkx', 18)
if not self.__instance: # 判断是否存在这个对象
obj = object.__new__(self) # 没有创建这个对象
self.__init__(obj, *args, **kwargs) # 进行初始化
self.__instance = obj # 赋值
return self.__instance # 返回对象,每一次进行实例化,就会使用同一个对象
class M(object, metaclass=Mymeta):
'''1'''
def __init__(self):
pass
m = M()
m1 = M()
print(m is m1) # True
print(type(M)) # 他的元类就是 <class '__main__.Mymeta'> 自定义元类

Python异常处理与反射

1.异常处理

1.处理原理:
1.基本格式
try
正常逻辑代码
except Exception as e: #捕获全部的异常
e 是一个对象 内部存放的错误信息 可以通过str进行转换
# 先执行 try内的代码,当出现异常是不会报错,会被excepr捕获,赋值给对象e
2.高级格式
try
正常逻辑代码
except Exception as e: #捕获全部的异常
try内的代码异常触发,此处代码。
finally
try中的代码是否报错,finally的代码都会执行。被成为释放资源
# 先执行 try内的代码,当出现异常是不会报错,会被excepr捕获,赋值给对象e 执行finally的代码
2.异常处理的细分
try
正常逻辑代码
except 其他异常 as e:
代码
except 其他异常 as e:
代码
except 其他异常 as e:
代码
except Exception as e: 捕获全部的异常
try内的代码异常触发,此处代码。
3.自定义异常类
class 自定义异常类名(继承Exception类):
pass 代码
try
正确的逻辑代码
# 主动触发异常
raise 自定义议程类名() # 触发异常类 固定格式,只有raise 才能触发自己常见的异常类
except 类名 as e:
当出现异常就会创建一个异常类的对象 赋值给 e ,e自定义异常类的对象
可以将e获取错误
finally
在函数内定义finally无论遇到return 都会返回finally内的代码
案例:
# 自定义异常
class MyException(BaseException): # 继承父类异常类
def __init__(self,msg):
super().__init__() # 继承父类的初始化方法
self.msg = msg
def __str__(self): # 类被调用时执行
return self.msg
raise MyException('抛出自己的错误') # 当抛出错误,就会执行

补充

raise参数的作用:显示与引发异常的作用
当程序出现错误时,python自动触发异常,也可以使用raise进行触发异常
一旦raise触发异常后,python后面的代码就不在执行
如果加入了tryexcept,那么except里面的语句会被执行
try是捕获异常,当raise触发异常时,就会触发except的异常报错
例如:
try:
s = None
if s is None:
print('s是空对象')
raise NameError # 报错
print(len(s))
except Exception: # 捕获异常
print('空对象没有长度') # 触发异常

2.反射

反射:支持以字符串的行式取操作成员 执行getattr
反射的使用方式:
class A :
def __init__(self,a):
self.a = a
def show(self):
pass
a1 = A()
#获取成员内的值
v1 = getattr(a1,“a”) 等价与 a1.a
#如果a变量存在 v1就是a的值,如果不存在就是none
v1 = getattr(a1,“show”,none)() 等价与 a1.show()
# 当利用反射进行判断执行类中的方法,需要添加第三个参数 none ,当方法不存在返回none
# 反射的其他操作
1.添加成员变量
setattr(a1,“b”,“值”) # 等价与 a1.b=“值”
2.判断成员是否存在
v1 = hasattr(a1,“a”)
print(v1) #返回结果 True/False
3.删除成员
delattr(a1,“a”) # 等价与 del a1.a
# 反射的组合操作
# import_module +反射 配合是用可以增加项目代码的扩展性
1.导入模块
from importlib import import_module
2.使用模块名
对象 = import_module("模块名称") #只能导入模块这个级别
# 正常使用方式 m.名称的方法 通过这种方式调用
3.使用方法
getattr(对象,模块的方法)

Python网络编程

计算机网络基础

1.计算机

计算机硬件
操作系统 系统软件 控制程序 让计算机硬件进行启动会起来
qq 微信 引用软件 应用软件是基于操作系统进行控制计算机硬件

2.网络设备

二交换机:
内部只会维护 接口和mac地址,局域网内的交换
每台电脑存在mac地址,电脑主板换掉,mac地址就会换
路由器:
内部存在网管
实现局域网和局域网之间的通信
三层交换机:
具有路由器的功能,也有尔晴交换机的功能
企业网络架构
光猫-核心路由器-防火墙-核心交换机(3层)-接入交换机(2层)
家庭网路架构
运营商-光猫-家用路由器
被覆盖的部分是:掩码部分,
没有覆盖:主机部分
子网掩码:为了掩盖电脑的ip地址
ip:ip地址代指这台电脑 32位的2进制
二进制:00000000.00000000.0000000......
十进制:255.255.255.15
0-255
DHCP服务:
开启后 自动随机设置网管和掩码,王关
以太网-属性-ipv4-使用下面的ip地址(手动编写)
内网ip:自己组件组建的网段
10.0.0.0-10.255.255.255
172.16.0.0.-172.31.255.255
192.168.0.0.-192.168.255.255
公网ip:运营商分配的ip地址
云服务器:
不需要自己配置服务器,其他公司整的服务器,把程序放到他的服务器上去运行。
端口:网站会存在一个端口:端口指向一个网站或者程序
0-65535 端口范围
0-5000 都存在含义
域名:用户很难记住ip和端口,就需要一个域名
根据域名寻找ip
获取ip地址再去访问网址
先域名解析,在去访问ip

3.网络是什么

计算机与计算机之间的通信
计算机之间的通信需要 一个标准进行通信 标准:互联网协议
# 例如:
计算机1: 发数据按照互联网协议发送
计算机2: 接受计算机1的数据 根据互联网协议进行反解
# 互联网协议:osi 七层协议
1.物理层:
与硬件相关 发送的是二进制数据 01001010 发电报
2.数据链路层:
将数据进行分组,赋予2进制数据意义
根据以太网协议:
1.将一串电信号 为数据包
2.数据报 分为:抱头 和包内容
包头规定 18 个字节: 前6字节原地址mac 6字节接受地址mac 6字节数据描述
包内容:就是主要的传递内容
以太网的工作方式:广播方式 基于mac地址进行广播方式进行数据传入的
mac地址只能表示一个计算的地址在哪里
广播的方式:必须要在同一个局域网之内,但是局域网存在多个,那么怎么根据mac地址找到不同局域网的进行传递数据(网络层)
3.网络层:
ip协议,每个机器配一个ip地址
也分为ip头部分和ip数据部分 固定字节
原地址 目标长度
ip地址就是:找到对应的子网地址(局域网)
# ip + mac 地址 就可以找到世界中的某个计算机的位置
ip 负责找 子网的位置
mac负责在子网中找到对相应的机器
4.传输层:
tcp 与 utp 协议 基于端口协议
tcp与utp头 和 包数据
基于端口操作 0-65535
一台机器中的端口就是一个软件
ip + 端口 就能表示全是键一个独一无二的软件
# 发包 就是封装包的数据
# 收包 就是解包的数据
5.应用层:
执行的硬用软件
应用软件自己规定的
应用层的软件头 + 数据
# 传输数据封装过程 从头开始一层一层的封装到
# 解数据,就是从尾部一点一点解开
6.数据链层
报头 + 数据(网络层 ip的头部 + 数据(传入层tcp 或者 utp 协议头 + 数据(硬用层头 + 数据)))
7.物理层
将数据链层的数据转为电报(2进制010101)
简单理解
OSI 7 层模型:
应用层:规定数据格式
表示层:压缩数据,分块,加密
会话层:负责服务端建立,关闭
传输层:确定端口 双方的端口
网络层:标记目标ip信息(ip协议)
数据链层:对数据分组设置和目标mac地址
物理层:将数据打包成为2进制传输

4.tcp/udp

UDP协议/TCP协议 都是在传输层进行定义的:
UDP协议:是一个无连接简单的面向数据的传输协议,UDP不提供可靠,它只是将应用程序传给IP层的数据发出去
不能保证能不能到达目标地址。不予客户端建立链接,超时没有重发机制,传输速度慢。
语音通话,视频通话,实时游戏画面
速度快,不需要链接
TCP协议:是面向链接的协议,在收发数据,必须和对方建立链接,在进行收发数据
存在重发数据机制
网站,手机App
1.tcp 协议:
可靠协议:发送者需要接受者的反馈
可靠,但是开销大,需要建立链接
流式协议,以水流一样传递
客户端 与 服务端 之间需要建一个 双向管道
在没有传入数据之前,就要建好一个双向的管道
2.udp协议:
不可靠的协议
不用创建链接 开销低,效率高
没有链接存在
在发送数据,直接丢给服务端或者客户端,不需要知道对方是否接受
3.tcp的三次握手与四次挥手
三次握手
服务端 <<< ----- 客户端 告诉服务端 进行链接(会传输数据随机生成)
服务端 ----- >>> 客户端 服务端在回一个数据+1 返回给客户端
服务端 <<< ----- 客户端 确定链接
四次挥手
服务端 <<< ----- 客户端 服务端或者客户端我要关闭连
服务端 ----- >>> 客户端 收到消息 有数据未处理
服务端 ----- >>> 客户端 在返回消息 数据处理完毕
服务端 <<< ----- 客户端 同意关闭

socket

1.什么是socket

套接字编程,因为tcp 与 utp协议,古老而庞大,研究下来时间过长
所以出现了socket
socket 是基于 应用层 传输层 之间的一个抽象层
负责将用户的数据转为tcp 与 utp协议的 内容传给传输层-网络层-数据链层-物理层
基于socket 写出的程序自然就是按照tcp 与 utp 协议
tcp 与 utp 使用的协议不同
TCP/UTP区别于:UTP:socket.SOCK_DGRAM 不同/TCP:socket.SOCK_STREAM不同

2.socket-utp

# UTP协议socket的代码
server 服务端
import socket
sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # socket.SOCK_DGRAM参数不同
sock.bind(('127.0.0.1',8001))
while True:
data,(host,post) = sock.recvfrom(1024) # 接受3个参数
print(data,host,post) # data 是收到的内容, (host,post) 是ip地址和端口
sock.sendto('返回的消息'.encode('utf-8'),(host,post)) # 将内容发送,发到指定的端口
client 客户端 socket.SOCK_DGRAM
import socket
client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # 实例对象
while True:
client.sendto('发送消息'.encode('utf-8'),('127.0.0.1',8001))
#将内容发送给指定的 ip 和端口
data, (host, post) = client.recvfrom(1024) # 接受3个参数
print(data.decode('utf-8')) # 将发送的消息转换
break
client.close() # 关闭链接

3.socket-tcp

server 服务端:
import socket
# 买手机
# AF_INET 基于 网络通信套接字
# AF_UNIX 本机通讯套接字
# socket.SOCK_STREAM 基于tcp协议
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定手机卡 传入(host,post) ip 端口
phone.bind(('127.0.0.1', 8000))
# 开机
phone.listen(5) # 最大链接的数量5个
# 等待链接
print('等待链接中....')
# 对象1 链接对象 对象2 链接对象的ip和端口
conn, client_add = phone.accept() # 阻塞状态,等待链接 返回两个对象
# 收发消息
data = conn.recv(1024) # 接受1024个字节 最大接受1024字节 最大的显示
print(data)
# 回消息
conn.send(b'1001')
# 挂电话
conn.close() # 关闭链接
# 关机
phone.close() # 关闭服务
client 客户端
import socket
# 买手机
# AF_INET 基于 网络通信套接字
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 播好进行链接
phone.connect(('127.0.0.1', 8000)) # 链接当前id端口服务器
# 发送消息
phone.send(b'hahah')
# 接受消息
data = phone.recv(1024)
print(data)
# 关闭消息
phone.close()

socket-tcp修复问题与循环操作

server 服务端
import socket
# 买手机
# AF_INET 基于 网络通信套接字
# AF_UNIX 本机通讯套接字
# socket.SOCK_STREAM 基于tcp协议
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 套接字对象
# 重用当前的端口,端口不会被系统回收
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定手机卡 传入(host,post) ip 端口
phone.bind(('127.0.0.1', 8000))
# 开机
phone.listen(5) # 最大链接的数量5个 进行3次握手
print('等待链接中....')
# 对象1 链接对象 对象2 链接对象的ip和端口
conn, client_addr = phone.accept() # 阻塞状态,等待链接 返回两个对象
print(client_addr)
# 等待链接
while True: # 通信循环
try:
# 收发消息
# 接受1024个字节 最大接受1024字节 最大的显示
data = conn.recv(1024) # 告诉操作系统,取网卡中拷贝发送的数据,给我
# if not data: break # 当客户端单方面关闭,linux
print(data.decode('utf-8'))
msg = input('>>:').strip()
# 回消息
conn.send(msg.encode('utf-8'))
except ConnectionResetError as e: # 当客户端单方面关闭 就会报错,是用错误获取机制,将服务关闭
break
# 挂电话
conn.close() # 关闭链接
# 关机
phone.close() # 关闭服务
client 客户端
import socket
# 买手机
# AF_INET 基于 网络通信套接字
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8000)) # 链接当前id端口服务器
# 播好进行链接
while True: # 通信循环
msg = input('>>:').strip()
if not msg:continue
# 发送消息
phone.send(msg.encode('utf-8')) # 将存在内存的数据msg,发给操作系统,操作系统在传给网卡,在进行发送
# 接受消息
data = phone.recv(1024)
print(data.decode('utf-8'))
# 关闭消息
phone.close()

加上通信循环

client 客户端
import socket
# 买手机
# AF_INET 基于 网络通信套接字
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8000)) # 链接当前id端口服务器
# 播好进行链接
while True:
msg = input('>>:').strip()
if not msg:continue
# 发送消息
phone.send(msg.encode('utf-8')) # 将存在内存的数据msg,发给操作系统,操作系统在传给网卡,在进行发送
# 接受消息
data = phone.recv(1024)
print(data.decode('utf-8'))
# 关闭消息
phone.close()
server 服务端
import socket
# 买手机
# AF_INET 基于 网络通信套接字
# AF_UNIX 本机通讯套接字
# socket.SOCK_STREAM 基于tcp协议
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 套接字对象
# 重用当前的端口,端口不会被系统回收
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定手机卡 传入(host,post) ip 端口
phone.bind(('127.0.0.1', 8000))
# 开机
phone.listen(5) # 最大链接的数量5个 进行3次握手
print('等待链接中....')
while True: # 链接循环 不会停止,一直循环监听循环
# 对象1 链接对象 对象2 链接对象的ip和端口
conn, client_addr = phone.accept() # 阻塞状态,等待链接 返回两个对象
print(client_addr)
# 等待链接
while True: # 通信循环
try:
# 收发消息
# 接受1024个字节 最大接受1024字节 最大的显示
data = conn.recv(1024) # 告诉操作系统,取网卡中拷贝发送的数据,给我
# if not data: break # 当客户端单方面关闭,linux
print(data.decode('utf-8'))
msg = input('>>:').strip()
# 回消息
conn.send(msg.encode('utf-8'))
except ConnectionResetError as e: # 当客户端单方面关闭 就会报错,是用错误获取机制,将服务关闭
break
# 挂电话
conn.close() # 关闭链接
# 关机
phone.close() # 关闭服务
11个进行服务,不会断开服务,等待上一次客户端断开后,下一个客户端才会链接

模拟ssh远程执行命令-项目分析

******** 命令只是点了解 *******
# 什么是命令,就是执行系统的命令
# win
# dir:查看某一个文件下的子文件名和子文件名
# ipconfig:查看本地网卡
# tasklist:查看运行的进程md
# 如何在机器上执行命令
# os 模块 中的system 方法就可以执行和cmd命令一样
# import os 不可使用
# os.system('dir /') # 只能拿到命令执行的结果的成功还是失败
# 需要使用subprocess
# shell=True 就是将字符串转为cmd的命令
import subprocess
obj = subprocess.Popen('dir C:\\Users\\56515', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# win 编码 是gbk mac是utf-8
# 从管道中取
print(obj.stdout.read().decode('gbk')) # 正常的命令
print(obj.stderr.read().decode('gbk')) # 错误的命令
******* 服务端 *******
import socket
import subprocess
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 重用当前的端口,端口不会被系统回收
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定手机卡 传入(host,post) ip 端口
phone.bind(('127.0.0.1', 8000))
# 开机
phone.listen(5) # 最大链接的数量5个 进行3次握手
print('等待链接中....')
while True: # 链接循环 通信循环,接受客户端
conn, client_addr = phone.accept() # 阻塞状态,等待链接 返回两个对象
print(client_addr)
# 等待链接
while True: # 通信循环
try:
# 1.接受命令
data = conn.recv(1024)
print(data.decode('utf-8'))
# 2.执行命令,拿到结果
obj = subprocess.Popen(data.decode('utf-8'), shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
# 3.返回命令执行的结果
stderrs = obj.stderr.read()
stdouts = obj.stdout.read()
if not stdouts:
conn.send(stderrs)
conn.send(stdouts)
except ConnectionResetError as e: # 当客户端单方面关闭 就会报错,是用错误获取机制,将服务关闭
break
# 挂电话
conn.close() # 关闭链接
# 关机
phone.close() # 关闭服务
******* 客户端 ********
import socket
# 买手机
# AF_INET 基于 网络通信套接字
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8000)) # 链接当前id端口服务器 进行3次握手
while True:
# 1.发命令
msg = input('>>:').strip()
if not msg: continue
phone.send(msg.encode('utf-8'))
# 2.拿到命令结果
data = phone.recv(1024)
print(data.decode('gbk')) # win 解码 需要使用cmd的内容gbk
# 关闭消息
phone.close()

4.IO多路复用

服务器:当等待客户端链接时,处于阻塞状态
客户端:与服务器连接时,也是阻塞状态
如何避免阻塞:
1.在创建服务端
加入:
服务器对象.setblocking(False)# 变为非阻塞状态
2.在客户端
加入:
客户端对象.setblocking(False)# 变为非阻塞状态
IO多路复用:配合 非阻塞 一起使用的一种技术,当没有链接时,可以执行别的代码。
IO读哟路复用基于select模块进行使用
案例:
import select # IO多路复用模块
import socket # 网络链接模块
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.setblocking(False) # 变为非阻塞状态
sock.bind(('127.0.0.1',8001))
sock.listen(5)
inputs = [sock,] #sock的 对象列表 监听列表
while True:
r,w,e = select.select(inputs,[],[],0.5)
# 0.5:用0.5秒的时间取 检测 inputs列表的 sock的对象,是否有新链接到来
# 当没有发生变化时(新链接发起请求),inputs被检测的列表就没有变化,是个 r = []列表
# 当有新链接到来时,r =[sock,] ,sock发生变化就会出现在r 的列表中别检测
# 在最开始 r=[] 是个空的列表,inputs 别检测的列标中发生变化 r列表就会添加发生变化的对象 r=[发生变化的对象]
for sock in r:
if sock = sock : # 确保 是否有新链接
conn,addr = sock.accept()
print("新链接")
inputs.append(conn) # 将新链接 添加到监控列表中 inputs = [sock,第一个客户端对象 conn]
else:
data = sock.recv(1024) # 当客户端发送的是空数据时,判断为关闭链接,就会将客户端移除检测列表
if data:
print(新消息,data)
else:
print('关闭链接')
inputs.remove(sock)
# 其他数据
1.两大优点:
1.没有新客户端到来,可以利用空档期,去处理其他的数据
2.可以支持多个客户端进行来接。不在等待1个客户端来发送消息。
3.客户端可以伪造并发现象
2.参数:
'''
r,w,e = select.select([第一个列表],[第二个列表],[第三个列表],0.5)
第一个列表:监听服务端有没有 发送数据 监听 sock对象是否有发送数据。
第二个列表:监听 sock对象是否和服务端 监听链接是否成功
第三个列表:监听 sock对象是否发生异常
0.5:监听时间
'''
IO多路复用的三种模式
1.select.select([],[],[],监听时间) # 逐一遍历,一个一个进行检测 , 最大1024个sock对象
2.select.poll([],[],[],监听时间) # 逐一遍历,一个一个进行检测 ,没个数的限制
3.select.epoll([],[],[],监听时间) # 基于回调机制,那个sock对象发生变化,那个就将数据返回,效率更高基于 ,没有数据的限制

5.sockeserver模块

服务器框架:
服务器
import socketserver
'''
1 创建功能类 继承seocketserver
class MyServer(socketserver.BaseRequestHandler):
def handle(self) -> None:
pass 将全部的并发方法写入到handle中
2 socketserver.ThreadingTCPServer( 传入端口 和 创建的功能类)
socketserver.ThreadingTCPServer(('127.0.0.1',8000),MyServer)
3 调用serve forever
serve.serve_forever()
'''
class MyServer(socketserver.BaseRequestHandler):
def handle(self) -> None:
'''
书写并发的逻辑
self.request 是客户的套接字对象
'''
while 1:
chient_data = self.request.recv(1024) # 客户端的传入的值
if not chient_data: break
print('接受的值', chient_data)
response = input('>>>发送值:')
self.request.sendall(response.encode('utf-8'))
self.request.close()
# 1.创建一个socket对象 2. 绑定了ip和端口 3.规定了最大链接数listen(5)
serve = socketserver.ThreadingTCPServer(('127.0.0.1', 8000), MyServer)
# 1.返回conn 和 attr 2.开启多线程模式 3.内部将conn赋值了request arrt赋值client_address 内部try 调用了def handle(self)方法
serve.serve_forever()
客户端
import socket
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.connect(('127.0.0.1', 8000))
print('服务端启动')
while True:
inp = input('发送服务端>>>')
sock.send(inp.encode('utf-8'))
response = sock.recv(1024)
print(response.decode('utf-8'))

粘包现象

如果没有收到的信息,没有收完时就会出现粘包的现象
粘包的底层原理分析:
send 通知操作系统 有数据要发送个自己操作系统的内存中,有没有被操作系统发送 也不知道(tcp协议),但是客户端和服务端是隔离的
recv
对方发送到网卡的缓存中,
由操作系统从内存将数据拷贝到系统中
都不是从自己的直接接受数据,而是在自己的操作系统拷贝网卡存储的在内存中的数据
不是一个send(可能是多个) 对应一个 recv

处理机制

粘包: 当客户端快速发送两个数据时,会出现粘包的现象。
解决粘包:先发送数据长度,在接受数据。
粘包基于 struct模块实现的
1.struct模块 进行获取长度 ,将数据转换为字节。
# 使用方式
import struct # 1.导入
# 2.将数字转为为固定的4个字节
v1 = struct.pack("i",数字/数字变量)
# 第一个参数为模式 “i”, 第二个参数为整数/整数的变量
print(v1) # 将数字转换为 就是4个固定字节
# 3.将字节转换为数字
v2 = struct.unpack('i',v1)
print(v2) # 字节转换为数字
2.粘包的处理过程
# 第一条数据
1.先去读取4个字节 ,就确定的数据的长度
2.直接读取长度
3.根据长度获取 数据
# 第二条数据
1.在去读取4个字节,确定长度
2.直接读取长度
3.根据长度获取 数据
3.粘包的网络编程案例
# 服务器:
import socket
import struct
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.bind(('127.0.0.1',8001))
sock.listen(5)
conn,addr = sock.accept()
# 接受第一个数据
header1 = conn.recv(4) # 读取4个字节
data_length1 = struct.unpack("i",header1)[0] #将字节转换为数值
# data_length1 就是第一条数据的总长度
has_recv_len = 0 # 设置接受时的起始长度,每次接受的字节长度加载这个变量
date1 = b'' # 将接受的字节加在一起
# 开始接受数据
while True:
leng1 = data_length1 - has_recv_len
# 现有的数据长度 总长度-已知长度 ,作用判断数据传入的总共是多少
# 还需传入多少
if leng1>1024 : # 字节最大数量接受为1024字节最大
lth =1024
else
lth = leng1 # 如果小于1024个字节就接受 剩余的字节
chunk = conn.recv(lth) # 判断的字节进行接受
has_recv_len += len(chunk) # 将每次传入的字节,转换为长度和以传长度相加
date1 += chunk # 将传入的字节,和已传的字节相加
if has_recv_len == data_length1:
# 传入的长度 和 总长度相同时 退出(数据传入完毕)
break
print(date.decode('utf-8')) # 打印
#接受第二条数据
header2 = conn.recv(4) # 接受的第一个4个字节是,客户端传入的数据长度
data_length2 = struct.unpack("i",header2)[0] #将字节转换为数值
data2 = conn.recv(data_length2) # 接受长度
print(data2.decode('utf-8'))
conn.close()
sock.close()
# 客户端
import socket
import struct
sock = socket.socket()
sock.connect(('127.0.0.1',8001))
# 第一条数据
data1 = '数据1'.encode('utf-8') # 转换为字节
header1 = struct.pack('i',len(data1)) # 转换为4个字节的长度
sock.sendall(header1) # 先传入数据的大小
sock.sendall(data1) # 在传入数据的本身
# 第二条数据
data2 = '数据2'.encode('utf-8') # 转换为字节
header2 = struct.pack('i',len(data2)) # 字节长度,转换为4个字节
sock.sendall(header2) # 先传入数据的大小的字节
sock.sendall(data2) # 在传入数据的本身
sock.close()

1.简单的处理粘包

****** 服务器 *******
import socket
import subprocess
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 重用当前的端口,端口不会被系统回收
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定手机卡 传入(host,post) ip 端口
phone.bind(('127.0.0.1', 8000))
# 开机
phone.listen(5) # 最大链接的数量5个 进行3次握手
print('等待链接中....')
while True: # 链接循环 通信循环,接受客户端
conn, client_addr = phone.accept() # 阻塞状态,等待链接 返回两个对象
print(client_addr)
# 等待链接
while True: # 通信循环
try:
data = conn.recv(8096)
print(data.decode('utf-8'))
obj = subprocess.Popen(data.decode('utf-8'), shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
# 拿到执行cmd命令结果,成功报错
stderrs = obj.stderr.read()
stdouts = obj.stdout.read()
import struct 可以将数据变为字节类型
# 1.发送数据的长度,但是必须设置标志 将包头发给客户端 固定长度
'''
使用struct 模块 将计算的长度进行打包(固定就是4个字节,不论数据多大都是4个字节)
打包
res = struct.pack('i',1024000) # res 4个字节的值,不论数字多大都是
解包
struct.unpack(i,res) # 返回元组类型(10244400,) 第一个元素就是
'''
# 发送报头 4个字节存储的就是 当前数据的长度
size = len(stdouts) + len(stderrs)
header = struct.pack('i',size)
conn.send(header)
# 2.发送数据 会出现粘包的情况
conn.send(stdouts)
conn.send(stderrs)
except ConnectionResetError as e: # 当客户端单方面关闭 就会报错,是用错误获取机制,将服务关闭
break
# 挂电话
conn.close() # 关闭链接
# 关机
phone.close() # 关闭服务
******** 客户端 **********
import socket
# 买手机
# AF_INET 基于 网络通信套接字
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8000)) # 链接当前id端口服务器 进行3次握手
while True:
msg = input('>>:').strip()
if not msg: continue
phone.send(msg.encode('utf-8'))
# 1.拿到数据长度 收到包头
import struct
header = phone.recv(4) # 接受包头,的数据4个字节存储的就是传入数据的长度
size = struct.unpack('i', header)[0] # 将包头给解出来,返回的元组,第一个元素就是长度
# 2.接受真实的数据描述信息
recv_size = 0
recv_data = b''
while recv_size < size:
data = phone.recv(1024) # 存在粘包信息
recv_data += data
recv_size += len(data)
print(recv_data.decode('gbk'))
# 关闭消息
phone.close()
'''
import struct 可以将数据变为字节类型
使用struct 模块 将计算的长度进行打包(固定就是4个字节,不论数据多大都是4个字节)
打包
res = struct.pack('i',1024000) # res 4个字节的值,不论数字多大都是
解包
struct.unpack(i,res) # 返回元组类型(10244400,) 第一个元素就是
i 模式 有最大上限长度
l 模式也是最大上限长度
'''

2.高级的处理粘包

**** 服务端 ******
import socket
import subprocess
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 重用当前的端口,端口不会被系统回收
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定手机卡 传入(host,post) ip 端口
phone.bind(('127.0.0.1', 8000))
# 开机
phone.listen(5) # 最大链接的数量5个 进行3次握手
print('等待链接中....')
while True: # 链接循环 通信循环,接受客户端
conn, client_addr = phone.accept() # 阻塞状态,等待链接 返回两个对象
print(client_addr)
# 等待链接
while True: # 通信循环
try:
data = conn.recv(8096)
print(data.decode('utf-8'))
obj = subprocess.Popen(data.decode('utf-8'), shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stderrs = obj.stderr.read()
stdouts = obj.stdout.read()
import struct
import json
# 第一步:制作固定的报头发给客户端
header_dict = {
'filename': "xx.txt",
'md5': 'xxx',
'total_size': len(stdouts) + len(stderrs) # 数据的长度
}
header_json = json.dumps(header_dict)
header_bytes = header_json.encode('utf-8')
# 第二步 将包头长度进行发送
header = struct.pack('i', len(header_bytes)) # 只能接受b类型
conn.send(header)
# 第三步 将包头信息发送出去
conn.send(header_bytes) # 只能接受bytes类型
# 第四步 将真实数据发送
conn.send(stdouts)
conn.send(stderrs)
except ConnectionResetError as e: # 当客户端单方面关闭 就会报错,是用错误获取机制,将服务关闭
break
# 挂电话
conn.close() # 关闭链接
# 关机
phone.close() # 关闭服务
***** 客户端 ******
import socket
# 买手机
# AF_INET 基于 网络通信套接字
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8000)) # 链接当前id端口服务器 进行3次握手
while True:
msg = input('>>:').strip()
if not msg: continue
phone.send(msg.encode('utf-8'))
# 1.拿到数据长度 收到包头
import struct
# 1.接受报头长度
header = phone.recv(4)
# 将长度获取 元组类型
header_size = struct.unpack('i', header)[0]
# 2.接受报头信息
header_bytes = phone.recv(header_size) # 接受报头的长度
import json
# 3.从报头信息中获取 主要内容的长度
header_dict = json.loads(header_bytes) # 解析为字典
print(header_dict)
size = header_dict.get('total_size') # 获取主要内容长度
# 4.解析真正的内容
recv_size = 0
recv_data = b''
while recv_size < size:
data = phone.recv(1024) # 存在粘包信息
recv_data += data
recv_size += len(data)
print(recv_data.decode('gbk'))
# 关闭消息
phone.close()
将报头 变为字典形式进行存储(主要内容的长度),转为json格式,将json格式转为 字节格式
在将字节格式进行len 获取长度,在使用struct.pack('i',报头)设置固定的长度,将内容发送(报头的长度)
在将字节类型的报头传给客户端

3.tcp套接字传输文件

***** 客户端 *****
import socket
# 买手机
# AF_INET 基于 网络通信套接字
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8000)) # 链接当前id端口服务器 进行3次握手
dow_dir = '客户端下载的路径,要与打开的文件 拼接'
while True:
# 1.发送命令
msg = input('>>:').strip()
if not msg: continue
phone.send(msg.encode('utf-8'))
# 2.写的方式打开一个新的文件,收到服务发来的文件写入文件
import struct
# 1.接受报头长度
header = phone.recv(4)
# 将长度获取 元组类型
header_size = struct.unpack('i', header)[0]
# 2.接受报头信息
header_bytes = phone.recv(header_size) # 接受报头的长度
import json
# 3.从报头信息中获取 主要内容的长度
header_dict = json.loads(header_bytes) # 解析为字典
print(header_dict)
size = header_dict.get('filename_size') # 获取文件的长度
filename = header_dict.get('filename')
# 4.解析真正的内容
# 不可以和服务端文件路径进行重复
with open(filename, 'wb') as f:
# 获取文件的长度,进行循环写入到一个新的文件中
recv_size = 0
while recv_size < size:
data = phone.recv(1024)
f.write(data)
recv_size += len(data)
# 关闭消息
phone.close()
******* 服务端 *********
import socket
import os
import struct
import json
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 重用当前的端口,端口不会被系统回收
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
ser_dir = '服务端存放的文件夹 与 客户端发送的文件名要进行拼接'
# 绑定手机卡 传入(host,post) ip 端口
phone.bind(('127.0.0.1', 8000))
# 开机
phone.listen(5) # 最大链接的数量5个 进行3次握手
print('等待链接中....')
while True: # 链接循环 通信循环,接受客户端
conn, client_addr = phone.accept() # 阻塞状态,等待链接 返回两个对象
print(client_addr)
# 等待链接
while True: # 通信循环
try:
# 1.收到命令
data = conn.recv(8096) # get 1.txt
print(data.decode('utf-8'))
# 2.解析命令,获得响应参数
cmds = data.decode('utf-8').split() # [get,1.txt]
filename = cmds[1] # 获取文件名
# 将文件内容写入固定长度当为报头写入
# 第一步:制作固定的报头发给客户端
header_dict = {
'filename': filename,
'md5': 'xxx',
'filename_size': os.path.getsize(filename) # 文件长度 获取文件大小要是需要进行路径拼接的
}
header_json = json.dumps(header_dict)
header_bytes = header_json.encode('utf-8')
# 第二步 将包头长度进行发送
header = struct.pack('i', len(header_bytes)) # 只能接受b类型
conn.send(header)
# 第三步 将包头信息发送出去
conn.send(header_bytes) # 只能接受bytes类型
# 3.以读的形式进行,让客户端进行文件下载
# 打开和写入不能与客户端的重复问题
with open(filename,'rb') as f:
# 将文件内容传递 循环
for line in f:
conn.send(line)
except ConnectionResetError as e: # 当客户端单方面关闭 就会报错,是用错误获取机制,将服务关闭
break
# 挂电话
conn.close() # 关闭链接
# 关机
phone.close() # 关闭服务

Python进程与线程

操作系统

操作系统是什么

1.协调管理计算机硬件资源与软件控制程序
将硬件复杂的细节控制起来形成一个接口给应用程序用
2.管理机器之上的多个进程(qq,微信....)
操作系统存储在硬盘之中,
当启动电脑时,就会将硬盘的操作系统代码启动起来,读到内存之中
cpu就从内存中读取操作系统的代码进行执行 (操作系统进程)
当启动qq时,qq的快捷方式是一个绝对的路径
提交给系统一个qq的绝对路径,操作系统(在内存中)到硬盘的根据qq的绝对路径,将qq读到内存中(qq的代码就在内存中),操作系统就调用cup让cup读取内存中的qq的代码(qq进程)执行
操作系统负责调用应用程序

介绍操作系统发展史

第一代的计算机: 真空管 与穿孔卡片
没有进程和编程语言的概念 浪费了计算机资源
第二代计算机: 晶体管和批处理系统
将生产人员和操作人员系统人员与维护人员进行分开
有了操作系统的概念
fortran语言或汇编语言 写入纸上,让后穿孔 成为卡牌
一大堆程序 输入 与 输出
一个一个执行叫做串行执行
节省时间
1401 程序输入输出 io操作
7094 负责就计算
第三代:集成电路和多道技术(还是在批处理系统)
多道程序:就是让cup 在对io操作时 不让cup等待,同时让他干别的
空间复用:内存存入多个程序,物理层面上需要进行程序在内存中进行隔离,如果程序A与程序B 在物理层面不隔离,A关闭,那么B也会关闭,同时操作系统也在内存中,同时也会关闭
时间的复用:共享cup的时间,提升cup的执行效率,当程序发生io操作,就进行切换到其他的程序中,程序io操作的状态进行记录,当代下次cup执行就接着执行这个程序
被称为并发的效果
遇到io cup 进行切换,一个程序运行长了 也要进行cup进行切换
分时操作系统
多个联机终端 + 多道技术
基于多道技术:针对单个处理进行的并发处理
空间复用
1.将程序读到内存中,进行隔离开来
时间复用
cup在多个进程之间进行切换
当cup执行当前程序遇到io操作(记录程序的当状态)进行切换到其他的程序中执行。在进程进行切换执行,看似多个程序进程在执行,的并发现象
多道技术:多核cup的话就是并行执行

进程的理论

1.进程?
正在进行的过程或者说一个任务呀,负责执行任务是cup
2.进程和程序的区别?
1.程序时一堆代码,而进程是程序的运行过程
2.进程就是系统在阅读程序的代码,将程序的代码执行的总和
3.执行
如果出现优先级更高的任务出现,那么cup就会先记录当前执行的任务的状态,记录完毕后,开始执行更高的优先级的任务,同一个程序执行两次,那么就是两个进程
4.进程创建过程
1.系统初始化
2.一个进程在运行中开启子进程(如nginx开启多进程)
3.用户的交互式请求,创建一个新进程(点击启动qq)
4.一个批处理作业的初始化(只在大型机的批处理系统)
5.无论哪一种,新进程的创建都是由一个存在的进程执行了一个用于创建进程的系统调用的而创建的
1.在unix中系统中调用 fork foke会创建一个与父进程一模一样的副本,二者都有相同的映射 同样的环境字符串,同样的打开文件(在shell解释器进程中,执行一个命令就会创建一个子进程)
2.在win中系统调用是createprocess 处理进程的创建,也负责把正确的程序装入新进程中
# 相同点:
进程创建后,父进程和子进程有各自不同的地址空间(多道技术要求物理层面的实现进程与进程之间的隔离)任何进程在修改自己的内容,不会影响其他的进程
# 不同点:
在unix中的子进程的初始地址空间是父进程的副本,子进程和父进程可以只读共享内存的,win系统中,父进程与子进程内存地址就不同
# 进程状态
运行态
阻塞态
进行态
# 子进程创建时
子进程创建时与主进程是隔离的。
子进程会复制主进程的全部参数内容值
进程之间可以共享硬盘之间的内容文件数据库之类的

1.进程创建

# 方式1 调用进程对象执行
import time
from multiprocessing import Process
def func(*args, **kwargs):
print(args)
time.sleep(3)
if __name__ == '__main__':
# args 传入的是元组,位置传参,kwargs 传入的关键字传参
p = Process(target=func, args=('子进程1', '这是子进程1')) # 调用子进程
p.start() # start给操作系统一个信号,开启一个进程
print('主')
#方式2
继承当前的进程类
class Myprocss(Process):
def __init__(self,name):
super(Myprocss, self).__init__()
self.name = name
def run(self):
print(self.name)
time.sleep(1.5)
if __name__ == '__main__':
p = Myprocss('wkx') # 调用子进程
p.start() # start给操作系统一个信号,开启一个进程 start默认自动进程执行run方法
print('主')
在python下执行当前py脚本时,当前执行的脚本到p.start()的时候会创建一个子进程
创建的子进程是当前执行py脚本的子进程
进程的执行:
当执行当前py 文件时,会生成一个主进程,从代码的上面执行到下面
当执行到start()的时候,就会给操作系统一个信号,创建一个子进程,执行func函数
所以:
主进程就会先打印
子进程好打印

2.进程的方法

import time,os
from multiprocessing import Process
def func(*args, **kwargs):
print(args)
time.sleep(3)
if __name__ == '__main__':
p = Process(target=func, args=('子进程1', '这是子进程1'),name='进程1的名字')
p1 = Process(target=func, args=('子进程2', '这是子进程2'))
p.start()
p1.start()
p.join()
p1.join()
print('主')
print(p.pid)
print(p.is_alive())
p.terminate()
print(p.name)
os.getpid
os.getppid
# 方法解析
1.创建进程,不会立即创建而是给cup发送信号告诉操作系统创建一个子进程
如果需要创建多个进程,那么需要等待上面的发送完毕信号下面创建进程才会发送信号 并行
进程对象.start()
2.等待子进程执行完毕后,父进程才会执行,也就让当前的主进程等待 子进程执行完毕后,在执行
进程对象.join()
3.查看当前子进程id
进程对象.pid()
4.查看子进程是否存活
进程对象.is_alive()
5.发送信号终止进程,也是发送一个信号,而不是立即执行
进程对象.terminate()
6.查看进程的名字
进程对象.name 默认为:Process-开启进程的数量递进
7.执行py文件进程id
os.getpid()
8.当前py文件的父id
os.getppid()
getpid 查看当前运行进程
getppid 查看当前运行进程的父进程
理解:
当开启系统是,就会创建一个进程,当点击运行pycharm时,就会在系统的进程中复制一个子进程,启动pycharm
也就是:
操作系统的进程 主进程
pycharm的进程 子进程
当在pycharm中执行一个py文件时,就会创建一个进程,而这个进程是由pycharm的进程中复制出来的一个子进程
也就是
pycharm的进程 主进程
py文件 子进程
当在py文件执行调用了multiprocessing模块创建一个子进程时
也就是,就会从当前py进程复制一个子进程
py文件 主进程
multiprocessing创建的进程 子进程

3.僵尸进程和孤儿进程?

1.僵尸进程:
当父进程执行时需要看到自己进程下的子进程的状态,如果子进程消失,不见了,不行,必须让父进程知道当前的子进程们的执行状态是什么样子的,所有的子进程一旦死掉(执行完毕)就会进入僵尸进程的状态(会将内存清除,但是保留当前的子进程状态),作用就是当前父进程 要看子进程都可以看到子进程,当父进程死掉(执行完毕),就会将自己的儿子进程全部清除
缺点:父进程一直不死的情况下
如果僵尸进程过多,会导致进程起不来,因为每一个僵尸进程都会拿着一个pid,每个新的子进程起来也会使用一个pid
2.孤儿进程:
没有父进程,也就是父进程执行完毕,子进程还没有执行完毕,那么这些孤儿进程都会被inid(孤儿进程的孤儿院) 进行接管

4.进程之间的内存隔离?证明

from multiprocessing import Process
n = 100
def work():
global n # 修改全局变量
n = 0 # 将n赋值为0
print('子进程:', n) # 打印 0
if __name__ == '__main__':
p = Process(target=work)
p.start()
p.join()
print('主进程:', n) # 打印100
主进程: 100
子进程: 0
# 证明两个进程之间是相互隔离的
当执行p.start() 告诉操作系统创建一个当前执行py文件的进程的一个子进程,将全部参数复制到创建的子进程中
子进程执行work函数,将n赋值为0,但是主进程的值不受影响

5.多进程证明并发?证明

import time
from multiprocessing import Process
from datetime import datetime
def func1(name):
time.sleep(2)
print(name)
def func2(name):
time.sleep(3)
print(name)
def func3(name):
time.sleep(5)
print(name)
print(datetime.now()) # 2022-12-10 22:54:41.438845
if __name__ == '__main__':
p1 = Process(target=func1, args=('进程1',))
p2 = Process(target=func2, args=('进程2',))
p3 = Process(target=func3, args=('进程3',)) # 最后创建的
p1.start()
p2.start()
p3.start()
# 不使用join 等待子进程的执行完毕,让主进程直接执行完毕
print(datetime.now()) # 2022-12-10 22:54:36.353487
证明多个进程进行执行时,会出现伪并发的状态
主进程的执行完毕的时间
22:54:36.353487
等待时间最长的子进程完成时间
22:54:41.438845
之间相差5秒,说明进程之间存在并发的现象,而不是串行执行

6.守护进程

守护进程:
1.主进程执行完毕后,代码终止,不论子进程是否执行
2.守护进程内部的子进程不能在开启进程
作用:
防止出现孤儿进程和僵尸进程
import time
from multiprocessing import Process
def func(name):
print(name)
time.sleep(2)
if __name__ == '__main__':
p = Process(target=func, args=('进程1',))
p.daemon = True # 开启守护进程
p.start()
p.join() # 开启一个join,让主进程等待,等待子进程执行完毕,销毁
print('主')
开启守护进程 与 join,主进程等待子进程完成后,因为开启守护进程原因就会销毁当前的子进程

7.进程互斥锁(锁概念)

import time
from multiprocessing import Process, Lock
将并发改为串行
'''
进程
进程 0 1
进程 1 1
进程 2 1
进程 0 2
进程 1 2
进程 2 2
进程 0 3
进程 1 3
进程 2 3
出现了子进程谁拿到了共享资源,谁就会先打印
应该是 谁先拿到资源打印完毕后,下一个子进程才会拿到资源
from multiprocessing import Lock # 就试一把锁
1.创建锁,锁必须是唯一的,要不没有意义
2.加锁,当子进程带上锁,那么其他子进程只能等待
3.解锁,当子子进程解锁后,其他进子程进行抢锁,谁拿到谁就会执行公共资源
进程 0 1
进程 0 2
进程 0 3
进程 1 1
进程 1 2
进程 1 3
进程 2 1
进程 2 2
进程 2 3
牺牲了效率,保证了程序不会错乱
'''
def task(name, mutex):
mutex.acquire() # 加锁
print('进程', name, 1)
time.sleep(1)
print('进程', name, 2)
time.sleep(1)
print('进程', name, 3)
mutex.release() # 解锁
if __name__ == '__main__':
mutex = Lock() # 创建一把锁
for i in range(3):
p = Process(target=task, args=(i, mutex))
p.start()
join 与 互斥锁的区别:
join是将全部代码变为串行
而互斥锁,只是将需要对数据改变的部分变为串行,其他部分还是伪并行的状态(牺牲效率,保证的了数据的安全)

8.项目案例

抢车票案例

import time
from multiprocessing import Process, Lock
def func(name, num, m):
m.acquire() # 加锁
time.sleep(1)
file = open('1.txt', 'r', encoding='utf-8')
n = int(file.read())
file.close()
if n > 0:
print(name, '抢到了票', num, )
n -= num
file2 = open('1.txt', 'w', encoding='utf-8')
file2.write('%s' % (n))
print('还有%d票' % n)
else:
print('没票了')
m.release() # 解锁
if __name__ == '__main__':
m = Lock()
for i in range(10):
p = Process(target=func, args=(i, 2, m))
p.start()
'''
注意:进程的内存是隔离的,内部的值也是进程自己私有的
只有在创建子进程时的公共资源是共有的,数据库信息,文件信息
这个案例可以看到 互斥锁就是为了防止 子进程之间对公共资源的同时修改造成的资源混乱现象
锁:
当自己上厕所时,其他人只能等待厕所外面,只有上完厕所,其他人才能进去上厕所
'''

多进程socket

from multiprocessing import Process
from socket import *
def talk(conn):
data = conn.recv(1024)
print(data)
conn.close()
def servers(ip, port):
server = socket(AF_INET, SOCK_STREAM)
server.bind((ip, port))
server.listen(5)
while True:
conn, attr = server.accept()
# 来一个客户起一个服务
p = Process(target=talk, args=(conn,))
p.start()
server.close()
if __name__ == '__main__':
servers('127.0.0.1',8000)

9.进程的管道和对列 *****

对列使用:
from multiprocessing import Queue # 当前进程模块的对列
# 参数:设置对列大小(对列使用的内存的大小数据)
q = Queue(3) # 实例化对列对象
# 在对列中放数据,当对列满,就会出现阻塞现象,等待另个一个数据拿出来才会将数据取走
q.put('110')
q.put('120')
q.put('119')
# 判断对列是否满了
print(q.full()) # 返回True 或者False
# 查看对列是否清空了
print(q.empty()) # 返回True 或者False
# 取数据,如果对列空了,在次取的时候就会阻塞等待 添加到对列中
print(q.get())
print(q.get())
print(q.get())

对列的生产者消费者模型

import time
from multiprocessing import Process, Queue
def func(q):
for i in range(3):
res = '包子%s' % i
time.sleep(2)
print('生产者生产%s' % res)
# 将生产者的生产的产品放入对列中
q.put(res)
def show(q):
while True: # 如果数据取完后,就会卡到
res = q.get()
if not res: break
time.sleep(1)
# 从对列中取出来内容进行消费
print('消费者消费了%s' % res)
if __name__ == '__main__':
q = Queue()
# 使用多线程,将q对列传入
p = Process(target=func, args=(q,))
# 消费者
c = Process(target=show, args=(q,))
p.start()
c.start()
p.join() # 让主线程等待子线程执行完毕,也就是当前的生产者生产完毕后
q.put(None) # 主线程等待生产者子线程执行完毕后,会执行put none 就是当前对列已经结束了
print('主线程')
'''
生产者 子进程 与主进程 同时关闭
在主进程关闭的同时 put 一个 None到对列中
在消费者中 当取到None时,就会关闭当前对列
'''
import time
from multiprocessing import Process, JoinableQueue
def func(q):
for i in range(3):
res = '包子%s' % i
time.sleep(2)
print('生产者生产%s' % res)
# 将生产者的生产的产品放入对列中
q.put(res)
q.join() # 当生产者 生产完毕 就会停止
def show(q):
while True: # 如果数据取完后,就会卡到
res = q.get()
if not res: break
time.sleep(1)
# 从对列中取出来内容进行消费
print('消费者消费了%s' % res)
q.task_done() # 给生产者发送信号,我已经取完了,可以停止了
if __name__ == '__main__':
q = JoinableQueue()
# 使用多线程,将q对列传入
p = Process(target=func, args=(q,))
# 消费者
c = Process(target=show, args=(q,))
c.daemon = True # 开启守护进程,当主进程执行完毕,子进程也同时销毁,
p.start()
c.start()
p.join() # 等待生产者 子进程执行完毕,主进程才会执行
print('主线程') # 当主进程执行完毕后,消费者子进程也会停止,因为开启了守卫进程
'''
等待生产完毕后,对列就消失停止
当消费者进行取时,会发送信号,如果取没了那么就会阻塞停止
内部发送了 我已经消费完毕的信号
生产者生产完毕, 消费者也就消费完毕
利用守护进程的概念进行停止对列
同时利用JoinableQueue 中的join 停止对列
'''

线程的理论

进程:将进程与进程之间进行隔离开来
那么进程也属于资源单位,每一个子进程之间都存在自己的资源,资源不能共享
真正工作的单位是 线程
每一个进程下面最少会出现一个线程的出现
在同一个进程中的线程是共享资源的(共享进程中的资源)

1.线程创建

线程的创建
from threading import Thread
# 方式1
def func():
print(123)
if __name__ == '__main__':
t1 = Thread(target=func)
t1.start() # 告诉操作系统在我的进程中在造一个线程
print('')
当前存在多少个进程和线程
当启动py文件是,会生成一个 ide的子进程执行py文件
那么 子进程下面 必定有一个工作的 线程
同时又开启一个线程执行func任务
那么就是 1进程 2线程
# 方式2
class Myprocss(Thread):
def __init__(self,name):
super(Myprocss, self).__init__()
self.name = name
def run(self):
print(self.name)
if __name__ == '__main__':
t1 = Myprocss('wkx') # 调用子进程
t1.start() # start给操作系统一个信号,开启一个进程 start默认自动进程执行run方法
print('主')

2.进程与线程的区别

1.开进程的开销远大于线程的开销
2.进程内容的多个线程 共享进程的地址空间
3.关于pid内容
# 1. 进程比线程开销大
from threading import Thread
from multiprocessing import Process
def func():
print('123456')
if __name__ == '__main__':
# t = Thread(target=func)
p = Process(target=func)
# t.start() # 向操作系统发信号,我要创建一个子线程
p.start() # 向操作系统发出信号,我要创建一个子进程
print('主')
线程打印顺序:
123456
进程打印顺序:
123456
线程的执行等同于 共同执行
进程的执行 先打印主,主进程先执行完毕,子进程可能正在申请创建索引 最后才会打印123456
线程创建,不需要在次开辟内存空间,使用当前的进程内的内存空间就可以
进程的创建,需要开辟进程空间,进程与进程之间是隔离的,所以需要单独申请内存空间
所以使用进程的开销比较大
# 2.同一个进程 内的子线程资源是共享的
n = 1000
def func():
global n
n = 0
print(n)
if __name__ == '__main__':
t = Thread(target=func)
# p = Process(target=func)
t.start() # 向操作系统发信号,我要创建一个子线程
# p.start()
print(n)
1.当使用进程执行时
1.给操作系统发出信号,在当前进程中创建一个子进程,将父进程的参数复制一份
那么 n =1000 就被子进程复制了一份
2.在外部的打印是当前主进程打印的内容 n=1000
3.在函数体内部 是创建的子进程去执行的,子进程复制了一份n=1000到自己的进程空间内部了
global n n=0 时,也就将自己进程空间的n=1000 变为了 n=0
进程与进程是隔离存在
2.当使用线程时
1.给操作系统发信号,在当前进程内部(存在一个主线程)创建一个子线程
2.这个子线程存在于当前进程的内存空间内部,所以获得的资源和主线程是相同的
3.当func内部进行对n=0 进行修改时就会修改进程内部的资源 n=1000
同一进程内部的线程 共享进程内的全部资源
****** 进程的id号 *******
import os
def func():
# 查看当前进程的id号
print(current_process().pid) # 14404
# 查看当前父级进程id
print(os.getppid()) # 8220
if __name__ == '__main__':
p = Process(target=func)
p.start() # 创建一个子进程
# 获取当前进程id
print('主进程id',current_process().pid) # 8220
3.线程的归属的进程id号 证明进程下的线程资源可以共享
# 可以证明,当前创建的新线程与原有线程都属于当前进程的内存空间中的,资源可以共享
def func():
# 查看当线程的进程id号
print(os.getpid()) # 8220
if __name__ == '__main__':
t = Thread(target=func)
t.start() # 创建一个子线程
# 获取当前线程的进程id
print('主线程id',os.getpid()) # 8220

3.线程的其他方法

from threading import Thread, currentThread,active_count,enumerate
def func():
print('6666')
print('当前线程的名字', currentThread().getName())
if __name__ == '__main__':
1.修改线程名
t = Thread(target=func, name='线程名') # name 属性设置线程名
t.setName('8899') # 修改创建线程的名字
t.start()
2.主线程等待子线程执行完毕后子线程销毁
t.join()
3.查看当前创建的线程是否存活
print(t.is_alive()) # 返回false ture
4.查看当前进程下活跃的线程
# 1.当启动py文件时,会生成创建一个进程,进程内部工作单位是线程(1个)
# 2.当创建一个线程,也是在当前进程内存空间内,那么线程就是2
print(active_count()) # 2
5.查看活跃线程对象
# [<_MainThread(MainThread, started 15356)>, <Thread(线程名, started 13116)>]
print(enumerate())
6.查看主线程名字(当前进程下的非创建的自带的一个工作最小单位线程)修改主线程名字
print('主线程', currentThread().getName()) # 查看名字MainThread
currentThread().setName('666') # 修改主线程名字

4.守护的概念

1.一个进程在什么时候应该被销毁掉
干完活后就让进程销毁
2. 一个进程内部存在一个干活的主线线程
而这个主线程的存在代表了 这个进程是在干活的
主线程当执行完毕后,会等待其他创建的线程执行完毕
如果主线程执行完毕直接停止,那么就代表当前进程执行完毕停止
3.在一个进程内,默认不开线程,那么就是一个主线程在工作
4.主线程 代表了 当前进程的生命周期
守护的概念:
进程:
当父进程 执行 到创建一个子进程时,子进程开启了守护进程
也就代表着当前子进程 会守护当前 父进程,当父进程执行完毕,子进程无论是否执行完毕都会停止
可以理解为 子进程 ----依附---> 父进程
内部代码怎么执行父进程不管,但是只要父进程执行完毕,子进程无论是否在干什么都要关闭
也就是 子进程 盯着 父进程 父进程死了 子进程也自杀死掉
线程:
一个进程内在初始的情况下就存在一个主线程
当创建多个线程时,其中一个线程为守护线程
那么 守护线程 盯着 主线程(主线程死 守护线程 死)
但是 主线程 代表了 当前进程的生命请求周期
所以 主线程 需要等待其他 非守护线程 执行完毕后才会死

5.守护线程

from threading import Thread
import time
def func():
time.sleep(2)
print('888999')
if __name__ == '__main__':
t = Thread(target=func)
t.setDaemon(True) # 守护线程
# t.daemon = True # 守护线程
t.start() # 线程创建的速度超级快
print(123) # 只会打印123 而创建的线程执行func 内部都不会执行
# 守护线程 主线程si 守护线程si

6.线程互斥锁

牺牲了效率 保护的数据安全
执行原理:将并发改为串行
from threading import Thread,Lock
import time
n = 100
def func(r):
global n
r.acquire() # 创建锁
temp = n
time.sleep(0.1) # 全部创建的线程都在这里停止 拿到了n=100
n = temp -1 # 每个线程都相当于 100 -1 最后就是99
r.release() # 释放锁
if __name__ == '__main__':
t_list = []
r = Lock() # 创建一个锁 效率低了 因为时一个一个执行的
for i in range(15):
t = Thread(target=func,args=(r,))
t_list.append(t)
t.start() # 线程创建的速度快
for i in t_list:
i.join()
print('主',n) # 99
# 锁只对 修改数据时使用,其他的代码部分不需进行加锁

7.线程中Event的事件

from thrading import Event
even = Event()
even.wait() # 开始 可以设置超时时间,wait(1) 不需要等待其他线程的set()执行,1秒后,这个线程就可以执行下面的任务
even.set() # 结束 那么其他线程内部设置wait() 线程就可以向下执行了
even.is_set() # 是否被设置了 event 返回bool
even.clear() # 恢复event的状态为False
作用:
一个线程通知另一个线程,我的活干完了,你可以你自己的任务了
实现线程之间互通
from threading import Thread, Event
even = Event() # 设置一个even的事件
def s1():
'''模拟学生'''
print('学生正在上课')
even.wait() # 处于阻塞状态
print('学生下课休息')
def s2():
'''模拟老师'''
print('老师正在上课')
import time
time.sleep(5)
even.set() # 释放even的事件
print('老师说下课')
if __name__ == '__main__':
t = Thread(target=s1) # 学生A
t2 = Thread(target=s2) # 老师
t.start()
t2.start()
*******
线程的关键特性,就是当前线程是独立的状态运行且不可以预测的,如果程序中的其他线程需要通过某一个线程的状态来确定自己下一步的操作,就可以使用envnt事件
不使用envet事件也是可以的,设置全局变量,其他线程根据这个变量进行是否执行下一步操作

8.线程中的定时器功能

在定时的时间进行执行某些任务
from threading import Timer
def task(name):
print(name)
# 参数1:间隔的时间秒为单位 参数2 函数 参数3 参数
# 内代码 继承了线程类,开启一个timer就是起一个线程,同时内部还有envnt事件控制者这时间的间隔
Timer(5, task, args=('11111',)).start()
*********** 60秒设置验证码 **********
# from threading import Lock,RLock
#
# # mutex = Lock() # 互斥锁
# # # 互斥锁只能 加锁1次,不能加锁多次
# # mutex.acquire()
# # print('加个锁')
# # mutex.acquire() # 阻塞到这个位置,因为互斥锁不能进行acquire两次
# # print('在加个锁')
# # mutex.release()
#
# # 递归锁,是可以加多个的
# # 递归锁,可以连续的acquire,每一次acquire一次计数器 +1
# # 只有计数器为0时,才会被其他的线程抢到
# r = RLock()
# print('加锁')
# r.acquire()
# print('在加个锁')
# r.acquire()
# r.release()
# from threading import Thread,Semaphore, currentThread
# import time
#
# sm = Semaphore(5) # 信号量,定义量为5,那么只有5个线程可以同时抢到
#
#
# def task():
# sm.acquire() # 加锁
#
# print('我是谁%s' % currentThread().getName())
# time.sleep(1)
# sm.release() # 解锁
#
#
# for i in range(10):
# Thread(target=task).start()
# from threading import Thread, Event
#
# even = Event() # 设置一个even的事件
#
#
# def s1():
# '''模拟学生'''
# print('学生正在上课')
# even.wait() # 处于阻塞状态
# print('学生下课休息')
#
#
# def s2():
# '''模拟老师'''
# print('老师正在上课')
# import time
# time.sleep(5)
# even.set() # 释放even的事件
#
# print('老师说下课')
# if __name__ == '__main__':
# t = Thread(target=s1) # 学生A
#
# t2 = Thread(target=s2) # 老师
#
# t.start()
# t2.start()
#
# from threading import Timer
#
#
# def task(name):
# print(name)
#
#
# # 参数1:间隔的时间秒为单位 参数2 函数 参数3 参数
# # 内代码 继承了线程类,开启一个timer就是起一个线程,同时内部还有envnt事件控制者这时间的间隔
# Timer(5, task, args=('11111',)).start()
import random
from threading import Timer
class Code:
def __init__(self):
'''在初始化的时候,就会执行一次验证码,先获取'''
self.make_code()
# 获取验证码:
def make_cache(self, interval=60):
# 1.初始获取一个验证码
self.cache = self.make_code()
print(self.cache)
# 2.定期进行修改,默认每隔60秒就会进行换一个验证码
# 内部的一个循环调用,所以会出现一个阻塞的状态
self.t = Timer(interval, self.make_cache)
self.t.start() # 开启定时器
# self.t.cancel() # 关闭定时器
# 生成验证操作
def make_code(self, n=4):
res = ''
for i in range(n):
s1 = str(random.randint(0, 9))
s2 = chr(random.randint(64, 90)) # 取随机的吗
res += random.choice([s1, s2])
return res
Code().make_cache(5)
循环调用函数:
每隔1秒打印123
from threading import Timer
def func():
print(123)
Timer(1, func).start()
func()

9.线程队列q

线程内部没有对列的方法,而进程中自带的有Queue
所以需要使用queue 进行对列的操作
import queue
******* 对列 *******
q = queue.Queue(3) # 对列最大3个 先进先出
# 对列加入3个值
q.put(11)
q.put(11)
q.put(11)
# 当对列满的时候
q.put(12,block=False) # 就会抛出异常
q.put(12,block=Ture) # 就会出现阻塞的状态
q.put(12,block=False,timeout=3) # 对列满了,阻塞timeout等待3秒后抛异常
# 对列取值
q.get()
# 当取值的时候
q.get(block=False) 如果对列中没有值,那么就会抛出异常
q.get(block=False,timeout=3) 对列中没有值,就会抛出异常 等待3秒后
********** 堆栈 *******
q = queue.LifoQueue(3) # 后进先出
# 对列加入3个值
q.put(11)
q.put(11)
q.put(11)
# 对列取值
q.get()
优先级的堆栈,可以设置优先级,级别高的先出来
q = queue.PriorityQueue
# 参数1 优先级 参数2 存的值
q.put((10,'666'))
q.put((10,'wkx'))
# 数字越少优先级越高
q.get()

10.多线程套接字socket

import socket
from threading import Thread
def commit(conn):
while True:
try:
data = conn.recv(1024)
if not data: break
conn.send(data.upper())
except ConnectionError:
break
conn.close()
def servers():
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('127.0.0.1', 8000))
server.listen(5)
while True:
# 建立成功链接后,立马起一个线程,执行获取内容
# 每一次来一个链接链接服务器,就会起一个线程(相当于一个服务员服务)
# 只要来新的就会存在一个新的服务员进行服务
conn, addr = server.accept()
t = Thread(target=commit,args=(conn,))
t.start()
server.close()
if __name__ == '__main__':
servers()

GIL全局锁的概念

当执行1.py文件,内部的流程
1.在python解释器的进程空间内将python解释器的cpython代码加载到内存空间中
2.再将1.py内部的代码加载到python解释器内存空间中
3.当执行时,会在当前python解释器进程 创建一个子进程执行1.py文件
4.1.py 是怎么执行的,需要将1.py中代码 交给cpython解释器中让进行执行(解释性语言)
原理:
GIL本身就是保证,在同一时间内,只有一个线程执行cpython解释器的代码
就是互斥锁,将并行的线程,变为一个串行的线程,效率低了,但是数据安全了
只有cpython解释器来说,存在GIL锁的概念
在一个进程中多个线程执行时,在GIL锁 只有一个线程去执行没办法使用多核优势
在什么条件下没有多核优势:
在同一个进程下起多个线程才会出现没有多核优势的情况
# 想要使用多核优势:
那么就是用多进程,每个进程只有一个主线程的情况,那么就没有其他线程和他抢GIL锁了
在cpython解释器中,想要使用多核优势,那么就是需要开多进程
GIL锁时存在cpython解释器中,而python中的代码将传入到cpython解释器中
所以每次执行,都会先从cpython解释器中获取GIL锁
cpython解释器(python代码字符串):
pass
补充:
垃圾回收线程 是cpython解释器中定时的开启使用,定时的关闭的,而垃圾回收线程是调用cpython代码进行对没有用的 到而占用的内存进行回收
# 所以说cpython的 多线程就是假的多线程,想要使用多核优势就需要使用多进程
解释器的代码是所有线程共享的,所有垃圾回收线程可能访问到解释器代码而去执行
问题:
对于同一个数据100,可能线程1执行 x=100,而垃圾回收机制需要执行回收x=100的操作
而解决并没有那么高明的操作,就是加锁处理,那个线程获取到了GIL,那么就会执行代码

GIL与自定互斥锁的区别

GIL锁保护的是和cpython解释器有关的,保护的垃圾回收线程的安全
GIL锁不是保护当前自己程序的数据,而是保护cpython中的数据安全
自定互斥锁 帮助的自己编写的数据安全
锁:目的就是要保护数据的安全,同一个时间只能存在一个线程来修改共享的数据,所以保护不同的数据需要加不同的锁
所以GIL与自定义的互斥锁,保护的数据不同,GIL锁保护的是cpython解释器级别的数据(垃圾回收的数据),自定义的锁保护的是自己开发的数据,明显GIL不负责用户数据的安全
进程中存在两个线程
线程1 线程2
同时执行func函数 对 n=0 进行+1操作
线程1 执行 先抢到cpython解释器中的GIL锁,将自己的代码丢给cpython解释器,当在执行到自定义的锁时,就会加上自己的锁,当出现io操作阻塞时,cup不会等待阻塞状态,会记录当前执行的位置,并且强行释放GIL锁。
这个时候线程2就会抢到GIL锁执行到 锁的位置,发现线程1,没有释放锁,线程2就会阻塞,会被操作系统强行的将cpu拿走,释放GIL锁
线程1重新拿到GIL锁,执行完毕内部的数据,自定义释放锁
线程2重新拿到GIL锁,执行内部代码,获取自定义锁,执行代码,到达阻塞,就会被操作系统拿走cup 执行其他的线程任务.....
结论:
1.对于计算 cup越多越好,但是对于io来说,再多的cpu也没有用
2.对于运行一个程序来说,随着cup核数增多,那么执行效率就会大大提高
在一个cup核数的情况下
现在写的软件都是 io密集型的,使用多线程进行处理(io 和网络打交道)
如果是金融类的产品,那就是计算过多,那么就是计算密集型(需要多核优势多线程)

计算密集与io密集型

import os
import time
from multiprocessing import Process
from threading import Thread
# def work():
# '''计算密集型使用多进程,可以使用多核优势,速度快
# 进程计算: 9秒
# 线程计算: 31秒
# '''
# res = 0
# for i in range(100000000):
# res *= i
def work():
'''
io密集型,也就是一直属于阻塞的状态,cup就会进行遇到阻塞io进行切换执行
速度比较快,另外多线程,不需要让操作系统申请内存空间,创建的速度也比较块
所以 io密集型还是比较适合使用多线程
'''
time.sleep(2)
if __name__ == '__main__':
l = []
state_time = time.time() # 开始时间
for i in range(400):
p = Process(target=work) # 10秒执行完毕
# t = Thread(target=work) # 2 秒执行完毕
l.append(p)
#l.append(t)
p.start()
#t.start()
for i in l:
i.join()
end_time = time.time() # 结束时间
print(end_time - state_time)
'''
如果遇到了计算密集型
使用多核优势,开多进程进行计算,速度比较快
遇到io密集型 使用多线程
cup当遇到io操作不会等待,而是进行切换到其他任务上去,
真正的时间是花在任务的切换,可以将单核发挥到性能最大化
'''

死锁与递归锁与信号量

锁A
锁B
线程1拿着 A锁 没有释放
线程2拿着 B锁 没有释放
现在线程1执行到需要使用到B锁
线程2执行到需要A锁
这种情况就属于死锁的概念
from threading import Lock,RLock
# mutex = Lock() # 互斥锁
# # 互斥锁只能 加锁1次,不能加锁多次
# mutex.acquire()
# print('加个锁')
# mutex.acquire() # 阻塞到这个位置,因为互斥锁不能进行acquire两次
# print('在加个锁')
# mutex.release()
# 递归锁,是可以加多个的
# 递归锁,可以连续的acquire,每一次acquire一次计数器 +1
# 只有计数器为0时,才会被其他的线程抢到
r = RLock()
print('加锁')
r.acquire()
print('在加个锁')
r.acquire()
r.release()
# 互斥锁:保证只能在同一时间内只有一个人去运行
# 递归锁:同时也是这么保证的,但是不同的是,每一把锁。都存在一个计时器,每一抢到锁,这个计时器就会+1,只有当前线程使用的锁,计数器为0,就会释放这把锁,那么其他的线程就会抢到
********** 信号量 **************
信号量也是一把锁,可以指定信号量为5,对比与互斥锁同一时间执行一个线程使用,信号量同一时间可以有5线程去拿这个把锁
比如:
互斥锁:如多个人在一个出租屋人,但是厕所只有一个,需要等待里面的人上完,其他的人才能取
信号量:相当于一群路人取公共厕所,公共厕所坑位多,这就一位这同一个时间可以有多个人上公共厕所,但是厕所的容纳是有限的,这就是信号量的大小
# 信号量,可以同时定义多个线程,同时拥有这把锁
from threading import Thread,Semaphore, currentThread
import time
sm = Semaphore(5) # 信号量,定义量为5,那么只有5个线程可以同时抢到
def task():
sm.acquire() # 加锁
print('我是谁%s' % currentThread().getName())
time.sleep(1)
sm.release() # 解锁
for i in range(10):
Thread(target=task).start()

进程池与线程池

可以将线程和进程控制到一定的数量中
******* 线程池与进程池的使用*********
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import os,time
from threading import currentThread
'''
ProcessPoolExecutor # 进程池
ThreadPoolExecutor # 线程池
'''
def func(name):
print(name,os.getpid())
time.sleep(1.5)
if __name__ == '__main__':
# 开进程池
# 属于一个异步提交,不管这个任务是否起来没有,提交完毕就走,不管是返回值
p = ProcessPoolExecutor(4) # 指定进程的多少,4个进程 默认是cup的核数
for i in range(10):
# 参数1函数名 参数2 函数内用的参数
p.submit(func,i)
# shutdown 作用就是让主等待进程池执行完毕后,在进行执行下面的代码 和join的意思是一样的
# 将进程池入口关掉,不让新的任务进行提交
p.shutdown()
print('主')
def func(name):
print(currentThread().getName())
print(name,os.getpid())
time.sleep(1.5)
if __name__ == '__main__':
# 开线程池
# 属于一个异步提交,不管这个任务是否起来没有,提交完毕就走,不管是返回值
p = ThreadPoolExecutor(4) # 开线程池 这个池子就4个
for i in range(10):
# 参数1函数名 参数2 函数内用的参数
p.submit(func,i)
# 传入多个参数
p.submit(函数名,参数1,参数2)
# shutdown 作用就是让主等待线程池执行完毕后,在进行执行下面的代码 和join的意思是一样的
# 将线程池入口关掉,不让新的任务进行提交
p.shutdown()
print('主')
线程池与进程池获取 调用函数返回值
# result() 方法可以获取调用函数的返回值
from concurrent.futures import ThreadPoolExecutor
def func():
print(1)
return 666
if __name__ == '__main__':
t = ThreadPoolExecutor(5)
m = t.submit(func).result()
print(m) # 获取返回值 666
******** 线程池与进程池的异步调用和回调机制 ************
# 提交任务的两种方式
# 1. 同步调用:提交完毕任务后,就在原地等待任务执行完毕,拿到结果,才会执行下面的内容,导致程序时串行执行
# 2. 异步调用:提交任务后,不等待任务执行完毕
import random
from concurrent.futures import ThreadPoolExecutor
def func():
print('开始执行')
return random.randint(1, 10),666
def show(val):
# val 不是一个结果,而是一个对象
# 需要使用result() 对象的返回值获取
print(val.result()) # <Future at 0x22ed9bf5940 state=finished returned int>
if __name__ == '__main__':
t = ThreadPoolExecutor(5)
# 在提交每个任务的时候执行完毕,触发的内容被成为回调函数
'''
执行流程:
add_done_callback(show) :回调函数
当t.submit(func) 执行完毕后 return 就会触发这个回调函数
不会将return的结果返回给回调函数中,而是将t.submit(func)对象当为一个参数给回调函数中
所以回调函数需要接受参数
如果t.submit(func) 返回的值时多个那么类型就会变为一个tuple
<Future at 0x202c6fe5970 state=finished returned tuple>
如果t.submit(func) 返回一个值,那么就会根据这个值的类型不同进行显示
<Future at 0x202c6fe5970 state=finished returned 根据类型不同>
'''
t.submit(func).add_done_callback(show)
'''
阻塞:
就是遇到io情况,被操作系统收回cup的使用权限
非阻塞
就是没有遇到io情况或者执行时间过长
'''

1.基于线程池socket

import socket
from concurrent.futures import ThreadPoolExecutor
def commit(conn):
while True:
try:
data = conn.recv(1024)
if not data: break
conn.send(data.upper())
except ConnectionError:
break
conn.close()
def servers():
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('127.0.0.1', 8000))
server.listen(5)
while True:
# 建立成功链接后,立马起一个线程,执行获取内容
# 每一次来一个链接链接服务器,就会起一个线程(相当于一个服务员服务)
# 只要来新的就会存在一个新的服务员进行服务,线程池内就5个线程,如果链接数量大于5,那么就会有客户端进行等待
conn, addr = server.accept()
t = t.submit(commit,conn)
server.close()
if __name__ == '__main__':
t = ThreadPoolExecutor(5) # 线程池一共5个线程
servers()

2.进程池

#进程池设置
from concurrent.futures import ProcessPoolExecutor #导入进程池
def func():
pass
return 123
def doo(return):
pass
if __name__ == "__main__": #进程
p = ProcessPoolExecutor(4)
# 创建一个进程池,最多4个进程
for i in range(10):
# 设置回调效果
特殊对象 = pool.submit(func,参数1....) #执行进程池
特殊对象.add_done_callback(doo)
#调用的函数动作 是由主进程进行函数动作
# 设置回调效果
1.回调函数是由主进程完成的执行函数foo。与线程池不同(由子线程执行回调函数)
当执行func函数时,将返回时封装到特殊对象中。
特殊对象调用.add_done_callback(doo) 回调方法并执行doo函数,将参数传入doo函数中。
2.特殊对象 会接受 func函数中 返回值
在特殊对象调用.add_done_callback(函数名)方法时,会将接受的返回传入 回调函数中
回调函数会接受return的返回值
# 设置回调效果

3.线程池

1.线程池方法:
from concurrent.futures import ThreadPoolExecutor # 导入线程池
pool = ThreadPoolExecutor(100) # 创建100个线程
pool.submit(函数名,传入函数参数1,参数2.....)
# 告诉线程,让线程执行函数,线程池会分配一个线程去指向函数
2.线程池案例:
#从上到下的执行代码 是主线程执行的
from concurrent.futures import ThreadPoolExecutor # 导入线程池
pool = ThreadPoolExecutor(10) # 创建10个线程
def func(): #需要执行的函数
pass
if i in range(100)
# 在线程池提交一个任务,当线程执行完毕后回到线程池,等待线程池的分配,如果没有线程,就会等待
pool.submit(func)
print('123') #主线程执行到这里停止 等待线程池执行完毕
线程池的执行流程:
循环100执行函数func,线程池只有10个,当线程执行完毕后回到线程池等待线程池安排任务。
3.线程池的方法
1.等待线程池执行完毕 主线程才会执行
pool.shutdown(True)
#主线程执行到这里停止 等待线程池执行完毕 和join方法一样
2.回调方法
1.创建一个线程池对象
特殊对象 = pool.submit(func,参数1) # 让线程池执行函数
2.利用线程池对象调用.add_done_callback(函数名)方法
特殊对象.add_done_callback(函数名) # 通过特殊对像调用方法
# 当线程池执行完毕后,执行.add_done_callback(函数名) 回调方法内的函数
回调方法案例:
#从上到下的执行代码 是主线程执行的
from concurrent.futures import ThreadPoolExecutor # 导入线程池
pool = ThreadPoolExecutor(10) # 创建10个线程
def func(): #需要执行的函数
pass
def show():
pass
if i in range(100)
# 在线程池提交一个任务,当线程执行完毕后回到线程池,等待线程池的分配,如果没有线程,就会等待
p = pool.submit(func) # 如果func函数又返回值,p对象就会携带返回值调用回调方法
p.add_done_callback(show) # 将返回值传入到show函数中当成参数
print('123') #主线程执行到这里停止 等待线程池执行完毕
回调函数的执行流程:
线程池执行完毕后,如果函数有返回值,就会将返回值封装到特殊对象中。特殊对象调用方法add_done_callback(函数名),会将封装的数据传入回调的函数中当成参数
当线程池任务完成后,可以根据 线程池返回的特殊对象,调用add_done_callback(函数名)方法,在执行其他的 函数方法

协程

单线程实现并发:就是协程操作
单线程并发:切换与保存状态
两种形式:
1.操作系统遇到io阻塞状态
2.时间过长
单线程下的 io阻塞行为,如果遇到io阻塞,那么就会进行切换
协程的优点;
1. 协程的切换开销小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量
2. 单线程内可以实现并发的效率,最大限度的利用cup(让操作系统以为你是一个io少的程序,拿到cup的使用权限)
缺点:
1.协程的本质还是一个单线程,无法利用多核优势,可以是一个程序开启多个进程,每个进程多个线程,每个线程内开启协程
2.协程本质就是单线程,一单协程进入阻塞,将会阻塞整个线程
特点:
必须在只有一个线程实现并发
修改共享数据不需要加锁
用户程序自己保存多个控制流的上下文栈

1.greenlet模块

from greenlet import greenlet
def eat(name):
print('1 %s' %name)
g2.switch('wkx')
print('2 %s' %name)
g2.switch('wkx')
def play(name):
print('1 %s' %name)
g1.switch()
print('2 %s' %name)
g1 = greenlet(eat)
g2 = greenlet(play)
g1.switch('wkx')
'''
1 wkx
1 wkx
2 wkx
2 wkx
可以函数之间的切换,但是无法进行监控io操作
'''

2.gevent模块

这个模块就是属于单线遇到io操作进行切换到其他的非io任务,也就是单线程并发效果
对greenlet 模块的封装可以监控到io操作
import gevent
from gevent import monkey
import time
monkey.patch_all()
def eat(name):
print('1 %s' % name)
time.sleep(3)
print('2 %s' % name)
def play(name):
print('1 %s' % name)
time.sleep(5)
print('2 %s' % name)
# 提交任务
# 属于异步提交,不会等待程序是否执行完毕
g1 = gevent.spawn(eat, 'wkx')
g2 = gevent.spawn(play, 'clll')
# 主等待任务执行
g1.join()
g2.join()
# 无法识别其他的io操作,只能识别自己模拟的io操作gevent.sleep(1)等待操作
# 模块下存在一个monkey.patch_all() 会将程序下面的全部io打上标记
# 这样就算不是gevent模块的io操作也会被检测到,实现切换操作
******* gevent模块的提交任务 *******
import gevent
from gevent import monkey;monkey.patch_all() # 打补丁 将当前程序下的全部io操作进行标记,让gevent模块能识别,如果不打monkey补丁的话,那么就只会识别gevent模块自己的io操作
import time
def eat(name):
print('1 %s' % name)
time.sleep(3)
print('2 %s' % name)
def play(name):
print('1 %s' % name)
time.sleep(4)
print('2 %s' % name)
# 提交任务
# 属于异步提交,不会等待程序是否执行完毕
g1 = gevent.spawn(eat, 'wkx')
g2 = gevent.spawn(play, 'clll')
# 线程等待,gevent异步提交的任务执行完毕后,才会死掉
g1.join()
g2.join()
# 传入参数 [任务1对象,任务2对象]
gevent.joinall([g1,g2]) # 等待全部的gevent的提交的任务执行完毕后,才会让线程死掉
计算密集型使用的意义不大,只有在遇到io操作,让操作系统将cup的使用权,给到下一个任务进行操作,在遇到io操作在进行切换,这样的话就属于并发执行。
例如:
A 执行io 6
B 执行io 3
C 执行io 2
那么协程gevent 先执行 A程序遇到io(记录当前程序的执行位置),切换执行B程序(记录当前程序的执行位置),切换执行C程序遇到io(记录当前程序执行位置),来回切换ABC正常程序执行下拉,就是一共也就是 6 秒时间 ,因为遇到io等待时间时,操作系统将cup的使用权限给到其他程序去执行,也就是利用A程序io操作的时间内执行完毕了BC程序。大大节约了时间

gevent模块实现单线程并发soket服务

import socket
from gevent import monkey, spawn
monkey.patch_all() # 打补丁,将全部的io操作打上标签让gevent模块可以使用
def commit(conn):
while True:
try:
data = conn.recv(1024)
if not data: break
conn.send(data.upper())
except ConnectionError:
break
conn.close()
def servers():
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('127.0.0.1', 8000))
server.listen(5)
while True:
conn, addr = server.accept()
spawn(commit, conn) # 进行提交gevent 协程 异步任务
server.close()
if __name__ == '__main__':
g = spawn(servers) # 进行提交gevent 协程 异步任务
g.join() # 必须让线程等待 gevent内部的任务,因为gevent任务属于异步触发,提交任务,不等待任务完成,所需需要线程等待任务进行执行

IO模型

同步操作:会等待执行完毕的结果,在执行下一步
异步操作: 提交完毕后,不会等待结果的产生,而是执行下一步的操作
阻塞,非阻塞
同步不等于阻塞
遇到io就是阻塞
套接字:
accept() send() recv() 属于阻塞行为,等待收发消息
例如:
recv()接受 差不多 accept()等待
1.等待缓存数据(客户端发给服务端的数据,会通过网卡,网络到服务端网卡,在到缓存中)
2.操作系统拷贝服务端内的缓存中(客户端发送的数据)
send() 也属于io 但是为什么体会不到他的等待时间,因为当进行send()时,数据就丢给了操作系统,剩下的操作都是由操作系统来执行(数据量少) 当数据量大时还是等体会到等待的时间的

1.阻塞IO模型

单线程子节套
只有到
sever.accept() # 出现阻塞 等来链接
一直处于wait data 读取是否有新的链接来
如果没有那么操作系统就会收回cup的使用权限给其他的非io状态的程序
那么当前的服务端程序时没有占cup使用,那么效率非常低
sever.accept() wait data copy data的两个状态
当运行客户端时:
那么就会被度读取到,sever.accept()变为非阻塞状态
阻塞到conn.recv(1024)那么当前属于等待客户端发送数据的阶段,服务端就会询问操作系统是否有新数据来。conn.recv(1024) 属于 wait data copy data
阻塞io 没有并发效果:
因为当遇到阻塞,操作系统就会将cup拿走
实现并发:
开多线程,让主线程(当前进程中的工作线程,执行accept()等待链接操作),没来一个链接 起一个线程进行处理新的链接,缺点:随着客户端链接增多,那么线程就会越来越多,会出现机器抗住不,
开线程池 固定当前线程的多少,问题规模大,线程池也会降低效率
# 最开始学习soket模块就是一个单线程阻塞io模型,当前线程等待客户端链接,处于io状态,recv 等待客户端数据,io状态

2.非阻塞IO模型

在单线程条件下,监控io操作进行动态切换,提升效率
wait data 与copy data(使用的时间短,从操作系统的缓存区拷贝到用户的内存中) 的操作
有应用程序发送系统调用,收一条消息 发给操作系统内核
如果没有数据,马上获取一个结果,error,知道数据没有准备好
在下次发送系统调用的时间段内,进行做其他的# 事情 #
直到问操作系统,有数据,就会操作系统的缓冲区拷贝到用户的内存中
非阻塞io,也就是在wait data中 在等待数据来的时候,这段时间内,去干其他的事情,等到数据真正来了,再去执行copy data 等一系列操作
******* 非阻塞io *********
from socket import *
server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8000))
server.listen(5)
# 默认为Teur 是一个阻塞的io 模型 设置为Fasle那么就是一个非阻塞
# 下面的代码全部就变为了非阻塞的状态
server.setblocking(False)
print('state')
rlist = []
wlist = []
while True:
try:
conn, attr = server.accept() # 阻塞
rlist.append(conn) # 将链接对象添加到列表中
print(rlist)
except BlockingIOError:
# 只负责收消息
del_rlist = []
for conn in rlist: # 循环链接对象
try:
# 这里也属于io阻塞,使用非io阻塞模型,会出现错误,使用try步骤
data = conn.recv(1024) # 阻塞
if not data: # 如果收到的消息为空
del_rlist.append(conn) # 添加到删除列表中
continue # 跳过开始下一次循环
# 将发送的消息添加到列表中
wlist.append((conn,data.upper()))
except BlockingIOError: # 出现错误说明链接没有新内容,跳过开始下个循环,看看下个是否有新消息
continue
except Exception:
# 出现链接断开的情况
conn.close()
del_rlist.append(conn)
# 发消息
del_wlist = []
for item in wlist:
try:
conn = item[0]
data = item[1]
conn.send(data)
del_wlis.append(item)
except BlockingIOError:
pass
# 删除已发送消息列表
for item in del_wlist:
wlist.remove(item)
# 将断开链接的客户端,从循环添加的rlist 中进行删除
for conn in del_rlist:
rlist.remove(conn)
server.close()
# 这样的话可以实现并发,出现io切换到执行其他的活中
# 不建议使用,会造成cup被当前程序占用,但是没有功的占用,造成cup,使用率高,其他程序没办法使用的状态
缺点:
1.如果在io阻塞,线程去干其他的活时,数据来了,没办法及时响应
2.当前程序属于一个及时状态随时准备的状态,那么操作系统 就会将cup尽量了给当前程序使用,但是当前处于死循环的状态,处于大量的询问工作,无用工作,cup的使用率高的,导致cup的吞吐量降低

3.多路复用io模型

可以同时检测多可套接字 效率高
如果检测1个,那么不如阻塞io的效率
当用户进程调用select ,那么整个进程就会被block(阻塞),相当于select 就如同中介一般进行在中间对操作系统进行询问,等待操作系统回消息。同时 kernel(计算机核心)会监控所有的select负责的soket(多个)
当任何一个soket 数据准备好了,那么select就会返回,这个时候进程就会调用read操作,将数据从 kernel(计算机核心)拷贝到用户进程的内存中
与阻塞io相比
就是多了一个步,中介 select 检测套接字io行为
******* 通过select去问操作sockt是否有数据或者链接 *******
import select
from socket import *
server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8001))
server.listen(5)
# 默认为Teur 是一个阻塞的io 模型 设置为Fasle那么就是一个非阻塞
# 下面的代码全部就变为了非阻塞的状态
server.setblocking(False)
rlist = [server] # 读消息的套接字,就是当前的服务器链接
wlist = [] # 写消息的套接字
wdata = {}
while True:
# 这个方法就是帮我们问操作系统,代理管理的 socket 数据是否准备好了
# 参数1 收的套接字列表 参数2 发套接字列表 参数3 出异常的列表 参数4 询问操作系统的间隔时间
rl, wl, xl = select.select(rlist, wlist, [], 0.5) # 询问成功后会出现一个返回值 收套接字列表,发套接字列, 出异常的套接字列表
print(rl, wl, xl)
for sock in rl: # 循环收套接字列表
if sock == server: # 如果与server 列表原内容收套接字相同,那么就是一个建立新链接的操作
conn, attr = sock.accept()
rlist.append(conn) # 将新建的链接 conn 添加到 收套接字列表中,进行监控
else:
try:
data = sock.recv(1024)
wlist.append(sock) # 添加到发消息的套接字列表中 进行监控
wdata[sock] = data.upper() # 将发消息的对象 k v 结构存放到字典中
except Exception:
sock.close()
rlist.remove(sock) # 从
# 传
for sock in wl:
data = wdata[sock] # 从消息字典中根据对象获取 val
sock.send(data) # 将消息发出去
wl.remove(sock) # 将对象从监控 传套接字中列表中剔除
wdata.pop(sock) # 在将发的数据从发消息字典中剔除
server.close()
# 这样的话可以实现并发,出现io切换到执行其他的活中
缺点:
select 会随着,监听的sokcet的增多,而造成效率越来越低
因为操作系统对select 监听的名单列表,也是进行循环查看的(遍历方式)

4.异步io

效率最高:当进行对操作系统进行询问的时候,会直接返回。下面的全部wait data 与copy data 都是操作系统进行做的事情,等有了数据,那么操作系统就主动将数据传递
用户进程发起 read操作 立即返回执行其他的事情,而另一方面 kernel的角度 当他受到一个异步io之后 会立即返回,所以不会对用户的进程产生任何的阻塞操作,然后 kernel会等待数据准备完成后,将数据拷贝到当前用户的进程内存中,当这一切完成后,kernel会给用户就进程发送信号signal,告诉他read操作完了

Python设计模式

设计模式

设计模式中使用了一个接口类
abc:Abstract Base Classes
作用:在代码中定义和使用抽象基类进行API检查。
为什么使用abc模块
Abstract base classes由一组接口组成,检查比hasattr()更严格。通过定义一个抽象基类,可以为一组子类定义一个通用的API。这对于第三方为应用提供插件等非常有用,另外当您在一个大型的团队中工作或在一个大型的代码库中,同时将所有的类放在您的头脑中是困难或不可能的时,它也可以帮助您。
abc模块的工作机制:
1.利用abc模块设置一个抽象类,并且设置一个类继承这个类,而设置的类必须按照抽象类的方法定制方法
例如定义一个抽象类
impout abc # 导入abc类
# 使用abc类创建一个抽象的类
class A(abc.ABCMeta):
@abc.abstractmethod
def load(self,input):
pass
# 创建一个子类,继承抽象类,继承后必须按照抽象类的方法的形式进行编写(参数和名字,内部的逻辑自行编写)
class B(A):
def load(self,input):
pass
对软件设计中普遍存在的各种问题,所提出来的方案,每一个设计模式的命名,解释评价面向对象系统中一个重要的和重复的设计
面向对象的三大特征:
封装 继承 多态
接口是什么:若干抽象方法的集合
作用:限制现实的接口的类必须按照给定的调用方法实现这些方法,对高层模块隐藏了类的内部实现
设计模式:是一种反复被使用,经过分类编目,代码设计经验的总结,使用设计模式可以重用代码,让代码更容易被人理解,保证代码的可靠性,程序的重用性

面向对象设计的solid原则(在创建类或者设计模式时必须遵守的)

1.开方原则:
一个软件实体如类,模块和函数应该对扩展开放,对修改进行关闭,软件应在不修改原有代码的情况下进行扩展
尽量不要改代码
2.里氏替换原则:
所有引用父类的地方必须透明的使用子类的对象
传入 usre 不会报错
传入 vipusre 不会报错
class User
def show_name(self):
pass
class VIPUser(User):
def show_name(self):
pass
# 保证两个返回值,参数都是一样的,内部逻辑可以不同
3.依赖倒置原则:
高层模块不应该依赖底层模块,都要依赖抽象,抽象不应该依赖细节,细节一栏抽象,
针对接口进行变成,而不是针对实现编程
先定义接口,按照接口进行编程
4.接口隔离原则
使用多个专门的接口,而不使用单一的总接口,客户端不应该依赖哪些它不需要的接口
在创建接口时,不能直接创建一个大的接口类,而是按照功能需求取创建
不需要依赖哪些不需要使用的接口,将接口进行细化,那个类需要,那个类继承
例如:
创建一个陆地动物类接口
创建一个飞禽动物类接口
创建一个水螅动物类接口
青蛙:可以继承落地动物类接口和水螅动物类接口
老虎:只能继承陆地动物类接口
老鹰:只能继承飞禽动物类接口
4.单一职责原则:不要存在多余一个导致类变更的原因,通俗的说,一个类只能负责一项职责
面向对象和设计模式:都要依赖solid原则

设计模式分类

创建型模式

创建型模式概念:
处理对象创建的设计模式,试图根据实际情况使用合适的方式创建对象。基本的对象创建方式可能会导致设计上的问题,或增加设计的复杂度。创建型模式通过以某种方式控制对象的创建来解决问题
创建型模式由两个主导思想构成:
一是将系统使用的具体类封装起来,二是隐藏这些具体类的实例创建和结合的方式

简单工厂模式(先使用)

在创建对象时使用的模式
1.简单工厂模式
内容:不直接向客户端暴露对象创建的实现细节,而是通过一个工厂类来负责类的实例
工厂角色:工厂类
抽象产品:接口类
具体产品:就是根据接口集体实现的功能
优点:
隐藏对像创建的实现细节
客户端不需要修改代码
缺点:
违反单一职责原则,将创建的逻辑都放入工厂类中
添加新产品时需要修改工厂类代码,违反开闭原则
代码案例:
import abc # 抽象基类方法
# 属于一个接口类,或者抽象类,使用这个类为父类的子类,必须使用接口类中的方法和参数
class Payment(metaclass=abc.ABCMeta):
@abc.abstractmethod
def pay(self, money):
pass
# 继承抽象类
class Alipay(Payment): # 支付宝类
def __init__(self, use_huabai=False):
# 定义一个花呗的支付方式
self.use_huabai = use_huabai
def pay(self, money):
if self.use_huabai:
print("花呗支付{}元".format(money))
else:
print("支付宝支付{}元".format(money))
# 继承抽象类
class Weixinpay(Payment): # 抽象类
def pay(self, money):
print("微信支付{}元".format(money))
# 工厂类:主要负责处理实现功能的类的逻辑
class Factory:
def factory_pay(self, types):
if types == 'alipay':
return Alipay()
elif types == 'weixinpay':
return Weixinpay()
elif types == 'huabai':
return Alipay(use_huabai=True)
else:
raise Exception('输入有误')
f = Factory() # 实例化工厂类
a = f.factory_pay('huabai')
a.pay(100)
'''
当进行继承创建的抽象基类时,
继承的子类必须按照抽象基类中的方法进行创建,参数方法名要一直,内部的逻辑根据情况而定
使用工厂设置模式,逻辑判断上面使用的就是工厂类暴露出来的接口方法,而内部的高层代码(真正的逻辑没有暴露)
'''

工厂方法模式(先使用)

# 就是将工厂模式的类中设置一个接口,将内部的全部代码拆分
概念:定义一个工厂的接口类,让子类决定实例化那个产品
角色:
抽象工厂角色
具体工厂角色
抽象产品角色
具体产品角色
工厂也有接口,产品也有接口
优点:
每个具体产品都对应一个具体的工厂类,不需要修改工厂代码
隐藏了对象创建的实现细节
缺点:
没具体增加一个产品角色,就要增加一个工厂角色,代码比较多
实例代码:
import abc
# 属于一个接口类,或者抽象类,使用这个类为父类的子类,必须使用接口类中的方法和参数
class Payment(metaclass=abc.ABCMeta):
@abc.abstractmethod
def pay(self, money):
pass
# 继承抽象类
class Alipay(Payment): # 支付宝类
def __init__(self, use_huabai=False):
# 定义一个花呗的支付方式
self.use_huabai = use_huabai
def pay(self, money):
if self.use_huabai:
print("花呗支付{}元".format(money))
else:
print("支付宝支付{}元".format(money))
# 继承抽象类
class Weixinpay(Payment): # 微信支付类
def pay(self, money):
print("微信支付{}元".format(money))
# 创建一个工厂抽象类 接口
class Factory(metaclass=abc.ABCMeta):
@abc.abstractmethod
def to_pay(self):
pass
# 创建一个工厂角色
# 支付宝支付 角色
class Alipay_Factory(Factory):
def to_pay(self):
return Alipay()
# 支付宝花呗支付工厂角色
class Alipay_Huabai_Factory(Factory):
def to_pay(self):
return Alipay(use_huabai=True)
# 微信支付 角色
class Weixinpay_Factory(Factory):
def to_pay(self):
return Weixinpay()
w = Weixinpay_Factory()
f = w.to_pay()
f.pay(100)
'''
为工厂创建一个抽象类接口,然后对创建产品角色,创建一个工厂角色
不会产生代码都存在一个工厂中,出现代码过多,当新创建产品角色时,只需要创建一个工厂角色就可以
'''

抽象工厂模式(场景复杂,后使用)

概念:定义一个工厂类接口,让工厂子类创建一系列相关或者相互依赖的对象
给一套东西作为一个限制,进行限制方法。不是生成一个对象,而是生成一套对象,可以对对象加入限制
相对于工厂模式方法,抽象工厂模式中的每一个具体的工厂都生成一套产品
例如:
生产一个手机:需要手机壳,cpu,操作系统,每一个类对象都不同的种类,对每个具体工厂,分贝生成一部手机
所需要的三个对象
使用:太过复杂,局限性较大
角色:
抽象工厂角色
具体工厂角色
抽象产品角色
具体产品角色
客户端
优点:
将客户端与类的具体实现相分类的
每一个工厂创建一个具体的产品类型,十的易于交换产品系列
有利于产品的一致性
缺点:
难以支持新种类
代码案例:
import abc
# 产品抽象类
# 手机cpu
class Product_CPU(metaclass=abc.ABCMeta):
@abc.abstractmethod
def cpu(self):
pass
# 手机操作系统
class Product_OS(metaclass=abc.ABCMeta):
@abc.abstractmethod
def os(self):
pass
# 手机壳
class Product_Shell(metaclass=abc.ABCMeta):
@abc.abstractmethod
def shell(self):
pass
# 工厂抽象类 负责将手机窜在一起
class Factory(metaclass=abc.ABCMeta):
@abc.abstractmethod
def factory_cpu(self):
pass
@abc.abstractmethod
def factory_os(self):
pass
@abc.abstractmethod
def factory_shell(self):
pass
# 创建产品角色 cpu 晓龙/联发科/m1
class A_CPU(Product_CPU):
def cpu(self):
print("晓龙cpu")
class B_CPU(Product_CPU):
def cpu(self):
print("联发科cpu")
class C_CPU(Product_CPU):
def cpu(self):
print("m1cpu")
# 创建产品角色 系统 安卓/os
class A_OS(Product_OS):
def os(self):
print("安卓系统")
class B_OS(Product_OS):
def os(self):
print("os系统")
# 创建手机壳/ 大小
class Big_Shell(Product_Shell):
def shell(self):
print("大手机壳")
class Small_Shell(Product_Shell):
def shell(self):
print("小手机壳")
# 创建工厂角色
# 创建苹果手机
class Apple(Factory):
def factory_cpu(self):
return C_CPU()
def factory_os(self):
return B_OS()
def factory_shell(self):
return Small_Shell()
# 创建小米手机
class Millet(Factory):
def factory_cpu(self):
return B_CPU()
def factory_os(self):
return A_OS()
def factory_shell(self):
return Small_Shell()
# 创建客户端
class Client:
def __init__(self, cpu, os, shell):
# 传过来的是工厂角色的对象
self.cpu = cpu # 接受的就是 Millet().factory_xxx()中return返回的类对象B_OS()....
self.os = os
self.shell = shell
def phone(self):
# 调用产品中的方法
print('手机组成部分')
self.cpu.cpu() # 在根据返回的类对象,调用他内部的方法cpu打印对应的内容
self.os.os()
self.shell.shell()
def make_phon(factory):
# factory 工厂对象
cpu = factory.factory_cpu() # 类名调用对应的方法,的返回的类对象就是B_OS()....
os = factory.factory_os()
shell = factory.factory_shell()
return Client(cpu,os,shell) # Millet().factory_cpu()的实例对象,传入到客户端中
p = make_phon(Millet()) # 将手机组成的工厂类当对象传入make_phon方法中
p.phone()
'''
抽象工厂方法,使用的太过复杂
make_phon函数接受了一个角色工厂的类名(),根据类名调用类内部的3个方法:
1.factory_cpu()
2.factory_os()
3.factory_shell()
而这三个方法同时返回对应的产品角色的类对象
1.A_OS()
2.A_shell()
3.A_cpu()
在make_phon函数中有同时调用了客户端类,将这三个产品角色传入到客户端类的init方法中
获取了一个客户端类的实例化对象p
p同时调用了客户端类中的phone方法
而phone方法中:
调用了角色工厂中的方法进行打印信息
'''

建造者模式(场景复杂,后使用)

概念:将一个浮在对选哪个的构建与它的表示分离,使同样的构建过程可以创建不同的表示
# 与抽象工厂模式有点像
# 控制组装顺序
角色:
抽象创造者:接口
具体创造者:实现的抽象建造者的子类
指挥者
产品
重点:
它与抽象工厂相似,也用来创建复杂的对象,主要区别是建造者模式是一步步构造成为一个浮在的对象,而抽象工厂模式重于多个系列产品对象
优点:隐藏一个产品的内部结构和装配过程(用户不需要知道装配过程)
将构造的代码与表示代码分开
可以对构造过程有更细化的控制
代码:
import abc
# 导演类
class Player:
def __init__(self, face=None, body=None, arm=None, leg=None):
'''
人物建模类
:param face:头
:param body: 身体
:param arm: 胳膊
:param leg: 腿
'''
self.face = face
self.body = body
self.arm = arm
self.leg = leg
# __str__自动返回实例变量的全部的内容
def __str__(self):
return "{},{},{},{}".format(self.face, self.body, self.arm, self.leg)
# 创建构造工厂/创建抽象创造者
class PlayerBuilder(metaclass=abc.ABCMeta):
@abc.abstractmethod
def build_face(self):
pass
@abc.abstractmethod
def build_body(self):
pass
@abc.abstractmethod
def build_arm(self):
pass
@abc.abstractmethod
def build_leg(self):
pass
# 创建具体工厂角色/创建具体的创造者
class SexyGirlBuilder(PlayerBuilder):
def __init__(self, player):
self.player = player() # 在初始化时,对人物建模类实例化对象
def build_face(self):
# 给任务模型进行赋值
self.player.face = '漂亮脸蛋'
def build_body(self):
self.player.body = '修长的身体'
def build_arm(self):
self.player.arm = '纤细的胳膊'
def build_leg(self):
self.player.leg = '大长的腿'
# 控制者
class PlayerDirector: # 控制组长顺序
# 将具体的创造者对象传入进去
def duild_palyer(self, builder):
builder.build_body()
builder.build_face()
builder.build_arm()
builder.build_leg()
return builder.player # 将创建的建模类对象进行返回
# 实例化创建者对象,将建模类传入,因为创造者类中对建模类中的一些实例变量进行了赋值
s = SexyGirlBuilder(Player)
# 实例化控制着,这个类主要的作用就是控制创造者的组装顺序
d = PlayerDirector()
# 根据组长类中的方法进行传入,将创造者类的实例化对象传入,执行创造者类中的方法,进行赋值
m = d.duild_palyer(s) # 返回建模类的实例对象,建模类中__str__方法就会自动执行
print(m) #
'''
创造者模式分为4个部分
1.导演类:
导演类起到封装的作用,避免高层模块深入到建造者内部的实现类。在建造者模式比较庞大时,导演类可以有多个。
2.抽象的创造者
主要作用:负责将需要创建的方法进行声明
3.具体实现的创造者
主要作用:实现抽象创造者类中的全部方法
4.客户端/控制类
指挥具体实现创造者对象创建 产品
'''

单例模式1

单例模式:
保证一个类只有一个实例,并提供一个访问他的全局访问点
角色:单利
优点:
对唯一实例的受控访问
单例相当于一个全局变量,防止命名空间的污染
python的模块就是一个单例模式
单利模式的书写:
class Singleton:
instance = None
# 用来分配空间,是给整个类初始化
def __new__(cls, *args, **kwargs):
if cls.instance: # 判断是不是None
return cls.instance # 不是None就将原对象返回
else:
cls.instance = object.__new__(cls) # 如果是None,就创建一个空对象
return cls.instance #在将空对象返回
单例模式的求证案例:
class Singleton:
instance = None
# 用来分配空间,是给整个类初始化
def __new__(cls, *args, **kwargs):
if cls.instance: # 判断是不是None
return cls.instance # 不是None就将原对象返回
else:
cls.instance = object.__new__(cls) # 如果是None,就创建一个空对象
return cls.instance # 在将空对象返回
class A(Singleton):
def __init__(self, a):
self.a = a
a = A(10)
b = A(20)
print(a.a)
print(b.a)
'''
执行流程:
单例模式,当对A类进行实例化对象时,就会判断instance存不存在
如果instance不存在,就会执行else中的代码:
cls.instance = object.__new__(cls),创建一个对象,将只返回
当创建第二个实例对象b时,就会进行判断 if cls.instance,存在值还将原值进行返回。
单例模式就是一直使用的是一个,实例化对象。确保一个类只有一个实例对象
Python new()方法,为对象分配内存,返回对象的引用
利用id()方法判断是不是用的一个实例化对象
'''
应用场景:日志对象,数据库链接器,文件系统,只有一个实例存在就用单利模式

单例模式2

单例模式:就是当对类进行实例化是,可以使用一个内存地址
正常情况:每一次实例化,对象的内存地址都不一样。
单例模式的方式:
import threading #导入线程模块
class 类名(object):
lock = threading.RLock() # 创建一把锁
instance = None # 设置类变量
def __new__(cls,*args,**kwargs):
# 在实例化对象之前先执行new方法 创建一个空对象
# 返回一个空对象
# 直接读取,而不会 申请锁 在进行判断读取,效率提升
if cls.instance:
# 判断instance是不是空
return cls.instance
# 不是空返回 instance
with cls.lock:
# 遇到 多线程 使用单例模式 创建一把锁,不会出现内存地址不同
if cls.instance:
# 判断instance是不是空
return cls.instance
# 不是空返回 instance
cls.instance = object.__new__(cls)
# 如果是空 创建一个空对像
return cls.instance
# 创建完毕将创建的空对象返回
基于模块导入的单例模式:
1.第一个py文件
class 类名(object):
def __init__(self):
pass
实例对象a1 = 类名() # 实例化对象
2.第二个py文件
from xxx import 实例对象a1 # 导入第一个py文件的实例化对象
实例对象a1.方法
当进行导入时,实例化对象a1就会存储在内存中,无论导入多少次,都是从内存中进行调用。实现模块级单例模式

创建模式总结

抽象工厂模式和创建工厂模式相对于简单工厂模式和工厂方法而言更为简单和灵活也更为复杂
通常情况下,设计简单工厂模式或者工厂方法模式开始,当发现设计需要更为灵活性时,则向更浮在的设计模式演化。

结构型模式

统一将几个对象几个类统一的整合一起使用

适配器模式

结构型模式:几个类组成一个什么结构
适配器模式:原本由于接口不兼容而不能一起工作的哪些类可以一起工作
实现方式:
1.类适配器:使用多继承/利用继承原理
2.对象适配器:使用组合/利用多态
适配器的目的:保持接口一直,复用之前的源代码
# 类的组合使用:
是将A类的对象,放入b类中
class A# A类
pass
class B# b类
def ——init——(self,a):
self.a = a() # 放入A类的对象
def dunc(self):
self.a.xxx() # 就可以进行调用A类中的方法,如果存在多个类b,d,e,都可以传入B类中的inita参数中进行调用。
角色:
1.目标接口
2.待适配器类
3.适配器
使用场景:
1.类适配器:如果一个类已经存在,但是接口不符合要就,就可以使用
2.对象适配器:如果存在多个类,但是多个类接口不符合要求,就可以使用
1.类适配器代码(适用于少量待适配器类)
import abc
# 接口类,规定支付接口按照这种模式取设置
class Payment(metaclass=abc.ABCMeta): # 目标接口
@abc.abstractmethod
def pay(self, money):
pass
# 继承抽象类
class Weixinpay(Payment): # 抽象类/按照接口设置的类
def pay(self, money):
print("微信支付{}元".format(money))
# 待适配器类:与目标接口设置不符合,使用类适配器
class BankPay:
def cost(self, money):
print("银联支付{}".format(money))
# 类适配器
class NweBankPay(Payment, BankPay):
def pay(self, money):
self.cost(money)
# 调用时:调用适配器中的方法就可以
b = BankPay() # 调用适配
b.pay(100)
# 类适配器的原理:
利用了继承的特性,子类没有找父类的规则。
适配类中:
1.继承接口类,设置好规定的接口
2.继承待适配器类,适配器需要利用self调用待适配器类中的方法,进行使用
2.对象适配器/其他待适配器对象中的方法是一致的(适用于大量待适配器类)
import abc
# 接口类,规定支付接口按照这种模式取设置
class Payment(metaclass=abc.ABCMeta): # 目标接口
@abc.abstractmethod
def pay(self, money):
pass
# 继承抽象类
class Weixinpay(Payment): # 抽象类/按照接口设置的类
def pay(self, money):
print("微信支付{}元".format(money))
# 待适配器类:与目标接口设置不符合,使用类适配器
class BankPay:
def cost(self, money):
print("银联支付{}".format(money))
# 对象适配器
class PaymentAdapter(Payment): # 继承接口类
# 写一个__init__方法
def __init__(self,payment):
# 加括号接受类名/不加括号接受类对象
self.payment=payment() # 这个参数就是接受待适配器类的类名
# 接口类规定的写法
def pay(self, money):
self.payment.cost(money) # 代用待适配器类中的方法
# 调用
p = PaymentAdapter(BankPay) # 将待适配器类名传入
p.pay(100) #代用对象适配器中的方法

桥模式

将一个的事物的两个维度分开,使其能够独立分开
例如:一幅画,需要颜色和图案内容,这就是两个维度。
角色:
抽象
细化抽象
实现者
具体实现者
应用场景:
当事物有两个维度的表现,两个维度可以扩展时
优点:
抽象和实现相分离
优秀的扩展能力
桥模式和对象适配器模式几乎相同。
代码实例:
import abc
# 形状类 接口/抽象
class Shape(metaclass=abc.ABCMeta):
def __init__(self, color):
self.color = color # 初始化一个实例变量/而这个实例变量时颜色类接口
@abc.abstractmethod
def deaw(self):
pass
# 颜色类 接口/细化
class Color(metaclass=abc.ABCMeta):
@abc.abstractmethod
def paint(self, shape):
pass
# 实现的具体形状类/抽象实现者
class Rectangle(Shape):
name = '长方形'
# 长方形逻辑
def deaw(self): # 因为接口类有init方法实例变量,可以通过变量调用
self.color.paint(self) # color = Red()类对象.方法
# 当调用deaw方法时。在这就直接执行了颜色类中paint方法将自己传入中
# 实现颜色具体/细化实现者
class Red(Color):
def paint(self, shape):# shape参数接受的是抽象者的本身对象
print('红颜色{}'.format(shape.name)) # shape.name就是调用本身的类变量
r = Rectangle(Red()) # 传入的颜色对象
r.deaw()
'''
如实例:
利用形象类中的init方法中写入一个实例对象的参数,而这个实例对象参数就负责接受具体颜色实现的类。
逻辑方式:
1.设置不同的两个维度的接口类,在其中一个接口类中设置init方法
2.创建两个接口具体实现的类
3.实例化抽象这,将细化者对象当参数传入到抽象这中,在利用实例化对象调用抽象者的方法
'''
记忆:
图形类中,init /color变量 = 颜色类实例对象
颜色类实例对象中的方法:paint接受了一个参数,就是图像类本身
1.实例化图像类,将颜色类当对象传入
图像类的执行
图像类的方法:deaw,调用了父类color变量,
因为:color变量 = 颜色类实例对象,索引直接调用了颜色类的方法paint并且将图像类本身对象传入到颜色类paint方法中
2.在颜色类中paint方法
直接调用设置的变量shape,而这个变量本身就是图像类的对象,直接使用了内部的类变量
3.在实例化对象.deaw(),就打印了红色的长方形

组合模式(属性结构时)

概念:将对象组成树形结构,以表示‘部分-整体’的层次结构,组合模式,是的用户对单个对象和组合对象的使用具有统一性。
角色:
抽象组件
叶子组件
复合组件
客户端
使用场景:
1.表对象的部分-整体的层次结构(特别结构是递归)
2.希望用户忽略组件对象与单个对象的不同,用户统一的使用组件结构的所有对象
优点:
1.带那个一包含基本对象和组合对象的类的层次结构
2.简化了客户端代码,即客户端可以一致的使用组合对象和单个对象
3.更容易增加新类型组件
代码实例
import abc
# 设置一个接口
class Graphic(metaclass=abc.ABCMeta):
@abc.abstractmethod
def deaw(self):
pass
# 设置一个叶子叶子组件/底层的
class Point(Graphic): # 点类
def __init__(self, x, y):
self.x = x
self.y = y
def deaw(self):
# 简单图形
print(self)
# 打印对象,如果类中带有str方法,就会打印对应返回的值
# 如果没有str方法,就会打印对象本身
def __str__(self):
return "A:{}B:{}".format(self.x, self.y)
# 叶子组件的调用方式
p = Point(1,2)
p.deaw()
# 设置一个组合类/接受的是叶子类的对象。可以说处理叶子类的一个类方式/高级的
class Picture(Graphic):
def __init__(self, li):
self.children = [] # 列表
for i in li: # 支持列表对象
self.add(i)
def add(self, graphic):
self.children.append(graphic)
def deaw(self):
# 复杂图形
for g in self.children: # 递归
g.deaw()
# 组合组件掉用方式
p1 = Point(1, 2)
p2 = Point(1, 2)
p4 = Point(1, 2)
p5 = Point(1, 2)
n = Picture([p1,p2,p4,p5])
n.deaw()
组合模式:其实就是,先设置一个简单的叶子类,主要负责代码的呈现,而组合类就负责将叶子类对象进行内部调用。而叶子类与组合类同时使用的是同一个接口类
只不过组合类比叶子类更为高级。

外观模式(非常简单模式)

给一套系统按配件设置接口,在定义一个高级接口,负责处理系统的配件接口
定义:为子系统中的一组接口提供一个统一的界面,外观模式定义了一个高层的接口,这个即可使整个子系统更加容易使用
角色:
外观类
子系统类
目的:不让用户直接操作子系统类,封装一个高层的代码负责封装子系统类的方法
优点:
1.减少系统的相互依赖
2.提高灵活
3.提高安全性
代码实例
import abc
# 一个接口
class Exterior(metaclass=abc.ABCMeta):
@abc.abstractmethod
def start(self):
pass
@abc.abstractmethod
def finish(self):
pass
# 子系统
class CPU(Exterior):
def start(self):
print('开启cpu')
def finish(self):
print('关闭cpu')
# 子系统
class Hard_Disk(Exterior):
def start(self):
print("开启硬盘")
def finish(self):
print("关闭硬盘")
# 外观类
class Computer(Exterior):
# 在外观类中直接实例化 上面两个子类,不讲子类暴露在外部
def __init__(self):
self.cpu = CPU()
self.hard_disk = Hard_Disk()
def start(self):
self.cpu.start() # 调用子类对象的方法
self.hard_disk.start()
def finish(self):
self.cpu.finish()
self.hard_disk.finish()
# 使用外观类进行嗲用 子类的内容.
d = Computer()
d.finish()

代理模式

概念:为其他对象提供一种代理控制对这个对象的访问
应用场景
远程代理:为远程的对象提供对象
数据在远程服务器,利用一个类去获取远程服务器的数据
虚拟代理:根据需要创建很大的对象/可以较少内存的开销
根据需要创建一个很大的对象,例如浏览器无图模式,只有点击图片才能显示图片的详情
保护代理:控制对象原始对象的访问,用于对象有不同访问权限
角色:
抽象实体:接口目的:使实现的细节的类,具有统一的接口方法
实体:
代理类:
优势:
远程代理:可以隐藏对象位于远程地址的空间事实
虚代理:可以优化内存,例如:根据要求创建对象
保护代理:在访问对象时,有一些内务的处理
import abc
# 代理需要现有实例
# 创建接口
class Subject(metaclass=abc.ABCMeta):
@abc.abstractmethod
def get_content(self):
pass
@abc.abstractmethod
def set_content(self, content): # 真是的与代理的用时时相同的
pass
# 真是的对象,不加入代理/真实代理
class RealSubject(Subject):
def __init__(self, filename):
# filename 文件名称
self.filename = filename # 文件名称
f = open(filename, "r", encoding="utf-8")
self.content = f.read() # 读取文件
f.close() # 关闭文件
def get_content(self): # 阅读文件
return self.content # 直接读取文件
def set_content(self, content):
# 写入文件,content 是写入文件的内容
f = open(self.filename, "r", encoding="utf-8")
f.write(content)
f.close()
# 直接使用真是对象
r = RealSubject('xx.text') # 不执行类中的方法,但是一直占用着电脑内存
# 虚代理
# 根据需要调用对象,如果用户没有需要/就必要创建对象,占用内存
class VirualProxy(Subject):
def __init__(self, filename):
self.filename = filename # 文件对象/只是存储文件名字符串/没有真的读取
self.subj = None # 真是对象
# 读取
def get_content(self):
# 虚代理,如果调用这个方法后,先判断subj有没有值
# 没有值就实例化一个真正的对象,将文件名实例变量传入
if not self.subj:
self.subj = RealSubject(self.filename) # 这个时候,subj就是RealSubject的实例化对象
return self.subj.get_content() # 直接调用RealSubject类中的get_content方法/相当于这个虚代理就拥有了读取的方法
# 写入
def set_content(self, content):
if not self.subj: # 优先判断subj这个参数有没有实例化对象
self.subj = RealSubject(self.filename)
return self.subj.set_content(content)
# 保护代理/只能直接读文件/根据条件判断用户是否有权限写入
class PortectedProxy(Subject):
def __init__(self, filename):
self.subj = RealSubject(filename) # 实例化真是的代理类,将文件名传入
# 读
def get_content(self):
return self.subj.get_content() # 直接读取/没有过多的操作
# 写
def set_content(self, content):
# 根据需要的逻辑才能使用这个方法/用户的权限进行是否写入
raise PermissionError('没有权限')
虚代理:使用后,在没有调用当前方法时,没回占用内存
保护代理:用户只有读的权利,没有写的权利,需要根据用户是否存在权限

行为型模式

责任链模式

内容:
使用多个对象都有集合处理请求,从而避免请求的发送者和就守着之间的耦合关系
将对象连成一条链子,并且沿着链子进行传递请求,知道有一个对象处理他为止
角色:
抽象处理者
具体处理者
客户端
使用场景:
有多个对象可以处理一个请求,那个请求处理根据逻辑而定
在不明白接受者情况下时,向多个对象中的一个提交一个请求
优点:
降低耦合度:一个对象无序知道其他哪一个对象处理请求(高层代码不需要知道怎么处理,只需要传递第一个就可以)
就如公司请假一样
我 - 组长 - 经理 -总经理 -董事长
代码案例
import abc
class Abstract(metaclass=abc.ABCMeta):
@abc.abstractmethod
def fake(self, day):
pass
# 第3层
class Manager(Abstract):
def fake(self, day):
if day <= 10:
print("经理批假{}".format(day))
else:
print("经理不批")
# 第2层
class Group_leader(Abstract):
def __init__(self):
self.nuxe = Manager()
def fake(self, day):
if day <= 5:
print("组长批假{}".format(day))
else:
print("组长不批,找经理批")
self.nuxe.fake(day)
# 第1层
class Small_Group_leader(Abstract):
def __init__(self):
self.nuxe = Group_leader()
def fake(self, day):
if day <= 5:
print("小组长批假{}".format(day))
else:
print("小组长不批,找组长批")
self.nuxe.fake(day)
s = Small_Group_leader()
s.fake(1)
# 不需要知道传入的对象是谁处理的,只需要将对象传给第1层类就可以,一层一层执行,知道处理完毕

观察者模式(用的比较多/发布订阅模式)

用的比较多
内容:
定义对象键的一种一对多依赖关系,当对象的状态发生变化,所有依赖的对象都会被通知并且被更新。
观察者模式:发布订阅模式
角色:
抽象主题
具体主题 -- 发布者
抽象观察者
具体观察者 -- 订阅者
使用场景:
当抽象模型有两个方面,其中一个方面依赖另一个方面,将这两个封装在一个独立对象中,以可以独立改变和复用
当对一个对象的改变需要同时改变其他对象,而不知道具体改变多少
当一个对象需要通知其他对象,而他又不能嘉定其他对象是谁
有点:
耦合小
支持支持广播通信
实例代码
import abc
# 抽象类/抽象的订阅者
class Observer(metaclass=abc.ABCMeta):
@abc.abstractmethod
def update(self, notice):
pass
# 抽象的发布者
class Notice:
def __init__(self):
self.obserbers = []
def attach(self, obs): # 添加
self.obserbers.append(obs)
def detach(self, obs): # 删除
self.obserbers.reverse(obs)
def notify(self): # 执行订阅者中的方法/相当于一个推送方法
for obs in self.obserbers:
obs.update(self) # 将自己本身类传入到了订阅者实现类方法中
class StaffNotice(Notice): # 发布者实现
def __init__(self, company=None):
super().__init__() # 掉用父类的__init__方法,防止重用问题
self.company = company # 可以传可以不传,默认为none
@property
def company_info(self):
return self.__company_info # 设置一个私有属性
@company_info.setter # 负责写/使用了setter就对company_info进行了写入info参数接受被写入的值
def company_info(self, info):
# info 就是被写入的文本信息
self.__company_info = info # 进行赋值
self.notify() # 推送,观察者 只一个操作是重点
class Staff(Observer): # 订阅者
def __init__(self):
self.company_info = None
def update(self, notice):
# notice 就是Notice类中方法notify的对象self
self.company_info = notice.company_info # company_info方法
n = StaffNotice('公司123') # 发布者/公司123传入到init company变量中
s1 = Staff() # 订阅者
n.attach(s1) # 将订阅者添加到列表中
n.company_info = '加入' # 将 参数写入到__company_info中info中
print(s1.company_info)

策略者模式(算法)

内容
定义系列算法,把他们一个个封装起来,并且使用他们可以相互替换
模式可以使算法可以独立使它的客户而变化(根据客户变化而变化)
角色
抽象策略
具体策略
上下文
优点:
定义了一些列的可以重用的算法和行为
消除了一些条件语句
可以提供相同行为的不同实现
缺点
客户必须列表不同的策略
代码
import abc
# 抽象策略
class Stra(metaclass=abc.ABCMeta):
@abc.abstractmethod
def execute(self, data):
pass
# 实现策略
class Fast(Stra):
def execute(self, data):
print('较快{}'.format(data))
# 实现策略
class Slow(Stra):
def execute(self, data):
print('较慢{}'.format(data))
# 上下文类/将数据和策略放在一个类中/不需要用户知道的数据
class Context:
def __init__(self,strategy,data):
self.strategy =strategy
self.data =data
def set_strategy(self,strategy): # 切换策略
self.strategy = strategy
def do_strategy(self): # 执行策略
self.strategy.execute(self.data)
data ='....'
s1 = Fast() # 快的
s2 = Slow() # 满的
c = Context(s1,data)
c.do_strategy()
# 如果需要进行切换策略,直接调用set_strategy方法,值不会变化
c.set_strategy(s2)
c.do_strategy()

模板方法模式(算法)

定义一个操作中的算法骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变算法的结构课重新定义算法的某些步骤
角色:
抽象类:定义抽象类的原子操作(钩子操作),实现一个模板方法,作为算法骨架
具体类:实现钩子方法的操作
场景:
一次性实现一个算法部分不变的部分
各个子类中的公共行为应该别提取出来集中到一个公共的父类中,避免代码重复
控制子类扩展
代码
import abc
import time
class window(metaclass=abc.ABCMeta):
@abc.abstractmethod
def start(self): # 开始/原子操作/钩子操作/大逻辑不用重复的书写
pass
@abc.abstractmethod
def reqaint(self): # 刷新
pass
@abc.abstractmethod
def sopt(self): # 停止
pass
# 模板方法/具体方法
def run(self):
self.start()
while True:
try:
self.reqaint()
time.sleep(1)
except KeyboardInterrupt:
break
self.sopt()
class A(window):
def __init__(self, msg):
self.msg = msg
def start(self):
print('窗口开始运行')
def sopt(self):
print('窗口关闭')
def reqaint(self):
print(self.msg)
a = A('.....')
a.run() # 直接运行模板方法

设计模式总结总结

如果自己的代码需要被别人使用:开源框架,或者公司的项目时就需要使用设计模式,使代码看起来更为简洁,更具有逻辑性。

Python协程

协程概念

1.异步非阻塞,asyncio
2.异步框架: 提升性能
tomado fastapi django3.x asgi aiohttp

协程是什么

协程是不是计算机提供出来的,程序员自己创建的。
协程(coroutine) 被称为微线程,是一种用户动态上下文切换的技术,简而言之使用一个线程在代码中进行切换的过程
1.采用原来的同步指向(代码从上到下执行) 一共使用了3秒时间
def func1():
print(1)
time.sleep(1)
def func2():
print(2)
time.sleep(2)
print('开始秒数' + time.strftime('%S'))
func1()
func2()
print('开始秒数' + time.strftime('%S'))
2.实现线程的方式
1.greenlet 早期模块
2.yield 关键字控制
3.asyncio装饰器 py3.4
4.async await 关键字 py3.5 [推荐]

greenlet实现协程方式

1.pip install greenlet
2.案例
from greenlet import greenlet
def func1():
print('func1-第1次打印')
gr2.switch() # 2.切换到func2 进行执行[会进行记录当前这个函数执行的位置,如果切换回来就从当前开始执行]
print('func1-第2次打印')
gr2.switch() # 4.切换到func2 进行执行
def func2():
print('func2-第1次打印')
gr1.switch() # 3.切换到func1函数执行
print('func2-第2次打印')
# 注册到greenlet 对象
gr1 = greenlet(func1)
gr2 = greenlet(func2)
# print(gr2) # greenlet.greenlet object
gr1.switch() # 1.启动gr1对象的进行执行

yield关键字实现

def func1():
yield 1
yield from func2() # 跳到 执行func2生成器
yield 2
def func2():
yield 3
yield 4
f = func1()
for i in f: # 循环执行生成器
print(i)

asynico实现

实际执行时间2
import asyncio
import time
@asyncio.coroutine
def func1():
print(1)
yield from asyncio.sleep(1) # 模拟io请求
print(2)
@asyncio.coroutine
def func2():
print(3)
yield from asyncio.sleep(2)
print(4)
print('开始秒数' + time.strftime('%S'))
tasks = [asyncio.ensure_future(func1()), asyncio.ensure_future(func2())]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
print('结束秒数' + time.strftime('%S'))

async与await关键字实现

实际执行时间2
@asyncio.coroutine 替换为 async def
yield from 替换为 await
import asyncio
import time
# 通过关键字将普通函数包装为协程函数(coroutine func)
async def func1():
print(1)
# await:1.将asyncio.sleep(1)协程对象 包装为task任务 告知event loop 2.将控制权限还给event loop
await asyncio.sleep(1) # 模拟i/o操作
# 协程函数
async def func2():
print(2)
await asyncio.sleep(2)
async def main():
print('开始秒数' + time.strftime('%S'))
coroutine_list = [func1(), func2()] # coroutine func object list
await asyncio.gather(*coroutine_list)
print('结束秒数' + time.strftime('%S'))
if __name__ == '__main__':
asyncio.run(main()) # main 获取event loop权限执行mian task任务执行 (协程函数对象)
当前的优势:
遇到io操作,就会将线程进行切换执行其他的task进行执行,大大的节省了时间的损耗

协程的意义

在一个线程中如果遇到io等待时间,线程不会傻傻的等待,利用空闲的时间赶其他的事情,大大的提高了效率
1.实例代码:同步方式[排队执行] 使用3秒下载完成
import requests
def download_img(url,img_name):
res = requests.get(url)
with open(f'{img_name}.png', mode='wb', ) as file_obj:
file_obj.write(res.content)
print('本地下载完成' + f'{img_name}.png')
url_list = [
'url2',
'url1',
'url3'
]
print('开始下载时间' + time.strftime('%X'))
for index,url in enumerate(url_list):
print('url:' + url)
download_img(url,index)
print('结束下载' + time.strftime('%X'))
# 如果图片下载时间为1分钟,需要花费3分钟[第一张下载完毕后,才会执行下一次下载]
2.实例代码:协程方式[不等带结果执行下一个] 使用1秒将3张图片下载完成
pip install aiohttp # 需要使用这个模块
import time
import asyncio
import aiohttp
async def download_img(session, url, img_name):
async with session.get(url, verify_ssl=False) as response: # 发送io请求
content = await response.content.read()
with open(f'{img_name}.png', mode='wb', ) as file_obj:
file_obj.write(content)
print('本地下载完成' + f'{img_name}.png')
async def main():
print('开始秒数' + time.strftime('%S'))
async with aiohttp.ClientSession() as session:
url_list = [
'url2',
'url1',
'url3'
]
# 协程对象列表
task_list = [download_img(session, url, index) for index, url in enumerate(url_list)]
await asyncio.gather(*task_list) # 将协程对象列表批量添加到event loop 中作为task任务
print('结束秒数' + time.strftime('%S'))
if __name__ == '__main__':
# asyncio.run(main()) # 使用这种方式会出现RuntimeError: Event loop is closed 异常
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
# 使用协程asyncio模块需要配合着能使用协程方式的模块进行操作,不然不会有所作用

异步编程

事件循环'event loop'

事件循环: 称为'event loop'
作用: 可以理解为死循环,去检测并执行某写代码[检测任务列表中得任务]
理解代码:
任务列表 = [任务1,任务2,任务3]
while True:
可执行任务列表,已完成任务的任务列表 = 去任务列表中检测全部任务,将可执行和可完成的任务返回('相当于当前的任务列表中得任务存在多个状态,将每个状态进行分类进行循环,直到任务列表中任务完全没有为止')
for 就绪任务 in 已经准备就绪的任务列表
执行就绪的任务
for 已完成的任务 in 已完成的任务列表
'任务列表'中进行移除已完成的任务
如果任务列表中得任务全部完成,那么这个死循环就会终止
'''
简单的理解事件循环的操作(大致的流程):
任务列表 = ['正在io请求的任务','已经完成的任务','可以执行的任务']
那么事件循环 event loop 就会先将 '正在io请求的任务无视'
将已完成的任务从任务列表中剔除
执行可以执行的任务
如果可以执行的任务出现【io请求的操作】,那么就会【放任这个任务进行io操作】,并【标记】当前任务时【请求io的任务】,执行下一个任务。
如果【当前任务】已经完成,那么就会添加到【已完成任务列表】中,进行循环从【任务列表】中进行剔除
'''
代码:
import asyncio
# 当前 生成或者获取一个事件循环
loop = asyncio.get_enent_loop()
# 将任务放到任务列表中,让可以理解为让当前的任务可以被事件循环监听
loop.run_until_complete('存放任务')

协程函数与协程对象

1.协程函数 coroutine func
async def 函数名: # async关键字为前缀的函数被称为'协程函数'
pass
2.协程对象 coroutine object
协程函数() # 协程函数+()得到的就是协程对象,当前函数并不会执行
3.协程函数与普通定义函数区别
最大的区别就是,普通函数()当前函数就会进行执行,协程函数()只能获取协程对象[内部代码不会执行]

协程函数的使用(async关键字)

async def func()
print('我是协程函数')
asy = func() # 不会执行
print(asy) # 获取当前协程函数的对象[协程对象]
怎么才能让协程函数执行?
需要借助 asyncio 模块中得事件循环 event loop
import asyncio
async def func():
print('123456')
# 执行方式
1.python3.7之前的执行操作
# 事件循环
loop = asyncio.get_event_loop()
# 使用事件循环执行协程函数
loop.run_until_complete(func())
2.python3.7后执行的方式
asyncio.run(func())
# 关于asyncio.run
内部帮助我们创建获取事件循环,并让事件循环执行传入的函数,当前run()作为一个异步编程的入口进行操作
run 的内部还是完成了原来版本的操作 执行创建或者获取event loop 将任务执行
# 注意:
异步函数一定要不事件循环进行一起使用,没有事件循环异步函数是没办法执行的

协程函数的使用(await关键字)

# await 等待对象[需要等待对象执行完毕后才会执行下面的代码]
# await + 代表的可等待的对象(协程对象,future,task对象[这三个对象可以理解为io等待对象])
await最重要的最用: 会将协程对象包装为一个task任务,告知event loop。
import asyncio
案例1:
async def func():
print('123456') 1.先执行当前打印
# 模拟io等待
res = await asyncio.sleep(1) 2.await会将当前协程对象对象包装为task任务,添加到event loop中,并告知当前task任务需要等待(会记录当前执行的位置),将权限还给event loop,去执行其他不需要等待(等待完成的任务)的task任务
print(res) 3.获取io等待返回值,当前等待结束变为可执行任务(已经有了执行的结果),event loop在下次循环中 会将权限给func()函数,从当前记录位置开始执行(并接受返回值)
asyncio.run(func())
案例2:
async def show():
print('正在下载中...') 4.打印
await asyncio.sleep(2) 5.等待asyncio.sleep(2)协程方法
print('下载完成...') 5. 打印
return '文件.txt' 6. 返回值
async def func():
print('我要开始下载了') 2.打印
# 执行的协程函数的执行结果的返回值
res = await show() 3.包装携程对象 并进去show()进行执行 7.接受返回值
print(res) 8.打印返回值
asyncio.run(func()) # 1.执行func() 协程对象
案例3
import asyncio
async def show():
print('正在下载中...')
await asyncio.sleep(2)
print('下载完成...')
return '文件.txt'
async def func():
print('我要开始下载了')
res1 = await show()
print(res1)
res2 = await show() # 等待第一个await等待对象的值得到结果后才会执行
print(res2)
asyncio.run(func())
只有第一个await协程对象(将这对象包装为一个event loop中得一个任务)执行完毕后,并且获取执行结果后,才会执行下一个协程函数
原因: 因为第二个协程对象并未被await包装为任务,告知event loop。代码是从上而下的执行,并没有执行到第二个await

Task对象

task作用:
1.帮助我们在事件循环(event loop)中添加多个任务
例如:
任务列表 = [task1,task2,task3] # 添加多个任务到event loop 中
当某个任务出现io操作就会就会进行任务切换
2.tasks 用于并发的调度协程,通过asyncio.create_task(协程对象)[3.7上使用]的方式创建task对象,这样可以将协程对象添加到event loop(事件循环)。其他的方式(更低级) loop.create_task() 或者asyncio.evsur_future()[3.7下使用]不建议手动实例化task对象
# 重点:
import asyncio
import time
1.实例1通过create_task将协程对象包装为task任务[2个任务执行2秒]
# 使用的较少create_task创建任务 需要大量的await进行通知event loop调度
async def show(num):
print('show函数正在执行...%s'%num)
await asyncio.sleep(2) # 模拟io阻塞
print('show函数执行完毕...%s'%num)
return 'ok'
async def func():
print('执行func函数')
# 创建task对象,将当前执行show函数添加到事件循环中(event loop)
task1 = asyncio.create_task(show(1))
task2 = asyncio.create_task(show(2))
# 当执行到某个遇到io操作会自动化切换执行其他任务
# 此处的awati是等待响应的协程全都执行完毕后获取的结果
task1_return = await task1
task2_return = await task2
print(task1_return,task2_return)
print(time.strftime('%S'))
asyncio.run(func())
print(time.strftime('%S'))
执行过程说明:
1.现在event loop中存在3个任务: [func(),task1,task2]
2.执行过程模拟(未必详细,大概过程)
2.1 执行脚本时,先执行asyncio.run()执行func()对象,并将任务添加到event loop中
2.2 执行到asyncio.create_task() 添加任务到event loop中[将task1-task2添加],任务列表中已经存在了3个任务
2.3 await task1 就会执行task1任务内部代码,遇到 asyncio.sleep(2) task1任务阻塞,'开始任务切换'
2.4 切换到task2任务(为什么不会切换到func任务中,因为当前的任务是在func任务内,task1在阻塞,那func也在阻塞),遇到 asyncio.sleep(2) task2任务阻塞,'开始任务切换'
2.5.切换到task1任务执行完成 获取返回值
2.6.切换到task2任务执行完成 获取返回值
2.7.func()任务执行完成,整段程序执行完毕
2.实例代码2
# 使用的较多,节省await编写调度
asyncio.gather(*任务列表)批量调度 # 需要通过*解构列表
asyncio.wait(任务列表)批量调度 # 直接添加列表
async def show(num):
print('show函数正在执行...%s'%num)
await asyncio.sleep(2)
print('show函数执行完毕...%s'%num)
return 'ok'
async def func():
print('执行func函数')
# 执行的协程函数的执行结果的返回值
task_list = []
# 循环将任务呢添加到任务列表中task_list
for i in range(5):
task = asyncio.create_task(show(i))
task_list.append(task)
# 方式1:通过gather方法批量通知event loop进行调度 asyncio.gather
res = await asyncio.gather(*task_list)
print(res) # 接受任务执行返回的结果 [task1,task2] 存放结果为添加任务的顺序
# 方式2:直接将列表添加 asyncio.wait
done,pending = await asyncio.wait(task_list,timeout=1) # timeout=None
print(done) # 返回的结果的集合
print(pending) # 返回timeout超时未完成任务集合 与参数timeout(设置后,timeout=2,任务最大执行时间为2秒,超出存入当前集合中)
print(time.strftime('%S'))
asyncio.run(func())
print(time.strftime('%S'))
注意:
# 不是用函数调用方式添加任务需要一下写法
async def show(num):
print('show函数正在执行...%s'%num)
await asyncio.sleep(2)
print('show函数执行完毕...%s'%num)
return 'ok'
task_list = [show(1),show(2)]
'''
不能
task_list = [
asyncio.create_task(show(1)),
asyncio.create_task(show(2))
]
因为当前操作不直接将协程对象添加到事件循环中 event loop 但是当前还没有创建事件循环就会报错
async def func():
pass
asyncio.run(func()) 会先创建事件循环将func()对象包装为任务进行添加并且执行
'''
asyncio.run(asyncio.wait(task_list)) # 当前内部代码如果发现并事件循环就会进行创建

asyncio.Future对象

future是底层对象,是task类的基类
Task继承Future,task对象内部await结果的处理基于future对象的
import asyncio
实例1
# 在这种情况下创建future对象毫无意义因为没有用返回结果就处于等待
async def func():
# asyncio.get_event_loop() 获取当前执行 run 创建的事件循环对象
loop = asyncio.get_event_loop()
print(loop)
# 创建一个任务(future对象),这个任务什么都不干
fut = loop.create_future()
# 当前就会在等待任务(future对象) 等待结果出现,如果没有就一直等待
await fut
asyncio.run(func())
案例2
# 如果有返回值那么future对象就会执行完毕,如果没有就会阻塞等待返回值
async def func1(fut):
# 4.执行任务
await asyncio.sleep(2)
# 5.给future对象进行设置值
fut.set_result('666')
async def func2():
# 1.获取当前事件循环
loop = asyncio.get_event_loop()
# 2.创建一个future对象
fut = loop.create_future()
# 3.添加一个任务到携程对象中同时将future对象传入做参数
await loop.create_task(func1(fut))
# 6.等待fut对象如果向下执行,没有值阻塞等待值
data = await fut
print(data)
asyncio.run(func2())
####
await asyncio.create_task(函数对象) 创建任务,任务完成后自动执行set_result进行设置返回值 等待的状态不会等待

coucurrent.futures.future对象

使用线程池与进程池实现异步函数时使用的对象与asynio没有关系
# concurrent 项目中得第三方模块不支持异步,只支持线程异步或者进程异步使用当前模块
import time
import asyncio
from concurrent.futures import Future
from concurrent.futures.thread import ThreadPoolExecutor # 线程池
from concurrent.futures.process import ProcessPoolExecutor # 进程池
1.基本使用了解
def func(value):
time.sleep(1)
print(value)
# 创建线程池
pool = ThreadPoolExecutor(max_workers=5)
# 创建进程池
# pool = ProcessPoolExecutor(max_workers=5)
for i in range(10):
# 返回值会赋值给fut
fut = pool.submit(func,i)
print(fut)
2. 使用场景: # 重点
项目中进行异步时80%为异步编程,但是20%使用项目使用的模块不支持异步编程可以使用当前方式
def func1():
print('我是一个普通函数')
return '666'
async def main():
# 获取事件循环
loop = asyncio.get_event_loop()
# 创建一个Future对象并且将函数包装为可以被async执行的协程对象
# 默认使用的是线程模式 run_in_executor(线程池对象或者进程池对象[如果参数为none那么默认使用线程池],普通函数(第三方模块不支持异步方式))
# 1.内部调用ThreadPoolExecutor的 submit 方法申请一个线程 去执行func1函数并返回一个concurrent.futures.Future对象
# 2. asyncio.wrap_future 将 concurrent.futures.Future对象包装为 asyncio.Future的对象
# 因为concurrent.futures.Future对象不支持 await方法所以需要包装
fut = loop.run_in_executor(None, func1)
res = await fut
print(res)
# 使用线程池: 可以传入max_workers 固定线程数量
with ThreadPoolExecutor() as pool:
res = await loop.run_in_executor(pool, func1)
print(res)
# 使用进程池 可以传入max_workers 固定进程数量
with ProcessPoolExecutor() as pool:
res = await loop.run_in_executor(pool, func1)
print(res)
if __name__ == '__main__':
asyncio.run(main())

案例:asyncio+不支持异步模块

import asyncio
import requests
import random
# asyncio + 不支持异步的模块下载
async def download_img(url):
print('开始下载图片', url)
loop = asyncio.get_event_loop()
# requests模块默认不支持一步操作,所以使用线程池方式配合
# run_in_executor方式非异步转为asyncio.future进行异步执行
# 内部默认是线程方式执行,3个人任务就是3个线程,那么资源上比普通的asyncio要大
future = loop.run_in_executor(None, requests.get, url)
ret = await future
file_img_name = str(random.randint(1, 10)) + '.jpg'
with open(file_img_name, mode='wb') as f:
f.write(ret.content)
if __name__ == '__main__':
url_list = [
'https://ts1.cn.mm.bing.net/th/id/R-C.df4462fabf18edd07195679a5f8a37e5?rik=FnNvr9jWWjHCVQ&riu=http%3a%2f%2fseopic.699pic.com%2fphoto%2f50059%2f8720.jpg_wh1200.jpg&ehk=ofb4q76uCls2S07aIlc8%2bab3H5zwrmj%2bhqiZ%2fyw3Ghw%3d&risl=&pid=ImgRaw&r=0',
'https://pic3.zhimg.com/v2-58d652598269710fa67ec8d1c88d8f03_r.jpg?source=1940ef5c',
'https://tse1-mm.cn.bing.net/th/id/OIP-C.xq6cOv82ubIhJY9qkFd5AgHaEK?pid=ImgDet&rs=1',
]
loop = asyncio.get_event_loop()
task_list = [download_img(i) for i in url_list]
loop.run_until_complete(asyncio.wait(task_list))

异步迭代器(不重要)

异步迭代器:
实现了 __aiter__() 和 __anext__() 方法的对象。__anext__必须返回一个awaitable对象 async for会处理异步迭代器 __anext__() 方法返回可执行等待对象,直到引发 stopasynciteration异常
异步可迭代对象
可在 async for 语句中使用的对象,必须通过__aiter__()方法返回一个asynchronous.iterator
import asyncio
# 无太大的应用场景
# 除非使用循环取每一个值,但是又不想使用 内部循环时可以使用
class Reader:
# 自定义异步迭代器(同时可以异步迭代对象)
def __init__(self):
self.count = 0
async def readline(self):
self.count += 1
if self.count == 100:
return None
return self.count
def __aiter__(self):
return self
async def __anext__(self):
val = await self.readline()
if val is None:
raise StopAsyncIteration
return val
# 正常的迭代器
# r = Reader()
# for i in r: print(i)
# 异步的迭代器
# async fro 必须写在异步函数内
async def func():
r = Reader()
async for i in r:
print(i)
if __name__ == '__main__':
asyncio.run(func())

异步上下文管理器(重要)

上下文管理器:with 上下文 自动帮打开文件写入信息并且在内容写入完毕后自动关闭文件
异步上下文管理器是一样的概念
使用对象定义 __aenter__() 和 __aexit__() 方法 对async with语句中的环境进行控制
# 在类中定义了:
__aenter__() 和 __aexit__() 支持async with上下文方式 [在正常定义的类中如果使用这个方法也可以使用这种方式]
import asyncio
# 作用例如: 链接数据库 关闭数据
# 正常的非异步 只要类中定义了这类方法都可以进行上下文
class AsyncioM:
def __init__(self):
self.count = 0
async def func(self):
return 666
async def __aenter__(self):
# 异步链接数据库操作
self.conn = asyncio.sleep(1)
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
# 异步关闭数据库链接
await asyncio.sleep(1)
# async with 方法需要在异步函数内进行使用
# 当使用是会执行当前类中得__aenter__ 这个方法返回什么那么f就是什么[可以进行设置数据库链接]
# 当上下文完成后 就会自动使用__aexit__方法[关闭数据库链接]
async def func1():
async with AsyncioM() as f:
res = await f.func()
print(res)
asyncio.run(func1())

uvloop事件循环

事件循环的替换方案
效率: 默认event loop < uvloop(接近go语言效率)
1.安装
pip3 install uvloop # 当前模块暂时不支持win系统(无法安装)
2.需要将默认事件循环替换为 uvloop
import asyncio
import uvloop
# 将以前的event loop替换为uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
# 代码与原来的一样
asyncio.run(..)
框架内部使用了asgi -> uviorn(内部使用了unloop)

案例

异步Redis操作

假设:
后端服务 A服务器
redis数据库 B服务器
那么后端代码与redis进行交互式的时候,就会产生网络请求(网络io)链接/操作/断开都是网络io请求。
# 语法都是相近的只不过 同步变异步
# 文档;https://aioredis.readthedocs.io/en/latest/migration/
1.安装
pip install aioredis
2.使用
import asyncio
import aioredis
'''
aioredis模块基础了原来的redis的类使用方式是相同
'''
async def execute(address, password=None):
print('开始执行链接操作', address)
# 1.创建链接
# decode_responses 获取参数时解码
# encoding 用于响应解码的编解码器。
# 链接方式1: 默认的生成方式需要 传入指定的ip端口链接[io等待]
redis = await aioredis.Redis(host='127.0.0.1', port=6379, db=0, decode_responses=True, encoding='utf-8')
# 链接方式2: 通过url进行生成redis 链接对象 cdn链接[io等待]
redis1 = await aioredis.from_url(address, encoding="utf-8", decode_responses=True)
# 2.链接对象
# print(redis)
# print(redis1)
# 3.设置值[io等待]
await redis.set('66789', 'wkx')
await redis1.set('8899', 'wkx')
# 4.获取值[io等待]
key = await redis.get('66789')
print(key)
key1 = await redis1.get('8899')
print(key1)
# 5.关闭链接[io等待]
await redis.close()
await redis1.close()
await redis.wait_closed()
await redis1.wait_closed()
asyncio.run(execute('redis://127.0.0.1:6379/1'))

异步Mysql操作

io操作:链接/设置数据/获取数据/关闭mysql
# 语法都是相近的只不过 同步变异步
# 文档地址:https://aiomysql.readthedocs.io/en/latest/
1.安装
pip install aiomysql
2.普通的mysql链接方式
import pymysql
pymysql.install_as_MySQLdb()
# 1.创建服务链接
serve = pymysql.connect(host='127.0.0.1',port=3306,user='root',password='123456',db='db21')
# 2.创建控制mysql权柄光标
conn = serve.cursor()
# 3.执行sql
sql = conn.execute('show tables')
# 4.获取数据
data = conn.fetchall()
print(data)
# 5.关闭链接
conn.close()
serve.close()
3.使用aiomysql链接与普通的方式相同
内部处理语法相同的
import asyncio
import aiomysql
async def execute():
# 创建mysql服务链接
conn = await aiomysql.connect(host='127.0.0.1', port=3306, user='root', password='123456', db='db21')
# 创建控制mysql权柄光标
cur = await conn.cursor()
# 执行命令
await cur.execute('show tables')
# 获取数据
res = await cur.fetchall()
print(res)
# 关闭链接
await cur.close() # 关闭光标
conn.close() # 关闭链接
asyncio.run(execute())

异步mongodb操作

# mongodb模块使用的异步模块是motor模块
# 语法都是相近的只不过 同步变异步
# 文档地址:https://motor.readthedocs.io/en/stable/
普通的链接方式:
import pymongo
# 设置了密码域账户
url = 'mongodb://root:123456@127.0.0.1:27017/admin'
mon = pymongo.MongoClient(url)
print(mon.list_database_names())
1.安装
pip install motor
2.使用
import asyncio
from motor import motor_asyncio
url = 'mongodb://root:123456@127.0.0.1:27017/admin'
async def func():
# 创建链接对象
conn = motor_asyncio.AsyncIOMotorClient(url)
# 获取数据库的全部名称
db = await conn.list_database_names()
print(db)
# 断开链接
conn.close()
asyncio.run(func())

FastAPI框架(为例)

# 文档:https://fastapi.tiangolo.com/zh/
性能比较高的框架(使用的事件循环:uvloop)
fastapi: 是一个api的接口异步框架
uvicorn:基于 uvloop 和 httptools 构建的非常快速的 ASGI '服务器'
1.安装fastapi框架
pip install fastapi
pip install uvicorn(asgi是一个支持异步的 uwsgi web服务器,内部基于uvloop)用来启动异步框架的服务器
2.基本使用[无异步功能,排队使用]
import asyncio
import uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.get('/')
def index():
'''普通的接口程序'''
print(1234)
return {'code': 200, 'msg': '你好,欢迎使用', 'err': ''}
if __name__ == '__main__':
# uvicorn.run(内部的参数)
# app: 运行出口文件的函数 所在文件.py:fastaspi实例对象 例如:测试13:app[测试13.py文件下的app=FastAPI()]
# host: 服务启动的url 默认127.0.0.1
# port: 端口 默认为 8080
# reload : 热更新,如果内容修改重启服务器相当于falsk debug=True
# debug: 同reload
# reload_dirs:设置需要 reload 的目录,List[str] 类型
# log_level: 设置日志等级 默认为等级为info
uvicorn.run('测试13:app')
# 第二种启动方式: 在命令行启动
# uvicorn main:app 命令含义如下:
# main:main.py 文件(一个 Python "模块")。
# app:在 main.py 文件中通过 app = FastAPI() 创建的对象。
# --reload:让服务器在更新代码后重新启动。仅在开发时使用该选项。
3.异步使用
import asyncio
import aioredis
import uvicorn
from fastapi import FastAPI
app = FastAPI()
# 创建连接池redis对象,添加了一个max_connections限制链接
# 方式1
conn = aioredis.from_url('redis://127.0.0.1:6379',max_connections=10)
# 方式2
conn1 = aioredis.ConnectionPool.from_url('redis://127.0.0.1:6379',max_connections=10)
redis = aioredis.Redis(connection_pool=conn1)
@app.get('/')
def index():
'''普通的接口程序'''
print(1234)
return {'code': 200, 'msg': '你好,欢迎使用首页接口', 'err': ''}
@app.get('/red')
async def red():
'''异步接口,用户访问出现io操作,不会进行等待,去接待新的访问用户。'''
print(456777)
await redis.execute_command("set", "my-key", "value")
return {'code': 200, 'msg': '你好,欢迎使用red接口', 'err': ''}
if __name__ == '__main__':
uvicorn.run('测试13:app')
其他的异步框架写法也是相同的

爬虫

1.安装
pip install aiohttp
2.实例
import asyncio
import aiohttp
async def download_img(session, url, img_name):
async with session.get(url, verify_ssl=False) as response: # 发送io请求
content = await response.content.read()
with open(f'{img_name}.png', mode='wb', ) as file_obj:
file_obj.write(content)
print('本地下载完成' + f'{img_name}.png')
async def main():
async with aiohttp.ClientSession() as session:
url_list = [
'https://ts1.cn.mm.bing.net/th/id/R-C.df4462fabf18edd07195679a5f8a37e5?rik=FnNvr9jWWjHCVQ&riu=http%3a%2f%2fseopic.699pic.com%2fphoto%2f50059%2f8720.jpg_wh1200.jpg&ehk=ofb4q76uCls2S07aIlc8%2bab3H5zwrmj%2bhqiZ%2fyw3Ghw%3d&risl=&pid=ImgRaw&r=0',
'https://pic3.zhimg.com/v2-58d652598269710fa67ec8d1c88d8f03_r.jpg?source=1940ef5c',
'https://tse1-mm.cn.bing.net/th/id/OIP-C.xq6cOv82ubIhJY9qkFd5AgHaEK?pid=ImgDet&rs=1',
]
# 协程对象列表
task_list = [download_img(session, url, index) for index, url in enumerate(url_list)]
await asyncio.gather(*task_list) # 将协程对象列表批量添加到event loop 中作为task任务
if __name__ == '__main__':
asyncio.run(main())

asyncio模块

asyncio

1.单进程单线程的程序,不能提高程序的运算速度
2.作用: 比较适合处理等待的任务,网络通信,不存在系统级的上下文切换
3.async分为: coroutine function(协程函数) coroutine object(协程对象)
例如:
import asyncio [3.7版本以上]
# 1.当前这个函数被称为coroutine function(协程函数)[async开头函数的都叫协程函数]
async def main():
print('hello')
await asyncio.sleep(2)
print('world')
# 2.正常的调用main函数[只会获取到当前协程对象,不会执行]
coro = main()
# coroutine object(协程对象) 打印出来的函数是携程的对象<coroutine object ..>
print(coro) # 不会进行运行 提示:运行错误,协程函数main主进程未等待
如果想启动async函数需要进入 event loop(事件循环) 控制程序的状态
1.需要导入asyncio
2.使用asyncio.run()接管整个程序[那么当前的入口程序就需要一个入口函数]
3.asyncio.run()函数: 1.建立起 event loop [事件循环] 2. 会将当前的main函数[coroutine function(协程函数)]作为当前event loop[事件循环]当其中的task[任务]
4.asyncio.run('参数是一个async关键字函数') 函数将程序从 '同步模式' 改变为 '异步模式' 的入口

asyncio使用说明

import asyncio
import time
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
return '666'
async def main():
print(f'当前时间{time.strftime("%X")}')
a = await say_after(1, 'hello') # 接受返回值
print(a)
await say_after(2, 'word')
print(f'结束时间{time.strftime("%X")}')
asyncio.run(main())
1.await:
'''
await会将 say_after() 时,变为一个task(任务)
发生:
1. 当前这个 协程函数对象 被包装为一个task(任务),告诉了event loop (循环事件) 当前这个新任务的存在
2. 告诉了event loop (循环事件) 这个task(任务)需要等待 say_after(async函数)执行完毕后 才能接着执行
3.yield出去,告诉event loop(循环事件) 当前这个任务暂时执行不了 请执行其他task(任务)
4. 当前event loop(循环事件) 再次安排当前task(任务)执行时 会将say_after(async函数)中得真正返回值拿出来进行保存(赋值给变量)
'''
2.整段程序执行过程:
'''
1.asyncio.run(main()) 当main函数作为一个task(任务) 给到了event loop(循环事件)中
event loop 在寻找task(任务)时,发现只有一个main task(任务),就运行了main函数
2.main任务再执行先打印了:print(f'当前时间{time.strftime("%X")}')打印
3.执行了 await say_after(1, 'hello') 函数得到了 协程对象(coroutine object)
4.await say_after(1, 'hello') await 将整这个协程对象(coroutine object) 变为一个 task(任务) 放回了event loop 里面 同时告诉event loop需要等待他,将控制权给到了event loop
5.现在event loop 中存在两个任务[main,say_after],main运行不了需要等待say_after运行后才能运行,但是say_after函数内部有await asyncio.sleep(delay)[将当前的sleep作为一个task(任务)添加到event loop中]需要等待,又将控制权转给了event loop
6.say_after需要等待 await asyncio.sleep(delay)任务执行完毕。await asyncio.sleep(delay) 执行完毕后 event loop 让say_after 执行 打印了 print(what)执行完毕 ,将控制权交给了event loop
7.event loop 就会将控制权给main函数执行[main就执行完毕第一个 say_after] 在执行第二个say_after(与上面第一个执行一样)
'''
3. 注意
关于event loop控制权交回的两种方式:1.await 2.函数执行完毕后自动交回控制权 。如果当前任务中是一个死循环那么 event loop就直接卡死了
4.发现问题:当前两个async执行实际时间为3秒钟
因为await做的事情太多,需要将对象变为一个任务,告诉event loop 将控制权交还给event loop还需要等待,那么后面需要执行的协程函数也就需要等待前面的任务(协程函数已经通过awati转为任务后)完成后,后面的协程函数才转为一个任务,才被event loop 调用
问题解决: create_task函数

asyncio中得create_task函数

create_task('coroutine object(协程函数对象)')
create_taskd的作用:将协程对象对象转变为task(任务)注册到event loop中,分担了await一部分功能将协程函数对象包装为一个task(任务)
import asyncio
import time
1.使用create_task
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
async def main():
# 分担await一部分功能,将协程函数对象包装为task(任务)告诉event loop 这个任务可以执行
# 但是没办执行,执行权在main手中 需要await将event loop控制权拿到并且执行
task1 = asyncio.create_task(say_after(1, 'hello'))
task2 = asyncio.create_task(say_after(2, 'word'))
print(f'当前时间{time.strftime("%X")}')
# 将event loop控制权拿到并且执行
# 在之前await 协程对象时,需要将整个对象包装为task(任务)在进行执行 在将event loop 控制权拿到执行
# await task(任务) 直接获取event loop控制权进行执行(我需要这个task任务完成,将控制权交还给event loop) 在控制权回来的时候将当前任务的返回值保存
await task1
await task2
print(f'结束时间{time.strftime("%X")}')
asyncio.run(main())
2.create_task拿返回值
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
return f'{what} - {delay}'
async def main():
task1 = asyncio.create_task(say_after(1, 'hello'))
task2 = asyncio.create_task(say_after(2, 'word'))
print(f'当前时间{time.strftime("%X")}')
# 获取 任务(协程对象)返回值(返回值会在任务执行完毕后赋值给变量)
ret1 = await task1
ret2 = await task2
# 打印 协程对象返回值
print(ret1)
print(ret2)
print(f'结束时间{time.strftime("%X")}')
asyncio.run(main())
3.批量将协程函数转变为task(任务)交给event loop
使用gather('接受多个协程函数对象')
会将多个协程函数对象包装成为任务,给到event loop 进行执行
返回值处理: 会将全部的任务的返回值添加到一个列表中(返回值顺序与gather(task任务顺序一致))
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
return f'{what} - {delay}'
async def main():
task1 = asyncio.create_task(say_after(1, 'hello'))
task2 = asyncio.create_task(say_after(2, 'word'))
print(f'当前时间{time.strftime("%X")}')
task_list = [task1, task2] # 任务列表
ret = await asyncio.gather(*task_list)
print(ret) # 全部的返回值['hello - 1', 'word - 2']
print(f'结束时间{time.strftime("%X")}')
asyncio.run(main())
4.gather的另一个好处:将全部的协程对象包装为task(任务)
# 省去 cerate_task 的操作
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
return f'{what} - {delay}'
async def main():
print(f'当前时间{time.strftime("%X")}')
task_list = [say_after(1, 'hello'), say_after(2, 'word')] # 协程函数对象列表
ret = await asyncio.gather(*task_list)
print(ret) # 全部的返回值['hello - 1', 'word - 2']
print(f'结束时间{time.strftime("%X")}')
asyncio.run(main())

async理解

event loop 当做大脑,若干个可以执行的task任务,task任务没办控制event loop去到那个task执行的,只能告诉event loop 我在等待那个task执行完毕,由event loop 进行控制权的分配到那个task
将event loop权限给出去的方式: 1.await 2.函数执行完毕
其实可以理解为,只是当前的代码在执行,只不过将等待的事件给利用了起来执行了另外的内容,节约了时间,如果代码中存在等待的操作,那么使用async最好,如果没有等待那么没什么用
要理解:
1.coroutine function(协程函数)
2.coroutine object(协程对象[协程函数加()执行的结果])
3.task(任务[是由协程对象被asyncio方法包装为任务给到event loop中,变为task才能被执行])
4.拿到task返回值需要使用await方法,await方法会获取到event loop控制权(告诉它这个任务需要执行)

补充

if name == 'main' 作用

if __name__ == '__main__' 作用
test.py :
在当前文件中进行打印 print(__name__) # 结果就被解析为 __mian__
show.py
import test # 导入 test脚本 执行当前show.py脚本
在test.py文件中打印 print(__name__) # 就变为 test 也就是当前的文件名
当模块被导入时,模块名称是文件名;而当模块作为脚本独立运行时,名称为 __main__。
让模块既可以导入又可以执行

本文作者:_wangkx

本文链接:https://www.cnblogs.com/kaixinblog/p/17878597.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   _wangkx  阅读(43)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起