Python基础语法
循环,终止,字符换格式化
while循环
语法:
whlie 条件:
执行代码
执行代码
'''
当条件成立时,代码会一直执行。
当条件不成立时,条件就不会执行。
'''
条件: 1. 比较条件 2. 布尔值条件 3. 变量条件
最后while 循环都会讲条件转换成为布尔值,False 和True
循环终止参数
循环终止,只有在循环是才能使用。
分为2 种:
1. **break ** 作用:帮助终止循环,只要代码执行到break 时,不论是后面有多少代码,都不会执行。
2. **continue ** 作用:结束本次循环,开始下一次循环,遇到 continue ,后面不论多少代码,都会停止到最初代码。
while 和 else使用
while 条件:
代码...
代码...
代码...
else :
代码....
当遇到break 时,代码不会执行,因为break 中止循环
字符串格式化
字符串格式化分为:
1. %百分号
%百分号的使用
%s:就是一个占位符号
name = wkx
text = “我叫什么%s”%(“wkx”) 字符串
text = “我叫什么%s”%(name) 变量
text=“我叫什么%(name)”%{“wkx”}
当遇到内容中有百分号%时,又想用%号占位符就需要特殊处理:%%使用两个百分号
2. format
format
text = “我叫什么{}” .format (name) 变量
text = “我叫什么{}” .format (123 ) 字符串或者其他类型
需要复用的情况下需要,设置一个标志性的符号进行复用
其他用法
3.1415926 {:.2 f} 3.14 保留小数点后两位
3.1415926 {:+.2 f} +3.14 带符号保留小数点后两位
-1 {:-.2 f} -1.00 带符号保留小数点后两位
2.71828 {:.0 f} 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 {:.2 e} 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
运算符的优先级
运算符补充
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 循环内容例如:(range (10 )) 可以写入条件 if ]
列表中的推导式案例
name = [i for i in range (10 )]
同时列表推导式中可以写上条件
name = [i for i in range (10 ) if i > 6 ]
集合和字典都是相同
特殊情况:
如果是元组推导式就会变成一个生成器
data = (i for i in range (10 ))
for i in data:
print (i)
列表推导式:在列表内的作用域,只能在自己的作用域运行
列表推导式和在外部的for 循环一样,当遇到函数时,
for i in range (10 ):
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)
交互式(用终端)用于测试
计算机底层都是 0 101 储存的 被称为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
不可哈希: list ,set ,dict
查找速度特别快
独有功能
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. 可哈希类型:
数字类型(int ,float ,bool )字符串str 、元组tuple
注意:int 与float 类型通过hash 计算后还是原来的值,取决于__hash__魔术方法的运算过程
bool :在通过hash 运算后为1 ,0
可以理解为:当前变量值为key,那么hash 运算后的值为value
使用str 类型举例:
string1 = 'string'
string2 = 'str' + 'ing'
print (string2 == string1)
print (string1 is string2)
print (id (string1))
print (id (string2))
print (hash (string1), hash (string2))
可以断定: 当前两个变量指向是同一块内存地址,那么hash 值也相同,也满足了hash 表的特点,通过变量元素找到key的内存地址
2. 不可哈希类型:
list set dict (dict 中得必须是可哈希类型的)
对于可变对象而言,比如一个列表,更改列表的值,但是对象的地址本身是不变的,也就是说不同的Key,映射到了相同的Value,这显然是不符合哈希值的特性的,即出现了哈希运算里面的冲突
可以理解为: 不可哈希类型中内部的值是可以变化的,但是占用的内存地址是不会变化的
利用列表为举例:
lis = [11 ,22 ,33 ]
print (id (lis))
lis.append('wkxxx' )
print (id (lis))
内存地址不会变化,但是里面的值发生了变化
3. 为什么dict 中得key必须是可哈希的?
字典的哈希表实现使用从键值计算的哈希值来查找键
比如:
d = {[1 , 2 ]: '100' }
print (d[[1 , 2 ]])
但是由于列表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' )
ini.get('mysql' ,'a' )
ini.has_section('mysql' )
添加节点
ini.add_section('nginx' )
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("文件路径" )
5. os.makedirs('新创建的文件' )
6. os.path.isdir("路径" )
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' ))
15. os.mkdir('dirname' )
16. os.rmdir('dirname' )
17. 其他
print (os.stat('path/filename' ))
print (os.sep)
print (os.linesep)
print (os.pathsep)
print (os.name)
os.system('dir C:\\Users\\56515\\Desktop\\基本内容复习\\其他' ) 作用就是在python中执行cmd的命令
print (os.environ)
18. 重点 将路径的\ 转为适合当下系统的斜杠
os.path.normcase('c:/win\\123' )
19. 查看文件下的全部
print (os.path.getsize('../model测试' ))
20. 判断是不是文件
print (os.path.isfile('bj.txt' ))
21. 判断是不是绝对路径
print (os.path.isabs(r'C:\b\b\a.txt' ))
22. print (os.path.getatime('aaa' ))
23. print (os.path.getmtime('aaa' ))
Path-文件路径
from pathlib import Path
root = Path(__file__)
ret = root.parent
print (ret)
res = Path(__file__) / 'a\e.txt'
print (res)
print (res.resolve())
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
sys.modules
sys.version
print (sys.maxsize)
print (sys.platform)
sys.exit(0 )
random模块
数字模块
import random
print (random.random())
random.randint(10 ,100 )
print (random.randrange(1 , 10 ))
random.uniform(10 ,100 )
lis = [11 ,22 ,33 ,44 ,55 ,66 ,77 ,88 ,99 ]
random.sample(lis,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())
密码加盐
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())
检验文件的完整性
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))
print (json.loads(json.dumps(dic)))
time模块
import time
1. 获取时间戳 从1970 -现在的时间
time.time()
2. 获取当前时区
time.timezome
3. 睡眠
time.sleep("停止时间按秒计算" )
4. 获取格林尼标准时间
print (time.asctime())
5. 获取时间格式化
time.localtime()
6. 获取格式化的时间
print (time.strftime('%Y-%m-%d %H:%M:%S %p' ))
print (time.strftime('%Y-%m-%d %X' ))
datetime模块
from datetime import datetime
1. 获取当前时间
datetime.now()
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()
变量2 = 变量1. strftime("转换时间的格式" )
%Y年,%m月份,%d天,%H时 %M分 %S秒
datetime.now().strftime('%Y-%m-%d' )
7. datetime格式转换时间戳
时间戳 = time.time()
datetime.datetime.fromtimestamp(时间戳)
8. 获取世界时间
print (datetime.datetime.utcnow())
格林尼标准时间
from datetime import datetime
1. 变为gmt时间格式
GMT_FORMAT = '%a, %d %b %Y %H:%M:%S GMT+0800 (CST)'
print (datetime.utcnow().strftime(GMT_FORMAT))
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))
时间转换
转换 格式化字符串 <-> 结构化时间 <-> 时间戳
1. time时间对象
s_time = time.localtime()
print (time.mktime(s_time))
2. 时间戳转为time时间对象
s_times = time.time()
print (time.localtime(s_times))
3. 本地结构化与世界结构化
print (time.localtime())
print (time.gmtime())
4. 重点:结构化时间对象 转为 格式化时间
s_time = time.localtime()
print (time.strftime('%Y-%m-%d' , s_time))
5 重点 格式化时间转为结构化时间
print (time.strptime('1988-03-03' ,'%Y-%m-%d' ))
案例:
目标 累积 +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))
name = '0aaaa2sa122aasd8l'
print (re.search('a?' , name))
{m} 匹配前一个字符的m次
{整数,填写几} 那么就会匹配前一个字符几次
例如:匹配a 4 次 也是就是 aaaa 连着
name = '0aaaa2sa122aasd8l'
print (re.search('a{4}' , name))
{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))
(...) 分组匹配
print (re.search('([a-z]+)([0-9]+)' , 'ajax123' ).groups())
将括号内的匹配到的参数 放到一块,可以通过groups() 获取分组匹配的全部的内容
groups 只能分组匹配使用
[] 范围匹配 都包含
print (re.search('[a-z]' , 'ajax123' ))
匹配 a-z 的全部范围的字母
也可以[1 -9 ] 匹配
\A 匹配 \相当于一个^ 从头开始匹配
\Z 匹配尾部的字符 同 $
\d = [1 -9 ]
\d+ = [1 -9 ]+
\D 匹配一个
\D+ 匹配多个
\w 跟re.后面的匹配模式 进行匹配一个或者多个
\w+ 匹配多个全部 大写a-z 与小写的a-z 与数字 0 -9
\W
\W+ 匹配全部的
\s
\s 匹配全部
print (re.search('(?P<name>\d{3})' , '13027630227' ).groupdict())
print (re.split('\d' , 'wkx16wuux1ww' ))
print (re.findall('\d' , 'wkx16wuux1ww' ))
print (re.sub('\d+' , '++' , 'sasd112a332' , count=1 ))
print (re.fullmatch('\w+@\w+\\.(com|cn|edu)' , '565151759@qq.com' ))
res = re.compile ('\w+@\w+\\.(com|cn|edu)' )
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)
r = pickle.loads(res)
print (r)
subprocess模块
import subprocess
obj = subprocess.Popen('dir /C;/C' , shell=True ,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
print (obj)
print (obj.stdout.read().decode('gbk' ))
print (obj.stderr.read().decode('gbk' ))
logging
import logging
******** 基本使用 *********
logging.basicConfig(
filename='login/access.log.text' ,
format ='%(asctime)s - %(name)s - %(levelname)s -%(module)s : %(message)s' ,
datefmt='%Y-%m-%d %H:%M:%S %p' ,
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('打印调试日志' )
logging.info('打印消息' )
logging.warning('打印消息' )
logging.error('打印报错' )
logging.critical('打印严重错误' )
*********日志配置字典*******
'''
日志轮转
日志记录这系统的非常重要的内容,不可以轻易的删除,可以用来查看当前系统的状态
就是当随着时间的变化日志就会一直写在同一个文件(造成几个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 ,
'formatters' : {
'standard' : {
'format' : standard_format
},
'simple' : {
'format' : simple_format
},
'test' : {
'format' : test_format
},
},
'filters' : {},
'handlers' : {
'console' : {
'level' : "DEBUG" ,
'class' : "logging.StreamHandler" ,
'formatter' : 'simple' ,
},
'default' : {
'level' : "DEBUG" ,
'class' : "logging.handlers.RotatingFileHandler" ,
'maxBytes' : 1024 * 1024 * 5 ,
'backupCount' : 1 ,
'formatter' : "standard" ,
'filename' : "按照文件大小轮转.txt" ,
'encoding' : "utf-8"
},
'times' : {
'level' : "DEBUG" ,
'class' : 'logging.handlers.TimedRotatingFileHandler' ,
'when' : 'S' ,
'interval' : 5 ,
'backupCount' : 10 ,
'encoding' : "utf-8" ,
'filename' : "login/按照时间轮转.txt" ,
'formatter' : "standard" ,
},
'other' : {
'level' : "DEBUG" ,
'class' : "logging.FileHandler" ,
'formatter' : "test" ,
'filename' : "a2.log.txt" ,
'encoding' : "utf-8"
},
},
'loggers' : {
'kkk' : {
'handlers' : ['other' , 'console' , "default" ],
'level' : "DEBUG" ,
'propagate' : False ,
},
'' : {
'handlers' : ["times" ],
'level' : "DEBUG" ,
'propagate' : False ,
}
}
}
from logging import config, getLogger
config.dictConfig(LOGGING_DIC)
logger = getLogger('日志名' )
logger.debug('这是debug日志' )
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
'''
payload['exp' ] = expiry
token = jwt.encode(payload, secret, algorithm='HS256' )
return token
from datetime import datetime,timedelta
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)
生成token和验证token
生成token :
头部 生成一个base64编码 -> 载荷payload生成一个base64为的编码 ->通过数学计算获取到token值
例如:
是由: head头部. payload载荷部分 .计算部分
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoxMjMsImV4cCI6MTY1MTUwMjMyMn0.ByqnK783wVHWHVqX4umnPN-ic09bJTitODOQnVt1nBU
验签部分:
head头部. payload载荷部分.计算部分
base64编码 base64编码
如果相同那么jwt有效,才会将载荷部分的内容返回,在进行查看exp中有有效期是否过期过期那么token值就是none
token刷新机制
客户端 --------------- 服务器
1. 客户端发送请求进行登录
2. 服务端验证登录的密码账号,成功返回一个token值
3. 客户端(前端保存token)
4. 当客户端访问其他的服务器页面时,会在header头部携带token值
5. 服务器进行验签如果携带token正确就进行返回正常视图
没有携带返回401
token有问题 返回401 /403
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码
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,获取请求的正常逻辑。
刷新的实现
1. 登录的接口设计:
1. 在登录是需要发送post请求
{
name:'' ,
paw:''
}
2. 在后端接受完成返回错误返回400
正确: 返回状态码,和token,刷新的token内容
{
code:200 ,
'message' :'ok' ,
data:{
'token' :'' ,
"refresh_token" :''
}
}
前端需要将data中的值存储到前端vue中的会话技术中localstorage
2. 接口的代码设计:
在flaks api框架获取用户的密码和账号的时候,就直接进行数据库或者其他到的逻辑判断,证明这个用户时可以登录,在登录的时候,需要携带token值返回给前端
刷新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)
pl.expire(key,token有效期)
pl.execute()
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:
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 = '这是一个密钥'
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
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
@app.before_request
def jwt_authentication ():
'''
flask的钩子函数
1.获取请求头中的token,因为存在两个请求头,所以需要进行识别
2.进行判断token值的是否存在
3.验签
4.存入到flask对象中的g对象中
:return:
'''
token = request.headers.get('Authorization' )
g.user_id = None
g.is_refresh = None
if token is not None and token.startswith('JWT ' ):
token = token.split(' ' )[1 ]
payload = verify_jwt(token, SECRET)
if payload is not None :
g.user_id = payload.get('user_id' )
g.is_refresh = payload.get('is_refresh' , False )
def login_required (func ):
def wrapper (*args, **kwargs ):
if g.user_id is not None and g.is_refresh is False :
return func(*args, **kwargs)
else :
return {'msg' : 'token非法' }, 401
return wrapper
class IndexLogin (Resource ):
method_decorators = {
'post' : [login_required, ],
}
def __init__ (self ):
self.expiry = datetime.utcnow()
def _generate_tokens (self, user_id, refresh=False ):
'''
:param user_id: 登录后用户的id
:param refresh: 根据这个值,需要请求token还是不需要请求token
:return: 刷新token和请求token
'''
'''这个方法主要是在用户登录时是生成token'''
expiry = self.expiry + timedelta(hours=2 )
token = generate_jwt({'user_id' : user_id}, expiry, SECRET)
if refresh:
expiry = self.expiry + timedelta(days=14 )
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/' )
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
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 ) * '#'
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
json.loads = ujson.loads
def mokey_patch_json ():
'''只是将json名称空间的方法进行替换了'''
json.__name__ = 'ujson'
json.dumps = ujson.dumps
json.loads = ujson.loads
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
2. 创建虚拟环境
virtualenv 虚拟环境名称 --python=python3.9
virtualenv 虚拟环境名称 --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 按照的关键字传参,按照的是字典结构接收
def func (*args ):
print (args)
func('函数' ,'函数666' )
def func (**kwargs ):
print (kwargs)
func(a='函数' ,b='函数666' )
def func (*args, **kwargs ):
print (kwargs)
print (args)
func('123' , 1111 , a='函数' , b='函数666' )
2.函数的返回值
1. 存在返回值 会将返回值返回给调用者,可以赋值给变量也可以直接打印
def func (val ):
print (val)
return '666'
val = func('函数' )
print (val)
2. 不返回值 没有返回值,那么接受者接收的值就是None
def func (val ):
print (val)
val = func('函数' )
print (val)
返回值的作用:
函数中代码处理后一些自己需要用的数据,可以通过返回值获取获取函数的结果
关键字,在函数中return
函数的概念性内容
1.关于函数穿的参数是什么?
函数在传参时默认传入的是什么:传入的是内存地址。
优势:
1. 省内存空间
2. 函数在内部可以对数值进行处理(处理的是内存地址)
参数需要可变类型才能对参数进行修改:参数可变类型:list ,dict ,set
3. 其他语言是对参数进行拷贝,对拷贝的进行处理。如果python也想这样需要copy模块进行拷贝,函数就会对拷贝的参数进行处理,不会对原数据进行处理
例如:引用的是当前变量的内存地址,而不是拷贝
name = '666sada'
def func (val ):
print (id (val))
print (id (name))
func(name)
2.函数的返回值是什么
函数的返回值返回的是:内存地址
在函数内部有引用计数器,当使用参数时,计数器加1
函数内部的变量在当函数执行完毕后,变量自动销毁,计数器减1
返回值的特点:
1. 当两个变量接受函数的返回值,数值相同,内存地址是不同的,两个执行函数的变量,相互不会干扰。
每个变量都会进行创建内存地址。
例如:
def foo ():
return [123 , 45645 ]
v1 = foo()
print (id (v1))
v2 = foo()
print (id (v2))
2. 每次执行函数,如果值时数值和字符串,那么的内存地址不论执行多少次函数都是相同的。
因为python有内存驻留机制。考虑到函数内部使用数值和字符串次数太多,为了优化内存产生。
def foo ():
return 123
v1 = foo()
print (id (v1))
v2 = foo()
print (id (v2))
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)
案例2
def func (a, b=[1 , 2 , 3 ] ):
b.append(a)
print (id (b))
return b
val1 = func(12 )
print (val1)
val2 = func(13 )
print (val2)
val3 = func(14 )
print (val3)
val4 = func(15 , [16 ])
print (val4)
每一次执行函数都不会对默认值进行内存地址的重新创建,从始至终一直使用默认值得初始的内存地址。
4.动态参数高级用法
*args **kwargs
动态参数不仅仅可以当成形参,也可以当成实参进行处理
在执行函数时,可以将传入的列表字典等数据打散传入。
def func (*args,**kwargs ):
print (args)
print (kwargs)
func([11 ,12 ],{"a" :'666' })
func(*[11 ,12 ],**{"a" :'666' })
可以对函数传入的参数进行打撒操作,会根据数据的结构传入args和kwargs中
元祖列表集合打撒后传入ages
字典 打撒后传入kwages
5.函数名/函数
1.
函数在执行可以成为元素,或者参数,或者返回值等等执行或者调用
将函当成返回值 or 参数 or 元素都可以
def func ():
print (1 )
def show ():
return func
v1 = show()
v1()
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)
func()
print (name)
2. 使用global 关键字
name = '666'
def func ():
global name
name = '777'
print (name)
func()
print (name)
函数高级语法
1.函数的嵌套
1. 作用问题
全局作用于函数,在每一次调用都会产生一个局部的作用域(每次调用就会产生一个新的局部作用域),在函数内部还有函数,就会在父级函数中产生一个子级的函数作用域,这就叫嵌套
2. 函数执行问题
当函数执行时内部有其他的函数执行,如果内部没有就去全局找并且执行。
当函数的名字相同时,下面的函数会覆盖上面的函数(python从上到下执行)
当函数执行时,确定函数作用域是谁。
函数可以在子作用域中或者更深。
例如:
def foo ():
print ('开始' )
def func ():
print ('正在执行' )
func()
print ('结束' )
def out ():
print ('我在执行out函数' )
foo()
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函数
例如:
def func (val ):
print ('开始' )
def show (*args, **kwargs ):
print ('666' )
res = val(*args, **kwargs)
return res
print ('结束' )
return show
@func
def inner ():
print (1 )
inner()
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
'''
具体的逻辑
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)
匿名函数
匿名函数:就是没有名字的函数。
关键子 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 ()
sum ()
v1,v2 = divmod ()
round ()
min ()
max ()
all ()
any ()
ord ()
chr ()
len ()
open ()
type ()
range ()
enumerate ()
help ()
callable ()
sorted ("字典,元组,集合" ,reverse=True )
sorted ("字典.itema()" ,key=lambda x:x["字典的其中一个值" ]["某个指的名字" ])
num = '123'
print (int (num))
print (str (num).__repr__())
print (bool (num))
print (float (num))
print (tuple (num))
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))
print (max (dic,key=lambda key:dic[key]))
******** sorted 排序的原理和参数 ****
print (sorted (dic,key=lambda key:dic[key]))
****了解*****
lis = [11 , 12 , 13 , 14 , 15 , 16 , 78 , 99 , 101 , 1104 , 1588 ]
print (map (lambda key: key * 2 , lis))
l = ['xx' , 'xx1' , 'xx2' ]
print (filter (lambda key: key.endswith('1' ), l))
from functools import reduce
print (reduce(lambda x,y:x+y,[1 ,2 ,3 ],10 ))
print (abs (-100 ))
print (round (1.3 ))
作用于关键字
global 关键字可以通过局部作用域对'全局变量' 进行修改
nolcal 关键字通过局部作用对'上级作用域变量' 进行修改
生成器
作用:生成器是由函数和yield 关键字 构造出来的,在特定条件下帮助我们省去内存空间
1. 生成器函数
def 函数名 ():
函数体
yield 数值或者内容,当添加yield 关键字时就是生成器函数
2. 生成器函数与函数的不同
当执行生成器函数时:不会被执行,会获得一个生成器的对象
例如:
def func ()
print (123 )
yield 123
print (123 )
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)
'''
a 与 b 都是独立的对象,但是都执行同一个内存地址(同一个引用)
只要其中一个改变,那么另一个就会改变
'''
深拷贝
import copy
a = {1 : [1 , 2 , 3 ]}
b = copy.deepcopy(a)
a[1 ].append(4 )
print (a, b)
'''
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
导入规范
import 内置的
import 第三方
import 自定义
模块的可以被赋值为变量
导入
循环导入的问题
也就是 1. py文件 导入了 2. py文件
2. py文件中 导入了 3. py
3. py文件中又导入 1. py 不会在造明细空间
将公用的内容变量方法 放到一个公共的文件夹中
模块的搜索的优先级
1. 内存
将包存入内存加载
2. 硬盘找
按照sys.path的文件顺序进行查找 导入的包
import sys
print (sys.path)
列表的第一个元素 是第一个执行文件的文件夹
列表的第二个元素 是当前执行文件夹的父类 (ied帮助加载的,忽略,在脚本目录中是不存在的)
进行import
就会将文件内存地址存到 内存中 导入后,一直存储到内存中,直到程序结束
print (sys.modules)
当导入文件时,如果内存没有就会从 硬盘找,将包缓存到内存中去
当再次到导入后,就会先从内存中找到对应的包
如果在内存中找到不包,同时又在硬盘中找不到就需要进行添加
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 ]
l2 = [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.get_name()
1.初始化方法__init__
在实例化对象时,会自动触发__init__方法
class 中的 name = Name() 被称为实例化对象
1. init方法的定义
class 类名 :
def __init (self ):
pass
2. 对实例变量进行定义(当前类下的全部方法都可以使用)
class C :
def __init (self,val ):
self.val = val
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()
print (id (name))
name2 = Name()
print (id (name2))
实例化时 将对象当成参数传入到self中,self内是一个内存区域,存放着类中的方法和数据。
2.class的成员修饰符
1. 成员 类变量 实例变量
实例变量:封装到实例化对象中,只有实例化对象才能调用,在类中属于局部变量
类变量: 封装到类中,类可以调用同时实例化对象也可以调用,在类中属于全局变量
class N (object ):
age = 18
def __init__ (self, name ):
self.name = name
n = N('zss' )
print (n.age)
print (n.name)
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():
self.current_page = 1
return
current_page = int (current_page)
if current_page < 1 :
self.current_page = 1
return
self.current_page = current_page
@property
def start (self ):
return (self.current_page - 1 ) * self.per_pagr_num
@property
def end (self ):
return self.current_page * self.per_pagr_num
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 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
class Text :
def read (slfe ):
pass
def write (self ):
pass
def func (obj ):
obj.read()
他们有都是由两个方式 read 和 write 两种方式
text = Text()
disk = Disk()
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 )
A.__init__(self,name)
self.age = age
def show (self, age ):
A.show(self, age)
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 )
class B (A ):
def __init__ (self, name, age ):
super ().__init__(name)
self.age = age
def show (self ):
super (B, self).func()
b = B('HAHA' ,18 )
b.show()
抽象类的使用与概念
python 提供了抽象类的概念,提供了一个接口的方式
模仿java 模仿 interface接口
抽象类:将全部的类抽取比较像的部分,形成一个父类,让子类继承父类必须使用当前的父类的方法,必须让继承父类的子类必须按照父类的规定进行使用
import abc
class Animal (metaclass=abc.ABCMeta):
@abc.abstractmethod
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):
pass
@staticmethod
def show3 ():
pass
a = foo()
a.show1()
foo.show2()
foo.show3()
绑定方法的特殊
1. 使用类名进行调用内部的方法函数时,需要对应的传入指定的位置参数self
而且类名调用的方法函数,就是一个普通的函数
2. 绑定方法是给实例化对象使用,绑定不同的对象效果也是不同的
而对象调用内部的方法函数时,默认自动将对象本身传入到方法函数中的self中
也就是 a1.learn(a1) 将对应本身默认传入参数
总结:对象来调用自己内部的绑定方法时,会将对象本身当为第一个参数(self)传入到方法中
剩余的参数,该怎么传入就怎么传入
类中的方法函数,是给对象使用,那个对象来调用,就将他当为第一个参数传入
如果对象的名称空间中这个参数存在,那么就是用对象的名称空间中的
如果名称空间中的参数不存在,就会使用类中的参数
和函数中的局部和全局的概念是相同的意思
如果对象名称空间(局部)不存在的,那么就使用类空间中的(全局的)
当找不到也不会去类外部找
绑定方法 如果用类名调用内部的方法是不同函数 用对象调用方法是绑定函数 同时他们的内存地址是不同的
2.类中的三个特殊属性property
属性是由绑定方法和装饰器组成的。
class Func :
@property
def func1 (self ):
print ('执行property' )
@func1.setter
def func2 (self, val ):
print (val)
@func1.deleter
def func3 (self ):
print ('deleter' )
a = Func()
a.func1
a.func2 = 123
del a.func3
方法1 :
定义规则:@方法名.setter,方法中需要多加一个value参数
使用方式:a.func2 = 123 参数value可以接受 添加的值
方法2 :
定义规则:在方法上加入:@property
使用方法:a.func 在调用方法时可以不用加括号
方法3 :
定义规则:@方法名.deleter
使用方式:del a.func3 执行方法
3.类中的其他方法内置1
class Func :
def __init__ (self, name ):
self.name = name
def __new__ (cls, *args, **kwargs ):
return object .__new__(cls)
def __str__ (self ):
return "这是一个不同的方法类"
@property
def __dict__ (self ):
return '7788'
'''
例如:
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)
return '110'
a = Func()
print (a('666' , '777' ))
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
a['666' ]
del a['666' ]
当时实例对象按照字典形式执行方式时 会触发当前三个方法
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
a = A()
a1= A()
v3 = a+a1
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 ):
return self
def __next__ (self ):
self.count +=1
if self.count == 3 :
raise stopiteration()
return self.count
a = A()
v1 = next (a) 1
v2 = next (a) 2
v3 = next (a) 抛出异常
for i in a :
print (i)
生成器
生成器属于迭代器的一种
def func ():
yield 1
可迭代对象
class A (object ):
def __iter__ (self ):
return
a = A()
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 )
s2 = student("4565" ,13 )
c1 = classes("年级" )
c1.add_student(s1)
c1.add_students([s1,s2])
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()
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)
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.元类
g = {
'x' : 10 ,
'y' : 20
}
l = {}
exec ('''
global x,mxxxx
x=100
mxxxx = 1000
z=30
''' , g, l)
****** python 中一切皆对象 *****
1. 都可以被引用 x = obj
2. 都可以当成函数的参数传入
3. 都可以作为函数返回值
4. 都可以当做容器的元素 [obj.....]
默认用class 定义的类,他们的原类都是type
class A :
pass
a = A()
print (type (a))
print (type (A))
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)
A = type (class_name, class_bases, class_dic)
print (class_dic)
print (A.__dict__)
****** 自定义元类 控制类的创建 *******
class Mymeta (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('类名首字母大写' )
if '__doc__' not in class_dic or not class_dic['__doc__' ].strip():
raise TypeError('必须有注释,并且不能为空' )
super ().__init__(class_name, class_bases, class_dic)
class Chinese (object ,metaclass=Mymeta):
'''000'''
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) # 继承的父类类
'''
****** 定于元类 控制类的实例化 ******
补充
class F :
def __call__ (self, *args, **kwargs ):
print (self)
print (args)
print (kwargs)
f = F()
f(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 = object .__new__(self)
self.__init__(obj, *args, **kwargs)
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
obj = Chinese('wkx' , 18 )
'''
实例化过程
1.创建空对象
2.初始化obj
3.返回obj
而元类的过程也是一模一样的
'''
print (obj.__dict__)
******* 使用创建的元类进行实例化应用 *******
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)
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 ):
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)
print (type (M))
Python异常处理与反射
1.异常处理
1. 处理原理:
1. 基本格式
try :
正常逻辑代码
except Exception as e:
e 是一个对象 内部存放的错误信息 可以通过str 进行转换
2. 高级格式
try :
正常逻辑代码
except Exception as e:
try 内的代码异常触发,此处代码。
finally :
try 中的代码是否报错,finally 的代码都会执行。被成为释放资源
2. 异常处理的细分
try :
正常逻辑代码
except 其他异常 as e:
代码
except 其他异常 as e:
代码
except 其他异常 as e:
代码
except Exception as e: 捕获全部的异常
try 内的代码异常触发,此处代码。
3. 自定义异常类
class 自定义异常类名 (继承Exception类):
pass 代码
try :
正确的逻辑代码
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后面的代码就不在执行
如果加入了try ,except ,那么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
v1 = getattr (a1,“show”,none)() 等价与 a1.show()
1. 添加成员变量
setattr (a1,“b”,“值”)
2. 判断成员是否存在
v1 = hasattr (a1,“a”)
print (v1)
3. 删除成员
delattr (a1,“a”)
1. 导入模块
from importlib import import_module
2. 使用模块名
对象 = import_module("模块名称" )
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 的数据 根据互联网协议进行反解
1. 物理层:
与硬件相关 发送的是二进制数据 0 1001010 发电报
2. 数据链路层:
将数据进行分组,赋予2 进制数据意义
根据以太网协议:
1. 将一串电信号 为数据包
2. 数据报 分为:抱头 和包内容
包头规定 18 个字节: 前6 字节原地址mac 6 字节接受地址mac 6 字节数据描述
包内容:就是主要的传递内容
以太网的工作方式:广播方式 基于mac地址进行广播方式进行数据传入的
mac地址只能表示一个计算的地址在哪里
广播的方式:必须要在同一个局域网之内,但是局域网存在多个,那么怎么根据mac地址找到不同局域网的进行传递数据(网络层)
3. 网络层:
ip协议,每个机器配一个ip地址
也分为ip头部分和ip数据部分 固定字节
原地址 目标长度
ip地址就是:找到对应的子网地址(局域网)
ip 负责找 子网的位置
mac负责在子网中找到对相应的机器
4. 传输层:
tcp 与 utp 协议 基于端口协议
tcp与utp头 和 包数据
基于端口操作 0 -65535
一台机器中的端口就是一个软件
ip + 端口 就能表示全是键一个独一无二的软件
5. 应用层:
执行的硬用软件
应用软件自己规定的
应用层的软件头 + 数据
6. 数据链层
报头 + 数据(网络层 ip的头部 + 数据(传入层tcp 或者 utp 协议头 + 数据(硬用层头 + 数据)))
7. 物理层
将数据链层的数据转为电报(2 进制0 10101)
简单理解
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
server 服务端
import socket
sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
sock.bind(('127.0.0.1' ,8001 ))
while True :
data,(host,post) = sock.recvfrom(1024 )
print (data,host,post)
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 ))
data, (host, post) = client.recvfrom(1024 )
print (data.decode('utf-8' ))
break
client.close()
3.socket-tcp
server 服务端:
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.bind(('127.0.0.1' , 8000 ))
phone.listen(5 )
print ('等待链接中....' )
conn, client_add = phone.accept()
data = conn.recv(1024 )
print (data)
conn.send(b'1001' )
conn.close()
phone.close()
client 客户端
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1' , 8000 ))
phone.send(b'hahah' )
data = phone.recv(1024 )
print (data)
phone.close()
socket-tcp修复问题与循环操作
server 服务端
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 )
phone.bind(('127.0.0.1' , 8000 ))
phone.listen(5 )
print ('等待链接中....' )
conn, client_addr = phone.accept()
print (client_addr)
while True :
try :
data = conn.recv(1024 )
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
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1' , 8000 ))
while True :
msg = input ('>>:' ).strip()
if not msg:continue
phone.send(msg.encode('utf-8' ))
data = phone.recv(1024 )
print (data.decode('utf-8' ))
phone.close()
加上通信循环
client 客户端
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1' , 8000 ))
while True :
msg = input ('>>:' ).strip()
if not msg:continue
phone.send(msg.encode('utf-8' ))
data = phone.recv(1024 )
print (data.decode('utf-8' ))
phone.close()
server 服务端
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 )
phone.bind(('127.0.0.1' , 8000 ))
phone.listen(5 )
print ('等待链接中....' )
while True :
conn, client_addr = phone.accept()
print (client_addr)
while True :
try :
data = conn.recv(1024 )
print (data.decode('utf-8' ))
msg = input ('>>:' ).strip()
conn.send(msg.encode('utf-8' ))
except ConnectionResetError as e:
break
conn.close()
phone.close()
1 个1 个进行服务,不会断开服务,等待上一次客户端断开后,下一个客户端才会链接
模拟ssh远程执行命令-项目分析
******** 命令只是点了解 *******
import subprocess
obj = subprocess.Popen('dir C:\\Users\\56515' , shell=True , stdout=subprocess.PIPE, stderr=subprocess.PIPE)
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 )
phone.bind(('127.0.0.1' , 8000 ))
phone.listen(5 )
print ('等待链接中....' )
while True :
conn, client_addr = phone.accept()
print (client_addr)
while True :
try :
data = conn.recv(1024 )
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()
if not stdouts:
conn.send(stderrs)
conn.send(stdouts)
except ConnectionResetError as e:
break
conn.close()
phone.close()
******* 客户端 ********
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1' , 8000 ))
while True :
msg = input ('>>:' ).strip()
if not msg: continue
phone.send(msg.encode('utf-8' ))
data = phone.recv(1024 )
print (data.decode('gbk' ))
phone.close()
4.IO多路复用
服务器:当等待客户端链接时,处于阻塞状态
客户端:与服务器连接时,也是阻塞状态
如何避免阻塞:
1. 在创建服务端
加入:
服务器对象.setblocking(False )
2. 在客户端
加入:
客户端对象.setblocking(False )
IO多路复用:配合 非阻塞 一起使用的一种技术,当没有链接时,可以执行别的代码。
IO读哟路复用基于select模块进行使用
案例:
import select
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,]
while True :
r,w,e = select.select(inputs,[],[],0.5 )
for sock in r:
if sock = sock :
conn,addr = sock.accept()
print ("新链接" )
inputs.append(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([],[],[],监听时间)
2. select.poll([],[],[],监听时间)
3. select.epoll([],[],[],监听时间)
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()
serve = socketserver.ThreadingTCPServer(('127.0.0.1' , 8000 ), MyServer)
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
v1 = struct.pack("i" ,数字/数字变量)
print (v1)
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 )
data_length1 = struct.unpack("i" ,header1)[0 ]
has_recv_len = 0
date1 = b''
while True :
leng1 = data_length1 - has_recv_len
if leng1>1024 :
lth =1024
else :
lth = leng1
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 )
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))
sock.sendall(header1)
sock.sendall(data1)
data2 = '数据2' .encode('utf-8' )
header2 = struct.pack('i' ,len (data2))
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 )
phone.bind(('127.0.0.1' , 8000 ))
phone.listen(5 )
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 可以将数据变为字节类型
'''
使用struct 模块 将计算的长度进行打包(固定就是4个字节,不论数据多大都是4个字节)
打包
res = struct.pack('i',1024000) # res 4个字节的值,不论数字多大都是
解包
struct.unpack(i,res) # 返回元组类型(10244400,) 第一个元素就是
'''
size = len (stdouts) + len (stderrs)
header = struct.pack('i' ,size)
conn.send(header)
conn.send(stdouts)
conn.send(stderrs)
except ConnectionResetError as e:
break
conn.close()
phone.close()
******** 客户端 **********
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1' , 8000 ))
while True :
msg = input ('>>:' ).strip()
if not msg: continue
phone.send(msg.encode('utf-8' ))
import struct
header = phone.recv(4 )
size = struct.unpack('i' , header)[0 ]
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 )
phone.bind(('127.0.0.1' , 8000 ))
phone.listen(5 )
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))
conn.send(header)
conn.send(header_bytes)
conn.send(stdouts)
conn.send(stderrs)
except ConnectionResetError as e:
break
conn.close()
phone.close()
***** 客户端 ******
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1' , 8000 ))
while True :
msg = input ('>>:' ).strip()
if not msg: continue
phone.send(msg.encode('utf-8' ))
import struct
header = phone.recv(4 )
header_size = struct.unpack('i' , header)[0 ]
header_bytes = phone.recv(header_size)
import json
header_dict = json.loads(header_bytes)
print (header_dict)
size = header_dict.get('total_size' )
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
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1' , 8000 ))
dow_dir = '客户端下载的路径,要与打开的文件 拼接'
while True :
msg = input ('>>:' ).strip()
if not msg: continue
phone.send(msg.encode('utf-8' ))
import struct
header = phone.recv(4 )
header_size = struct.unpack('i' , header)[0 ]
header_bytes = phone.recv(header_size)
import json
header_dict = json.loads(header_bytes)
print (header_dict)
size = header_dict.get('filename_size' )
filename = header_dict.get('filename' )
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 = '服务端存放的文件夹 与 客户端发送的文件名要进行拼接'
phone.bind(('127.0.0.1' , 8000 ))
phone.listen(5 )
print ('等待链接中....' )
while True :
conn, client_addr = phone.accept()
print (client_addr)
while True :
try :
data = conn.recv(8096 )
print (data.decode('utf-8' ))
cmds = data.decode('utf-8' ).split()
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))
conn.send(header)
conn.send(header_bytes)
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.进程创建
import time
from multiprocessing import Process
def func (*args, **kwargs ):
print (args)
time.sleep(3 )
if __name__ == '__main__' :
p = Process(target=func, args=('子进程1' , '这是子进程1' ))
p.start()
print ('主' )
继承当前的进程类
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()
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
print ('子进程:' , n)
if __name__ == '__main__' :
p = Process(target=work)
p.start()
p.join()
print ('主进程:' , n)
主进程: 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())
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()
print (datetime.now())
证明多个进程进行执行时,会出现伪并发的状态
主进程的执行完毕的时间
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()
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())
print (q.empty())
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()
p = Process(target=func, args=(q,))
c = Process(target=show, args=(q,))
p.start()
c.start()
p.join()
q.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()
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
def func ():
print (123 )
if __name__ == '__main__' :
t1 = Thread(target=func)
t1.start()
print ('' )
当前存在多少个进程和线程
当启动py文件是,会生成一个 ide的子进程执行py文件
那么 子进程下面 必定有一个工作的 线程
同时又开启一个线程执行func任务
那么就是 1 进程 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()
print ('主' )
2.进程与线程的区别
1. 开进程的开销远大于线程的开销
2. 进程内容的多个线程 共享进程的地址空间
3. 关于pid内容
from threading import Thread
from multiprocessing import Process
def func ():
print ('123456' )
if __name__ == '__main__' :
p = Process(target=func)
p.start()
print ('主' )
线程打印顺序:
123456
主
进程打印顺序:
主
123456
线程的执行等同于 共同执行
进程的执行 先打印主,主进程先执行完毕,子进程可能正在申请创建索引 最后才会打印123456
线程创建,不需要在次开辟内存空间,使用当前的进程内的内存空间就可以
进程的创建,需要开辟进程空间,进程与进程之间是隔离的,所以需要单独申请内存空间
所以使用进程的开销比较大
n = 1000
def func ():
global n
n = 0
print (n)
if __name__ == '__main__' :
t = Thread(target=func)
t.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 ():
print (current_process().pid)
print (os.getppid())
if __name__ == '__main__' :
p = Process(target=func)
p.start()
print ('主进程id' ,current_process().pid)
3. 线程的归属的进程id 号 证明进程下的线程资源可以共享
def func ():
print (os.getpid())
if __name__ == '__main__' :
t = Thread(target=func)
t.start()
print ('主线程id' ,os.getpid())
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='线程名' )
t.setName('8899' )
t.start()
2. 主线程等待子线程执行完毕后子线程销毁
t.join()
3. 查看当前创建的线程是否存活
print (t.is_alive())
4. 查看当前进程下活跃的线程
print (active_count())
5. 查看活跃线程对象
print (enumerate ())
6. 查看主线程名字(当前进程下的非创建的自带的一个工作最小单位线程)修改主线程名字
print ('主线程' , currentThread().getName())
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.start()
print (123 )
6.线程互斥锁
牺牲了效率 保护的数据安全
执行原理:将并发改为串行
from threading import Thread,Lock
import time
n = 100
def func (r ):
global n
r.acquire()
temp = n
time.sleep(0.1 )
n = temp -1
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)
7.线程中Event的事件
from thrading import Event
even = Event()
even.wait()
even.set ()
even.is_set()
even.clear()
作用:
一个线程通知另一个线程,我的活干完了,你可以你自己的任务了
实现线程之间互通
from threading import Thread, Event
even = Event()
def s1 ():
'''模拟学生'''
print ('学生正在上课' )
even.wait()
print ('学生下课休息' )
def s2 ():
'''模拟老师'''
print ('老师正在上课' )
import time
time.sleep(5 )
even.set ()
print ('老师说下课' )
if __name__ == '__main__' :
t = Thread(target=s1)
t2 = Thread(target=s2)
t.start()
t2.start()
*******
线程的关键特性,就是当前线程是独立的状态运行且不可以预测的,如果程序中的其他线程需要通过某一个线程的状态来确定自己下一步的操作,就可以使用envnt事件
不使用envet事件也是可以的,设置全局变量,其他线程根据这个变量进行是否执行下一步操作
8.线程中的定时器功能
在定时的时间进行执行某些任务
from threading import Timer
def task (name ):
print (name)
Timer(5 , task, args=('11111' ,)).start()
*********** 60 秒设置验证码 **********
import random
from threading import Timer
class Code :
def __init__ (self ):
'''在初始化的时候,就会执行一次验证码,先获取'''
self.make_code()
def make_cache (self, interval=60 ):
self.cache = self.make_code()
print (self.cache)
self.t = Timer(interval, self.make_cache)
self.t.start()
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 )
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 )
q.get()
q.get(block=False ) 如果对列中没有值,那么就会抛出异常
q.get(block=False ,timeout=3 ) 对列中没有值,就会抛出异常 等待3 秒后
********** 堆栈 *******
q = queue.LifoQueue(3 )
q.put(11 )
q.put(11 )
q.put(11 )
q.get()
优先级的堆栈,可以设置优先级,级别高的先出来
q = queue.PriorityQueue
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代码进行对没有用的 到而占用的内存进行回收
解释器的代码是所有线程共享的,所有垃圾回收线程可能访问到解释器代码而去执行
问题:
对于同一个数据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 ():
'''
io密集型,也就是一直属于阻塞的状态,cup就会进行遇到阻塞io进行切换执行
速度比较快,另外多线程,不需要让操作系统申请内存空间,创建的速度也比较块
所以 io密集型还是比较适合使用多线程
'''
time.sleep(2 )
if __name__ == '__main__' :
l = []
state_time = time.time()
for i in range (400 ):
p = Process(target=work)
l.append(p)
p.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
r = RLock()
print ('加锁' )
r.acquire()
print ('在加个锁' )
r.acquire()
r.release()
********** 信号量 **************
信号量也是一把锁,可以指定信号量为5 ,对比与互斥锁同一时间执行一个线程使用,信号量同一时间可以有5 线程去拿这个把锁
比如:
互斥锁:如多个人在一个出租屋人,但是厕所只有一个,需要等待里面的人上完,其他的人才能取
信号量:相当于一群路人取公共厕所,公共厕所坑位多,这就一位这同一个时间可以有多个人上公共厕所,但是厕所的容纳是有限的,这就是信号量的大小
from threading import Thread,Semaphore, currentThread
import time
sm = Semaphore(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 )
for i in range (10 ):
p.submit(func,i)
p.shutdown()
print ('主' )
def func (name ):
print (currentThread().getName())
print (name,os.getpid())
time.sleep(1.5 )
if __name__ == '__main__' :
p = ThreadPoolExecutor(4 )
for i in range (10 ):
p.submit(func,i)
p.submit(函数名,参数1 ,参数2 )
p.shutdown()
print ('主' )
线程池与进程池获取 调用函数返回值
from concurrent.futures import ThreadPoolExecutor
def func ():
print (1 )
return 666
if __name__ == '__main__' :
t = ThreadPoolExecutor(5 )
m = t.submit(func).result()
print (m)
******** 线程池与进程池的异步调用和回调机制 ************
import random
from concurrent.futures import ThreadPoolExecutor
def func ():
print ('开始执行' )
return random.randint(1 , 10 ),666
def show (val ):
print (val.result())
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 :
conn, addr = server.accept()
t = t.submit(commit,conn)
server.close()
if __name__ == '__main__' :
t = ThreadPoolExecutor(5 )
servers()
2.进程池
from concurrent.futures import ProcessPoolExecutor
def func ():
pass
return 123
def doo (return ):
pass
if __name__ == "__main__" :
p = ProcessPoolExecutor(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 )
pool.submit(函数名,传入函数参数1 ,参数2. ....)
2. 线程池案例:
from concurrent.futures import ThreadPoolExecutor
pool = ThreadPoolExecutor(10 )
def func ():
pass
if i in range (100 )
pool.submit(func)
print ('123' )
线程池的执行流程:
循环100 执行函数func,线程池只有10 个,当线程执行完毕后回到线程池等待线程池安排任务。
3. 线程池的方法
1. 等待线程池执行完毕 主线程才会执行
pool.shutdown(True )
2. 回调方法
1. 创建一个线程池对象
特殊对象 = pool.submit(func,参数1 )
2. 利用线程池对象调用.add_done_callback(函数名)方法
特殊对象.add_done_callback(函数名)
回调方法案例:
from concurrent.futures import ThreadPoolExecutor
pool = ThreadPoolExecutor(10 )
def func ():
pass
def show ():
pass
if i in range (100 )
p = pool.submit(func)
p.add_done_callback(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()
******* gevent模块的提交任务 *******
import gevent
from gevent import monkey;monkey.patch_all()
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' )
g1.join()
g2.join()
gevent.joinall([g1,g2])
计算密集型使用的意义不大,只有在遇到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()
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)
server.close()
if __name__ == '__main__' :
g = spawn(servers)
g.join()
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()等待链接操作),没来一个链接 起一个线程进行处理新的链接,缺点:随着客户端链接增多,那么线程就会越来越多,会出现机器抗住不,
开线程池 固定当前线程的多少,问题规模大,线程池也会降低效率
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 )
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 :
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)
for conn in del_rlist:
rlist.remove(conn)
server.close()
缺点:
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 )
server.setblocking(False )
rlist = [server]
wlist = []
wdata = {}
while True :
rl, wl, xl = select.select(rlist, wlist, [], 0.5 )
print (rl, wl, xl)
for sock in rl:
if sock == server:
conn, attr = sock.accept()
rlist.append(conn)
else :
try :
data = sock.recv(1024 )
wlist.append(sock)
wdata[sock] = data.upper()
except Exception:
sock.close()
rlist.remove(sock)
for sock in wl:
data = wdata[sock]
sock.send(data)
wl.remove(sock)
wdata.pop(sock)
server.close()
缺点:
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
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
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
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" )
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
self.os = os
self.shell = shell
def phone (self ):
print ('手机组成部分' )
self.cpu.cpu()
self.os.os()
self.shell.shell()
def make_phon (factory ):
cpu = factory.factory_cpu()
os = factory.factory_os()
shell = factory.factory_shell()
return Client(cpu,os,shell)
p = make_phon(Millet())
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
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)
print (m)
'''
创造者模式分为4个部分
1.导演类:
导演类起到封装的作用,避免高层模块深入到建造者内部的实现类。在建造者模式比较庞大时,导演类可以有多个。
2.抽象的创造者
主要作用:负责将需要创建的方法进行声明
3.具体实现的创造者
主要作用:实现抽象创造者类中的全部方法
4.客户端/控制类
指挥具体实现创造者对象创建 产品
'''
单例模式1
单例模式:
保证一个类只有一个实例,并提供一个访问他的全局访问点
角色:单利
优点:
对唯一实例的受控访问
单例相当于一个全局变量,防止命名空间的污染
python的模块就是一个单例模式
单利模式的书写:
class Singleton :
instance = None
def __new__ (cls, *args, **kwargs ):
if cls.instance:
return cls.instance
else :
cls.instance = object .__new__(cls)
return cls.instance
单例模式的求证案例:
class Singleton :
instance = None
def __new__ (cls, *args, **kwargs ):
if cls.instance:
return cls.instance
else :
cls.instance = object .__new__(cls)
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 ):
if cls.instance:
return cls.instance
with cls.lock:
if cls.instance:
return cls.instance
cls.instance = object .__new__(cls)
return cls.instance
基于模块导入的单例模式:
1. 第一个py文件
class 类名 (object ):
def __init__ (self ):
pass
实例对象a1 = 类名()
2. 第二个py文件
from xxx import 实例对象a1
实例对象a1.方法
当进行导入时,实例化对象a1就会存储在内存中,无论导入多少次,都是从内存中进行调用。实现模块级单例模式
创建模式总结
抽象工厂模式和创建工厂模式相对于简单工厂模式和工厂方法而言更为简单和灵活也更为复杂
通常情况下,设计简单工厂模式或者工厂方法模式开始,当发现设计需要更为灵活性时,则向更浮在的设计模式演化。
结构型模式
适配器模式
结构型模式:几个类组成一个什么结构
适配器模式:原本由于接口不兼容而不能一起工作的哪些类可以一起工作
实现方式:
1. 类适配器:使用多继承/利用继承原理
2. 对象适配器:使用组合/利用多态
适配器的目的:保持接口一直,复用之前的源代码
是将A类的对象,放入b类中
class A :
pass
class B :
def ——init——(self,a):
self.a = a()
def dunc (self ):
self.a.xxx()
角色:
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 ):
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 ):
self.color.paint(self)
class Red (Color ):
def paint (self, shape ):
print ('红颜色{}' .format (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)
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 ):
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 ):
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 ):
if not self.subj:
self.subj = RealSubject(self.filename)
return self.subj.get_content()
def set_content (self, content ):
if not self.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
class Manager (Abstract ):
def fake (self, day ):
if day <= 10 :
print ("经理批假{}" .format (day))
else :
print ("经理不批" )
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)
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 )
观察者模式(用的比较多/发布订阅模式)
用的比较多
内容:
定义对象键的一种一对多依赖关系,当对象的状态发生变化,所有依赖的对象都会被通知并且被更新。
观察者模式:发布订阅模式
角色:
抽象主题
具体主题 -- 发布者
抽象观察者
具体观察者 -- 订阅者
使用场景:
当抽象模型有两个方面,其中一个方面依赖另一个方面,将这两个封装在一个独立对象中,以可以独立改变和复用
当对一个对象的改变需要同时改变其他对象,而不知道具体改变多少
当一个对象需要通知其他对象,而他又不能嘉定其他对象是谁
有点:
耦合小
支持支持广播通信
实例代码
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__()
self.company = company
@property
def company_info (self ):
return self.__company_info
@company_info.setter
def company_info (self, info ):
self.__company_info = info
self.notify()
class Staff (Observer ):
def __init__ (self ):
self.company_info = None
def update (self, notice ):
self.company_info = notice.company_info
n = StaffNotice('公司123' )
s1 = Staff()
n.attach(s1)
n.company_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()
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()
print ('func1-第2次打印' )
gr2.switch()
def func2 ():
print ('func2-第1次打印' )
gr1.switch()
print ('func2-第2次打印' )
gr1 = greenlet(func1)
gr2 = greenlet(func2)
gr1.switch()
yield关键字实现
def func1 ():
yield 1
yield from 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 )
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
async def func1 ():
print (1 )
await asyncio.sleep(1 )
async def func2 ():
print (2 )
await asyncio.sleep(2 )
async def main ():
print ('开始秒数' + time.strftime('%S' ))
coroutine_list = [func1(), func2()]
await asyncio.gather(*coroutine_list)
print ('结束秒数' + time.strftime('%S' ))
if __name__ == '__main__' :
asyncio.run(main())
当前的优势:
遇到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' ))
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:
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)
print ('结束秒数' + time.strftime('%S' ))
if __name__ == '__main__' :
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
异步编程
事件循环'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 函数名 :
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())
内部帮助我们创建获取事件循环,并让事件循环执行传入的函数,当前run()作为一个异步编程的入口进行操作
run 的内部还是完成了原来版本的操作 执行创建或者获取event loop 将任务执行
异步函数一定要不事件循环进行一起使用,没有事件循环异步函数是没办法执行的
协程函数的使用(await关键字)
await 最重要的最用: 会将协程对象包装为一个task任务,告知event loop。
import asyncio
案例1 :
async def func ():
print ('123456' ) 1. 先执行当前打印
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())
案例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()
print (res2)
asyncio.run(func())
只有第一个await 协程对象(将这对象包装为一个event loop中得一个任务)执行完毕后,并且获取执行结果后,才会执行下一个协程函数
原因: 因为第二个协程对象并未被await 包装为任务,告知event loop。代码是从上而下的执行,并没有执行到第二个await
Task对象
task作用:
1. 帮助我们在事件循环(event loop)中添加多个任务
例如:
任务列表 = [task1,task2,task3]
当某个任务出现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 秒]
async def show (num ):
print ('show函数正在执行...%s' %num)
await asyncio.sleep(2 )
print ('show函数执行完毕...%s' %num)
return 'ok'
async def func ():
print ('执行func函数' )
task1 = asyncio.create_task(show(1 ))
task2 = asyncio.create_task(show(2 ))
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
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 = []
for i in range (5 ):
task = asyncio.create_task(show(i))
task_list.append(task)
res = await asyncio.gather(*task_list)
print (res)
done,pending = await asyncio.wait(task_list,timeout=1 )
print (done)
print (pending)
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 :
async def func ():
loop = asyncio.get_event_loop()
print (loop)
fut = loop.create_future()
await fut
asyncio.run(func())
案例2
async def func1 (fut ):
await asyncio.sleep(2 )
fut.set_result('666' )
async def func2 ():
loop = asyncio.get_event_loop()
fut = loop.create_future()
await loop.create_task(func1(fut))
data = await fut
print (data)
asyncio.run(func2())
await asyncio.create_task(函数对象) 创建任务,任务完成后自动执行set_result进行设置返回值 等待的状态不会等待
coucurrent.futures.future对象
使用线程池与进程池实现异步函数时使用的对象与asynio没有关系
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 )
for i in range (10 ):
fut = pool.submit(func,i)
print (fut)
2. 使用场景:
项目中进行异步时80 %为异步编程,但是20 %使用项目使用的模块不支持异步编程可以使用当前方式
def func1 ():
print ('我是一个普通函数' )
return '666'
async def main ():
loop = asyncio.get_event_loop()
fut = loop.run_in_executor(None , func1)
res = await fut
print (res)
with ThreadPoolExecutor() as pool:
res = await loop.run_in_executor(pool, func1)
print (res)
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
async def download_img (url ):
print ('开始下载图片' , url)
loop = asyncio.get_event_loop()
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
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 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
2. 需要将默认事件循环替换为 uvloop
import asyncio
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
asyncio.run(..)
框架内部使用了asgi -> uviorn(内部使用了unloop)
案例
异步Redis操作
假设:
后端服务 A服务器
redis数据库 B服务器
那么后端代码与redis进行交互式的时候,就会产生网络请求(网络io)链接/操作/断开都是网络io请求。
1. 安装
pip install aioredis
2. 使用
import asyncio
import aioredis
'''
aioredis模块基础了原来的redis的类使用方式是相同
'''
async def execute (address, password=None ):
print ('开始执行链接操作' , address)
redis = await aioredis.Redis(host='127.0.0.1' , port=6379 , db=0 , decode_responses=True , encoding='utf-8' )
redis1 = await aioredis.from_url(address, encoding="utf-8" , decode_responses=True )
await redis.set ('66789' , 'wkx' )
await redis1.set ('8899' , 'wkx' )
key = await redis.get('66789' )
print (key)
key1 = await redis1.get('8899' )
print (key1)
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
1. 安装
pip install aiomysql
2. 普通的mysql链接方式
import pymysql
pymysql.install_as_MySQLdb()
serve = pymysql.connect(host='127.0.0.1' ,port=3306 ,user='root' ,password='123456' ,db='db21' )
conn = serve.cursor()
sql = conn.execute('show tables' )
data = conn.fetchall()
print (data)
conn.close()
serve.close()
3. 使用aiomysql链接与普通的方式相同
内部处理语法相同的
import asyncio
import aiomysql
async def execute ():
conn = await aiomysql.connect(host='127.0.0.1' , port=3306 , user='root' , password='123456' , db='db21' )
cur = await conn.cursor()
await cur.execute('show tables' )
res = await cur.fetchall()
print (res)
await cur.close()
conn.close()
asyncio.run(execute())
异步mongodb操作
普通的链接方式:
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框架(为例)
性能比较高的框架(使用的事件循环: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('测试13:app' )
3. 异步使用
import asyncio
import aioredis
import uvicorn
from fastapi import FastAPI
app = FastAPI()
conn = aioredis.from_url('redis://127.0.0.1:6379' ,max_connections=10 )
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:
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)
if __name__ == '__main__' :
asyncio.run(main())
asyncio模块
asyncio
1. 单进程单线程的程序,不能提高程序的运算速度
2. 作用: 比较适合处理等待的任务,网络通信,不存在系统级的上下文切换
3. async 分为: coroutine function(协程函数) coroutine object (协程对象)
例如:
import asyncio [3.7 版本以上]
async def main ():
print ('hello' )
await asyncio.sleep(2 )
print ('world' )
coro = main()
print (coro)
如果想启动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 ():
task1 = asyncio.create_task(say_after(1 , 'hello' ))
task2 = asyncio.create_task(say_after(2 , 'word' ))
print (f'当前时间{time.strftime("%X" )} ' )
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)
print (f'结束时间{time.strftime("%X" )} ' )
asyncio.run(main())
4. gather的另一个好处:将全部的协程对象包装为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)
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__)
show.py
import test
在test.py文件中打印 print (__name__)
当模块被导入时,模块名称是文件名;而当模块作为脚本独立运行时,名称为 __main__。
让模块既可以导入又可以执行
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义