Akemi

Python学习笔记

2024/02/06

如果出现无法载图的情况,请检查与github的连通性

Anaconda开发环境介绍与搭建

Anaconda是一个软件集合,自带了很多的模块的二进制文件(可以直接本地构建模块),并提供了conda和pip两个包管理工具。能够提供python解释器、模块与虚拟环境的管理

模块 —— 实现具体功能的工具包

比如想实现下载、上传功能,存储功能,可以寻找现成的网络的、数据库的模块可以来调用
(1)标准模块——python的开发者编写的模块
(2)第三方模块

虚拟环境 —— 给开发环境做用途上的区分

通过划分多个开发环境,为不同的环境设置不同版本的解释器和模块,比如区分python2与3的环境
比如做web开发、爬虫开发、图形化开发、科学计算和数据分析、机器学习与深度学习
图片

pip和conda包管理工具的对比:

(1)软件仓库的区别
conda使用anacconda.org软件仓库
pip使用pypi,python官网的软件仓库,模块数量多

(2)模块内容
conda都是二进制,可以直接读取
pip源码和二进制都有,源码需要编译会比较慢

(3)支持的语言
conda支持多语言
pip只支持python

(4)多环境
conda支持多环境
pip不支持

(5)依赖的检查
conda严格检查依赖项——类似于yum的区别
pip不严格检查

anaconda的conda与pip命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
1、一般操作
conda -version #查看anaconda版本
conda update conda #升级conda版本
conda update pip #升级pip版本
conda update anaconda #升级anaconda版本与所有软件包
conda env list #查看所有虚拟环境

在linux和macos中
source activate xx #进入xx环境
source deactivate #退出
在windows中
activate xx
deactivate

2、创建虚拟环境
conda create --name/-n 新环境名 #创建新环境
conda create -n 新环境名 -c 老环境名 #克隆出一个新环境
conda remove -n 环境名 --all #完整删除

3、操作模块
conda/pip install -n 环境名 模块1,模块2,模块3 #在环境中安装模块
(base)conda install 模块1,模块2,模块3 #在当前环境安装
conda list -n 环境名 #查看所有模块
conda uninstall 模块名 #只能删除conda安装的模块,pip安装的就用pip删除
conda install -n 环境名 python=3.7 #安装python,默认安装latest版本
conda create -n demo python=3.7 requests #在创建环境的同时安装pyhton解释器和requests模块

windows安装anaconda与pycharm

在清华大学开源镜像站-anaconda-miniconda找到安装包进行下载最新版本
图片

安装过程中选择添加到环境变量,以及作为默认解释器,反正全勾上了
安装完成后,打开cmd,使用conda -V查看不到信息,通过开始菜单的按钮打开cli界面可以用
创建一个名为demo的虚拟环境
conda create -n demo python=3.7
图片

直接默认安装pycharm
安装完成后,将虚拟环境导入到pycharm去进行编辑
图片
图片

创建新项目,选择conda环境——demo环境——选择创建
图片

标识符与数据类型

python程序的构成

Python程序是由模块组成的。每个模块都对应一个py源文件

Python是一种强制使用缩进的语言,1个tab制表符或4个空格代表一个缩进

书写中可以使用行连接符\

Python官网建议使用PEP-8代码风格https://www.python.org/dev/peps/pep-0008/

注释

注释方法:
(1)井号
(2)连续的引号
“”” “””
‘’’ ‘’’

解释器位置特殊注释:
#!/usr/bin/python3
#coding:utf-8 指定编码格式
#--coding:utf8--

标识符定义规则

标识符规则:用于变量、函数、类、模块等的名称
由数字、字母、下划线组成
不能由数字开头
严格区分大小写
不能用系统内置关键字

驼峰命名法:
从第二个单词开始首字母大写

Python标识符命名规则

开发中,我们通常约定俗称遵守如下规则

关键字和内置函数

都是python开发者定义封装好嗷的,具有特殊意义
关键字会使用特殊颜色标注(编辑器会特别标注出来,如def,if,for,and,都是来完成特定操作的)

变量

赋值:
变量名 = 表达式

其本质为:
a=3 会生成对象,并且把这个对象的地址给a

变量删除与垃圾回收机制

通过del可以删除变量,del a 代表将栈内存中的a删除
与此同时堆内存中的相应对象也会被垃圾回收

链式赋值

用于同一个对象赋予多个变量

x=y=500

解包赋值

a,b,c = 10,20,30

a,b = b,a #互换

数据类型

Python是动态类型的语言,不需要显示声明类型。
变量是没有类型的,都是引用的对象。
对象有类型

整形int
浮点形float
布尔形
字符串
列表
元组
字典
集合

1
2
3
4
5
#整数可以使用下划线作为分隔符
var=100_0000

#浮点型可以使用科学计数法
var=1.5e5 #表示1.5×10^5

整型int

在Python2中,int是32位,long类型64位
在Python3中,int可以存储任意大小整数

1
2
3
4
5
6
7
8
a = 0b10101010
b = 0o123
c = 0x8e
d = int("123") #字符串转为整形,123
e = int(10.5) #10
f = 10.0 + 8
type(f) #浮点型
print(a,b,c,d)

浮点型float

用科学计数法表示

1
2
3
4
5
6
7
8
9
a = 3.14
b = 352e-3
c = 413.02
d = round(c) #自动四舍五入,并且产生新的值

float()#类型转换

y += x*2 # y = y + x*2
y *= x + 2 # y = y*(x + 2)

布尔值

在Python中,将true和false定义成了关键字。true其实就是1,false是0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
a = true
b = 5
print(a+b)

if(""):
print("true")
else
print("false")

bool的空字符串——> false
bool的空列表——> false
boolNone——> false
bool0 ——> false
只有bool("False"),结果是true

运算符

基本运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#算数运算符
//表示整除,不会产生小数
**表示乘方
var=64 ** (1/2) #也可以用来做开方
%表示取余

#算数运算符与赋值结合使用
x+=y # x=x+y
x-=y # x=x-y
x*=y # x=x*y
x/=y # x=x/y
x//=y # x=x//y
x%=y # x=x%y
x**=y # x=x**y

divmod0()方法-同时得到商和余数

运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#身份运算符
print(a is b) #如果两者相同,则输出true
not is #如果两者相同,则输出false

#比较运算符,返回布尔值
!= #不相等
== #相等,除了数值之外,还要比较数据类型
其本质为使用__eq__方法

#逻辑运算符
and #两个都为真才为真
or #只要有一个真即为真
not #将判断的结果取反
in #判断对象在不在另一个对象中

#算数符优先级,从高到低为
()
乘方
乘除
加减
比较运算符
in
not
and
or

#转义字符\,用法与linux中相同
var="ws is \"ws\""

#成员运算符
a="asd"
b="asdqwe"
print(a in b)
#True

位运算符

1
2
3
4
5
6
7
8
9
10
位运算
a = 0b000110
b = 0b011010
bin()转换为二进制
print(bin(a&b)) #与 0b000010
print(bin(a|b)) #或 0b011110
print(bin(a^b)) #异或 相异时为1 0b011100
print(bin(a~)) #按位进行取反

print(bin())s

整数缓存问题

命令行模式下,python仅对比较小的整数进行对象缓存,范围为-5到256

文件模式下,所有数字都会被缓存

-5到256依然底层用数组实现,除此之外的数缓存到链表中

1
2
3
4
5
6
7
8
a=5
b=5
print(a is b)
#True
a=267
b=267
print(a is b)
#True

字符串

在python中,单字符也是字符串类型

python中的字符串是不可变的

编码

python3直接支持字符集Unicode,可以表示所有字符

python3字符默认为16位的Unicode编码,ASCII码是Unicode的子集

使用内置函数ord()可以把字符转换成对应的Unicode码;
使用内置函数chr()可以把十进制数字转换成对应的字符。

1
2
3
4
print(ord("王"))
# 29579
print(ord("盛"))
30427

字符串创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#长字符串,可以使用三个"""与'''
#长字符串中对特殊符号比如正则表达式 不需要进行转义
#"""与'''都是带换行符\n的
var="""
123455
'12345'
"""

#从控制台 接受用户输入的函数input
input('提示信息')
#默认接受的是字符串类型,如果需要处理就要变化类型

#不换行打印
print("123",end='')
print("123")

字符串的拼接

1
2
3
4
5
#字符串的运算与拼接
var="1"
print(var * 20) #字符串相乘
print(var1 + var2) #字符串连接
print("123"+"12345")

转义字符

实现某些难以用字符表示的效果。比如:换行等。常见的转义字符有这些:

转义字符 描述
(在行尾时) 续行符
\ 反斜杠符号
单引号
双引号
\b 退格(Backspace)
\n 换行
\t 横向制表符
\r 回车

字符串操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#字符串替换
字符串是不能变的,所以只能创建一个新对象,将变量指向新的对象
print(var.replace('老字符','新字符','替换次数,-1为全部替换')) #查找与替换
a = '123'
print(a.replace('1','王'))
# 王23

#字符串判断
var="qwe poi"
print(var.startswith('ab')) #判断是否是以ab开头(必须完全匹配),返回布尔值
print(var.endswith('e')) #判断是否是以e结尾,返回布尔值

#字符串修改
var="QwErT y sTj"
print(var.capitalize()) #首字母大写
print(var.title()) #每个单词都变成首字母大写
print(var.lower()) #全部小写
print(var.upper()) #全部大写

print(var.strip()) #删除首尾空格
print(var.lstrip()) #删除首空格
print(var.rstrip()) #删除尾空格
print(var.strip('y')) #删除收尾特定内容y

var.startswith('xx') #是否以字符串开头 返回布尔值
var.endswith('xx') #是否以字符串结尾 返回布尔值

#字符串索引
在字符串中每个字符都有一个索引,第一位索引是0
索引超出也不会报错,与序列的切片一致
var="qwertyu"
print(var[2]) #输出e
print(var[0:7]) #截取索引号0-6的字符
print(var[-4:-1]) #截取最后第四位到最后第二位
print(var[-4:]) #截取最后第四位
print(var[-4:-0:2]) #步长2
print(var[::-1]) #倒叙排列

#字符串查找
var="qwe poi"
print(var.find('poi')) #查找poi这个字符串,返回起始位的索引号,返回4;未找到则返回-1
print(var.index('poi')) #查找poi这个字符串,返回起始位的索引号,返回4;未找到则报异常

#字符串切割,按索引进行切片
var='qw er ty ui'
var.split() 默认参数为
print(var.split()) #默认使用空格进行切割,切割的内容会放到列表内进行返回
print(var.split('r'))#会把r本身消掉,然后把剩下两个部分放到列表内
#['qw', 'er', 'ty', 'ui']
#['qw e', ' ty ui']

#join
#将序列重新合并
print(''.join(var.split()))
print('*'.join(var.split()))
#qwertyui
#qw*er*ty*ui

现在有两种方法可以输出同一字符串到字符串中
1.使用+=的方法
a =+ "123"

2."123"添加到一个列表中
然后用join将列表合并成str

1方法的效率需要重复生成字符串,效率很低。要用2

#字符串排版
var="王盛"
print(var.center(10))
print(var.center(20,'*'))
print(var.ljust(10)) #右侧有10字符
print(var.rjust(10)) #左侧有10字符
# 王盛
#*********王盛*********
#王盛
# 王盛

#特征判断
isalnum() 是否为字母或数字
isalpha() 是否只有字母或汉字
isdigit() 是否只有数字
isspace() 是否为空白符
isupper() 是否为大写字母
islower() 是否为小写字母

可变字符串

io.StringIO() 是一个在内存中操作字符串数据的工具,它允许你像文件一样读写字符串,但它是可变

1
2
3
4
5
6
7
8
9
10
import io
a = 'adwqewq'
aio = io.StringIO(a) #可变字符串
print(aio) #_io.StringIO类型的值
print(aio.getvalue())
# adwqewq
aio.seek(3) #移动指针到3这个位置
aio.write("***")
print(aio.getvalue())
# adw***q

字符串驻留机制

双引号引起来的字符串,如果相同则只保存一份,被认为是同一个对象,ID也相同

字符串的格式化format

1
2
3
4
5
6
7
8
9
10
11
12
13
#字符串引入进行定义
a = "名字:{0},年龄:{1}"
b = a.format("ws",'25')
c = a.format("xhy",'24')
print(b)
print(c)
#名字:ws,年龄:25
#名字:xhy,年龄:24

d = "名字 {name},年龄 {age}" #指定参数的
print(d.format(name='ws', age=24))
#名字 ws,年龄 24

数字格式化

1
2
3
a = "我是{0},工资{1:.2f}"
print(a.format('ws',0.05))
#我是ws,工资0.05

类型转换

类型转换
int(x [,base]) 将x转换为一个整数
long(x [,base] ) 将x转换为一个长整数
float(x) 将x转换到一个浮点数
complex(real[,imag]) 创建一个复数
str(x) 将对象 x 转换为字符串
repr(x) 将对象 x 转换为表达式字符串
eval(str) 用来计算在字符串中的有效Python表达式,并返回一个对象
Complex(A) 将参数转换为复数型
tuple(s) 将序列 s 转换为一个元组
list(s) 将序列 s 转换为一个列表
set(s) 转换为可变集合
dict(d) 创建一个字典。d 必须是一个序列 (key,value)元组
frozenset(s) 转换为不可变集合
chr(x) 将一个整数转换为一个字符
unichr(x) 将一个整数转换为Unicode字符
ord(x) 将一个字符转换为它的整数值
hex(x) 将一个整数转换为一个十六进制字符串
oct(x) 将一个整数转换为一个八进制字符串

序列

常见的序列有:列表、字典、元组、集合、双端队列、区间
序列的本质是一块存放多个值的连续内存空间
在序列中,存储的是标识的地址。值在对应的标识中
比如a = [10,20,30,40],a存放的是标识的地址

列表

列表是一种常用的数据结构,可以容纳各种类型的数据

列表是内置可变序列,包含多个元素的有序连续内存空间,并且可以根据需要随时增大和缩小

列表的创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#
list1=[]
list2=list() #python的一个内置函数,可以直接使用
print(list(range(10)))
#[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

推导式生成列表
a = [x*2 for x in range(3)]
range(3)=[0,1,2]
a = [0,2,4]
b = [x*2 for x in range(50) if x%9==0] #对9可以整除的数*2

#range 创建整数列表
range(2,20,4) #开始2结束20,步长4
需要使用list()使其变成列表

#列表中的元素可以是任意的数据类型(也可以嵌套其他的数据结构)
list1=[1,'2354',False,[1,2]]

print(type(list1))
#列表的数据类型为list

#内置的转换函数list
tuple1=(1,2,3)
print(type(tuple1)) #输出tuple
tuple1= list(tuple1)
print(type(tuple1)) #输出的不是tuple而是list

列表操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#列表的加法
list1=[1,2,3,4,5,6]
list2=[1,2,3]
print(list1 + list2)
#[1, 2, 3, 4, 5, 6, 1, 2, 3],将两个列表进行了组合,次数生成一个新的列表
list1.extend(list2)
print(list1)
#[1, 2, 3, 4, 5, 6, 1, 2, 3] 在原列表修改

#列表的乘法,扩展列表
print(list2 * 2)
#输出[1, 2, 3, 1, 2, 3],只能乘数字

#列表添加元素append
list1=[1,2,3,4]
list1.append('12345sq') #一次只能传递一个元素,在尾部添加
print(list1)
#[1, 2, 3, 4, '12345sq']
list1.append([1,2]) #传递进去一个列表
print(list1)
#[1, 2, 3, 4, '12345sq', [1, 2]]

#列表插入元素insert
list1=[1,2,3,4]
list1.insert(0,'wqe') #在索引0的位置前插入元素,也只能一次传递一个元素
print(list1)
#['wqe', 1, 2, 3, 4]
插入和删除都需要改变元素的位置,会影响性能

#列表中删除元素remove,pop,del
list1=[1,2,3,4]
list1.remove(4) #要删除的元素
remove不存在会报错
print(list1)
#[1,2,3]
list1.pop() #弹出末尾的一个元素,但pop会把这个元素返回
var=list1.pop() #把最后一位‘2’,弹出并赋值到var
print(var)
#2
del list[0] #删除索引的元素
#del 是一个关键字,而不是和remove、pop一样是一种方法

#列表修改和查看元素
list1=[1,2,3,4,5]
list1[0]='ws'
print(list1)
#[ws,2,3,4,5]

#列表修改和查看元素——索引叠加
list1=[1,2,3,4,5,['ws','qwe',['ert','uty']]]
print(list1[5][2][1])
#uty

#列表的元素排序——reverse倒叙排序
list1=[1,2,3,4,5]
list1.reverse()
print(list1)
#[5, 4, 3, 2, 1]

#列表的元素排序——sort可自定义排序
list1=['qwe','asdc','szcqqq','aaaaaaa','a']
#参数reverse=Ture代表倒序排序,False为正序排序
#参数key作为排序依据
list1.sort(key=len,reverse=False) #key=len说明以字符以多到少排序
print(list1)
#['a', 'qwe', 'asdc', 'szcqqq', 'aaaaaaa']

#reversed内置函数
返回一个迭代器对象,并且只能使用一次
a = [10,50,30,60
b = reversed(a)
c = list(b)
print(c)

#随机排序
import random
list1=[1,2,3,4,5,6,1]
random.shuffle(list1)
print(list1)
#[2, 1, 6, 4, 5, 3, 1]

列表遍历len max min

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
list1=[1,2,3,4,5]
list2=['10','20','30','40','50']
str1='ABCDE edcba'

#len函数 返回字符串长度
print(len(list1)) #输出5
print(len(list2)) #输出5
print(len(str1)) #输出11

#max函数 返回ASCII码值最大的
print(max(list1)) #输出5
print(max(list2)) #输出5,对字符串处理时逐个处理
print(max(str1)) #输出e

#min函数 返回ASCII码值最小的,同上

#sum求和
只针对数值型列表
print(sum(a))

统计次数、指定元素:count、index方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#count——统计元素出现的次数
list1=[1,2,3,4,5,6,1]
print(list1.count(1))
#2

#index——统计元素的位置
list1=[1,2,3,4,5,6,1]
print(list1.index(4)) #返回元素第一次出现的索引位置
#3

list1=[1,2,3,[4,5],6,1]
print(list1.index(5)) #无法搜索嵌套列表的值
#报错
list1=[1,2,3,['4',5],6,1]
print(list1.index('4')) #无法搜索嵌套列表的值
#报错
list1=[1,2,3,['4',5],6,1]
print(list1[3].index('4')) #通过指定列表所在位置
#0

列表切片

与字符串的切片几乎相同

1
2
3
4
5
6
7
8
9
#列表的索引,与字符串的索引相同
list1=[1,2,3,4,5,6]
print(list1[3]) #查看list索引位3的元素,输出4
print(list1[0:4]) #查看索引位0开始,索引位3结束(不包含4)
print(list1[1:]) #查看索引位1开始,输出到最后
print(list1[:4]) #查看索引0开始到第三位
print(list1[1:-1]) #查看索引1开始到倒数第二位
#带有步长的查询
print(list1[1:5:2]) #查看索引1开始到索引5,步长为2,输出结果为2,4,6

字典

通过键值对key=value的形式保存元素的一种数据结构
键是不可变数据,列表、字典、集合这种可变对象不能作为键
键不可重复
值任意,可重复

字典的创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#创建字典
dict1={'ws':123,'qwe':'weq','王':'sheng'} #字典的key不允许相同、且key必须为常量
print(type(dict1)) #<class 'dict'>

dict1=dict([['qwe','ewq'],['asd','dsa']])
dict2=dict(name='ws', age='25', job=None)
print(dict1)
#{'qwe':'ewq', 'asd':'dsa'}
print(dict2)
#{'name': 'ws', 'age': '25', 'job': None}

#通过zip创建字典
k=['name','age','居住地']
v=['ws','25','sx']

d = dict(zip(k,v))
print(d)
#{'name': 'ws', 'age': '25', '居住地': 'sx'}

#fromkeys方法引入
a=dict.fromkeys(['name', 'age', 'job'])
print(a)
#{'name': None, 'age': None, 'job': None}

#

字典的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#字典通过key查找value
dict1={'ws':123,'qwe':'weq','王':'sheng'}
print(dict1['ws'])
#123
print(dict1['NA'])
#报错

#字典通过get方法查找value 不会报错
print(dict1.get('NA'))
#None

#在字典中添加元素——直接赋值新的key
dict1={'ws':123,'qwe':'weq','王':'sheng'}
dict1['ads']='qcvx'
print(dict1)
#{'ws': 123, 'qwe': 'weq', '王': 'sheng', 'ads': 'qcvx'}

#在字典中添加元素——setdefault传参
dict1={'ws':123,'qwe':'weq','王':'sheng'}
dict1.setdefault('ads','wqw') #前面为key,后面为value,如果没有value则会记为空
print(dict1)
#{'ws': 123, 'qwe': 'weq', '王': 'sheng', 'ads': 'wqw'}
#{'ws': 123, 'qwe': 'weq', '王': 'sheng', 'ads': None}

#在字典中删除元素——del、pop
dict1={'ws':123,'qwe':'weq','王':'sheng'}
del dict1['ws']
print(dict1)
#{'qwe': 'weq', '王': 'sheng'}
dict1={'ws':123,'qwe':'weq','王':'sheng'}
var=dict1.pop('ws')
print(var)
#123
print(dict1)
#{'qwe': 'weq', '王': 'sheng'}

#字典中修改value——重新赋值、update
dict1={'ws':123,'qwe':'weq','王':'sheng'}
dict1['ws']='ws'
print(dict1)
#{'ws': 'ws', 'qwe': 'weq', '王': 'sheng'}
dict1.update({'ws':'wsc'})
print(dict1)
#{'ws': 'wsc', 'qwe': 'weq', '王': 'sheng'}

#获取字典的key、value与键值对——keys(),values(),items()
dict1={'ws':123,'qwe':'weq','王':'sheng'}
print(dict1.keys())
print(dict1.values())
print(dict1.items())
#dict_keys(['ws', 'qwe', '王'])
#dict_values([123, 'weq', 'sheng'])
#dict_items([('ws', 123), ('qwe', 'weq'), ('王', 'sheng')])

元组

一种不可变的数据结构,一旦创建不能添加、删除与修改
出于数据安全考虑的,安全性很高

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#创建元组
tuple1=(1,'2',True,[2,4],(1,2))
print(type(tuple1)) #<class 'tuple'>

#内置转换函数tuple
list1=[1,2,3]
tuple1=tuple(list1)
print(tuple1)
#(1, 2, 3)

#可以通过赋予变量的重新创建对元组进行修改
tuple1=(1,'2',True,[2,4],(1,2))
var1=2
tuple1=(var1,'2',True,[2,4],(1,2))
print(tuple1)
#(2, '2', True, [2, 4], (1, 2))

封包与解包

封包:将多个值赋值给一个变量
解包:将一个元组或列表赋值给多个变量

1
2
3
4
5
6
7
8
9
10
11
#解包,一一赋值,多或少都会导致报错
tuple1=(1,2,3,4)
var1,var2,var3,var4=tuple1
print(var1,var2,var3,var4)
#1 2 3 4

#封包————让变量赋值多元素
tuple1=(1,2,3,4)
*var1,var2,var3=tuple1
print(var1,var2,var3)
#[1, 2] 3 4 用列表来承载多元素

集合

分为可变集合set和不可变集合fornzset

特点:不支持索引,元素随机排列,只能包含常量,不能重复,有重复自动删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#创建集合
set1={1,2,3,5,6,7,8,7,8,True,(1,3)}
print(type(set1)) #<class 'set'>
print(set1)
#{1, 2, 3, (1, 3), 5, 6, 7, 8}

#可变集合添加元素add
set1={1, 2, 3, (1, 3), 5, 6, 7, 8}
set1.add('ws')
print(set1)
#{1, 2, (1, 3), 3, 5, 6, 7, 8, 'ws'}

#可变集合删除元素remove、discord
set1.remove('ws')
print(set1)
#{1, 2, (1, 3), 3, 5, 6, 7, 8}
set1.remove('ws')
print(set1)
#报错
set1.discard('ws')
print(set1)
#不会报错,什么都不执行

#pop也可以在集合中使用,同理

#集合用以消除 在列表相加而出现的重复元素
list1=[1,2,3,4]
list2=[2,3,4,5,6]
list3=list(set(list1 + list2))
print(list3)
#[1, 2, 3, 4, 5, 6]

集合的关系测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
set1={1,2,'qwe'}
set2={2,3,'erq'}
set3={1,5,4,'qwe'}

#包含关系判断issubclass、issuperset
print(set1.issubset(set2)) # 测试前面的集合是不是后面集合的子集
#False
print(set1.issuperset(set2)) # 测试前面的集合是否完全包含后面的集合
#False

#集合相减difference、difference_update
print(set1.difference(set3)) #difference集合相减
set1-set3 #等同于
print(set1) #set1不会有变化吗,需要再次赋值之后才行
#{1, 2, 'qwe'}
set1.difference_update(set3) #difference_update集合相减立即生效
print(set1)
#{2}

#集合相交intersection、intersection_update
set1=set1.intersection(set3) #n找出相交的元素,通过赋值给予
set1 & set3 #等同于
print(set1)
set1.intersection_update(set2) #立即成效给set1
print(set1)

#集合并集union、update
set1=set1.union(set2)
print(set1)
#{'erq', 1, 2, 'qwe', 3}
set1.update(set2)
print(set1)
#{1, 2, 3, 'erq', 'qwe'}

不可变集合

和元组差不多,所以不支持自动生效的关系测试的方法,例如只支持关系判断issubclass,集合相减difference,集合相交intersection,集合并集union

元组是有序的,但不可变集合是无序的

1
2
3
4
#创建
fest1=frozenset([1,2,3,4])
print(type(fest1))
#<class 'frozenset'>

流程控制方法

流程控制有三种方法:分支、循环、跳出

流程的控制通过布尔值来实现,分支和循环都需要对一定的条件进行判断,根据判断结果(布尔值)决定下一步要做什么

布尔值通过比较运算符、逻辑运算符来进行判断是True还是False

不需要判断直接得到True——数字1,非空数据类型与结构,如非空list,非空string,非空set等

不需要判断直接得到False——数字0,空数据类型与结构

博主以前学过51和c,这块就轻松很多,快速带过

分支

分支是根据条件,让有些代码可以被执行或不被执行

分支关键字有:if、else、elif、pass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#if
var = 123
if var == '111': #条件不满足
print('123123123') #条件不满足,不会执行
print('over') #if外的语句,输出over
#over

#else二选一:条件不满足则执行,条件满足则不执行,else和if都只能出现一次
var = 123
if var == '111': #条件不满足
print('123123123') #条件不满足,不会执行
else:
print('ws') #条件不满足,可以满足else,输出ws
print('over') #if外的语句,输出over
#ws
#over

#elif:多分支,但要注意条件必须递增
var = 68
if var >= 80:
print('优秀')
elif var >= 70:
print('良好')
elif var >= 60:
print('及格')
else:
print('不及格')
print('over')
#及格
#over

#分支嵌套
dict1 = {'ws':'1111','xhy':'2222'} #创建一个包含密码的字典
name = input('username:')
if name in dict1.keys(): #判断输入的username是否在字典的key中
pwd = input('password:')
if pwd == dict1[name]: #判断输入的password是否是字典中username的key对应的value
print('success') #成功输出success
else:
print('error') #失败输出error
else:
print('no such username')

#pass占位语句,保证语法结果的完整性,不然会报错,最后再对pass部分进行修改
dict1 = {'ws':'1111','xhy':'2222'}
name = input('username:')
if name in dict1.keys():
pwd = input('password:')
if pwd == dict1[name]:
pass #满足条件则 跳过这部分,什么都不执行
else:
print('error')
else:
print('no such username')

#三目运算符————简化分支结构的写法 -只能提供二选一
var = int(input('soure:'))
print('good' if var >= 60 else 'bad') #"真" if 条件判断 else "假"
#good
等同于:
if var >= 60:
print('good')
else:
print('bad')

循环

让某些代码重复执行

关键字包括for、while

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# while循环
list1 = [] #给列表添加元素
i = 1
while i <= 10: #判断条件
list1.append(i)
i += 1
print(list1)
#[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

#for循环,一般而言是对序列、字符串等对象进行遍历操作时使用for
#for循环不需要指定退出条件
list1=[1,2,3,4,5]
for i in list1: #按顺序访问序列
print(f'{i}') #输出这个i,次数为序列的元素数
i += 1
#for对字符串进行操作
str='wsxhy'
for i in str:
print(f'{i}') #依次输出

#for解包:
#如果遍历的序列每一项元素都包含若干个子元素
#在循环时可以设置固定数量变量来接受
var=[(1,2,3),(4,5,6),(7,8,9),(10,11,12)]
for v1,v2,v3 in var:
print(f'{v1},{v2},{v3}')
#1,2,3
#4,5,6
#7,8,9
#10,11,12

#for封包
var=[(1,2,3),(4,5,6),(7,8,9),(10,11,12)]
for i in var:
print(i) #直接得到元组
#(1, 2, 3)
#(4, 5, 6)
#(7, 8, 9)
#(10, 11, 12)

#读取字典
dict1={'ws':1,'xhy':'qwe','esq':'12w'}
for i in dict1.items():
print(f'{i}') #分组读出key和value
#('ws', 1)
#('xhy', 'qwe')
#('esq', '12w')

#循环嵌套循环,先执行里层循环,再执行外层循环
a = 0
while a <= 10:
for i in range(3):
print(i) #先执行
a += 1 #跑完了i的循环,才会跑这条命令

#列表推导式——通过循环快速生成列表
list1=[i for i in range(1,21)]
print(list1)
#[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
等同于
list1 = []
i = 1
while i <= 20:
list1.append(i)
i += 1
print(list1)
#步长为2
list1=[i*2 for i in range(1,21)]
#结合三目运算符,挑选偶数(i % 2 ==0)
list1=[i for i in range(1,21) if i % 2 == 0]

跳出

跳出循环的方法,有break与continue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#break跳出循环
i =1
list1 = []
while i <= 20:
if i == 5:
break #遇到break则直接跳出循环
list1.append(i)
i += 1
print(list1)
#[1, 2, 3, 4]

#continue跳过当前循环,最好用在for中,用在while会容易出问题
list1 = []
for i in range(1,8): #在for中没有自增概念
if i == 5:
continue #在i=5的时候不执行这次循环
list1.append(i)
print(list1)
#[1, 2, 3, 4, 6, 7]

#for else——for跑完后执行else语句,如果break了则不执行
#即两个要么都执行,要么都不执行
for i in range(1,10):
pass
else:
pass
#找不到则提示error
user_list={'ws':'1','xhy':'2','qwe':3}
name =input('name:')
for i in user_list:
if i == name:
break
else:
print('error')
#如果不使用这种方法,则需要单独定义一个变量来存储布尔值,来为后面判断依据
name = input('name:')
find=''
for i in user_list:
if i == name:
find = True
break
find = False
if find is False:
print('error')

# while else——返回True时else不被执行,返回False则执行else
#即只执行一个
while True:
pass
else: #False则执行
pass

练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
users=[{'ws':'ws111'},{'xhy':'xhy111'},{'qwe':'qwe111'}]
#判断用户输入是否存在
#如果有,则输入密码,检测密码是否正确
#正确则登录成功

name=input('username:')
for v1 in users: #对这个列表进行解包,解包为三个单独的字典
if name in v1.keys(): #判断有无用户名
password=input('password:')
if password in v1.values(): #判断输入的密码是否为字典对应key的value
print('login successful')
break
else:
print('password incorrect')
break
else:
print('no such user')

通过循环来遍历可迭代对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
user1 = {'name': '王盛','age':'25','salary':'5000','address':'sx'}
user2 = {'name': 'xhy','age':'25','salary':'5000','address':'yk'}
user3 = {'name': 'zhz','age':'25','salary':'5000','address':'hz'}
tb = [user1,user2,user3]
# print(tb)
# for i in range(len(tb)):
# print(tb[i].get('address'))

for x in user1:
print(x)

for x in user1.values():
print(x)

for x in user1.items():
print(x)

name
age
salary
address
王盛
25
5000
sx
('name', '王盛')
('age', '25')
('salary', '5000')
('address', 'sx')

嵌套循环练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#
输出乘法口诀表:

for n in range(1,10):
for m in range(1,n+1):
print("{0}*{1}={2}".format(n,m,n*m),end='\t')
print()

1*1=1
2*1=2 2*2=4
3*1=3 3*2=6 3*3=9
4*1=4 4*2=8 4*3=12 4*4=16
5*1=5 5*2=10 5*3=15 5*4=20 5*5=25
6*1=6 6*2=12 6*3=18 6*4=24 6*5=30 6*6=36
7*1=7 7*2=14 7*3=21 7*4=28 7*5=35 7*6=42 7*7=49
8*1=8 8*2=16 8*3=24 8*4=32 8*5=40 8*6=48 8*7=56 8*8=64
9*1=9 9*2=18 9*3=27 9*4=36 9*5=45 9*6=54 9*7=63 9*8=72 9*9=81

#
输出工资大于6000的人的信息:
user1 = {'name': '王盛','age':'25','salary':4000,'address':'sx'}
user2 = {'name': 'xhy','age':'25','salary':5000,'address':'yk'}
user3 = {'name': 'zhz','age':'25','salary':6000,'address':'hz'}
tb = [user1,user2,user3]

for x in tb:
if x.get('salary') > 5000:
print(x)

循环中的优化

  • 尽量减少循环中不必要的计算
  • 尽量减少内层循环的计算
  • 尽量使用局部变量,查询较快
  • 连接多字符串时,尽量使用join

函数

函数是将代码封装起来,实现代码复用的目的

函数的分类:
1.内置函数,比如str(),list(),len()
2.标准库函数,通过import引入
3.第三方函数,Python社区提供的库,也通过import导入
4.用户自定义函数

函数的命名规则——同变量命名规则:
不能中文、数字不能开头、不能使用空格、不能使用关键字

LEGB规则——查找顺序

如果某个name映射在局部local命名空间中没有找到,接下来就会在闭包作用域enclosed进行搜索,如果闭包作用域也没有找到,Python就会到全局global命名空间中进行查找,最后会在内建built-in命名空间搜索 (如果一个名称在所有命名空间中都没有找到,就会产生一个NameError

Local 指的就是函数或者类的方法内部

Enclosed 指的是嵌套函数(一个函数包裹另一个函数,闭包)

Global 指的是模块中的全局变量

Built in 指的是Python为自己保留的特殊名称

函数的格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def 函数名([参数列表]):  #方括号代表可选
"""文档字符串(注释)""" 注释可以通过help(函数名)来获取
函数体和语句

例:
def add(a,b,c) #定义方法,输入形参a b c
"""相加"""
print("{0}、{1}、{2}的和是{3}".format(a,b,c,(a+b+c)))
#reture sum # 输出返回值

add(10,20,30) #调用这个方法,输入实参

返回值:
1.没有定义返回值,返回值为None
2.定义了返回值,返回指定的值,可以用来赋值给另外的变量
3.返回值返回后,类型是一致的

python执行def时,会创建一个函数对象,绑定到函数变量名上
函数在定义时,只需要定义形参,且不需要声明类型,不需要制定返回值类型

函数的内存分析:
def定义函数后,系统就创建了相应的函数对象,并通过print_star这个变量进行引用
print_star这个变量存放在栈stack中,他在物理内存中的地址是这个函数对象的id
在执行了c=print_star后,c也存放在了stack中,**c和print_star是同一个函数对象**

函数变量

分为全局变量和局部变量

在函数里和不在函数里的区分,很好区分。
全局变量降低了函数的通用性和可读性。应尽量避免全局变量的使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def f1():
global a #如果要在函数内改变全局变量的值,增加global关键字声明
print(a) #打印全局变量a的值
nolocal a #声明使用外部函数的局部变量
每次调用方法,都会产生一个栈帧
在栈帧中,调用a的值

a = 100
def f1(a,b,c):
print(a,b,c)
print(locals()) #打印输出的局部变量
print("#"*20)
print(globals()) #打印输出的全局变量
def f2(a,b,c):

f1(2,3,4)

函数参数的传递

本质上就是:从实参到形参的赋值操作。Python中“一切皆对象”,所有的赋值操作都是“引用的赋值”。所以,Python中参数的传递都是“引用传递”,不是“值传递”。

  1. 对“可变对象”进行“写操作”,直接作用于原对象本身。
1
2
3
4
5
6
7
8
9
10
11
12
b = [10,20]
def f2(m):
print("m:",id(m)) #b和m是同一个对象
m.append(30) #由于m是可变对象,不创建对象拷贝,直接修改这个对象
f2(b)
print("b:",id(b))
print(b)

#m: 1464229716552
#b: 1464229716552
#[10, 20, 30]
ID一样,说明他们两个是同一个对象,所以是“只引用”,不“新创建”

2 .对“不可变对象”进行“写操作”,会产生一个新的“对象空间”,并用新的值填充这块空间。

传递参数是不可变对象(例如:intfloat、字符串、元组、布尔值),实际传递的还是对象的引用。在”赋值操作”时,由于不可变对象无法修改,系统会新创建一个对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
a = 100
def f1(n):
print("n:",id(n)) #传递进来的是a对象的地址
n = n+200 #由于a是不可变对象,因此创建新的对象n
print("n:",id(n)) #n已经变成了新的对象
print(n)
f1(a)
print("a:",id(a))

在赋值之前还是相同的对象,但赋值之后就生成了新的对象,同时也不影响原本的a的值

如果拷贝的是不可变对象,但不可变对象内的子对象是可变的(比如元组不可变,但元组内有一个列表)
在元组中,记录的是列表的地址,所以调用进行修改时,会直接用列表的地址进行修改

浅拷贝和深拷贝

内置函数:copy(浅拷贝)、deepcopy(深拷贝)。

  1. 浅拷贝:拷贝对象,但不拷贝子对象的内容,只是拷贝子对象的引用。
  2. 深拷贝:拷贝对象,并且会连子对象的内存也全部(递归)拷贝一份,对子对象的修改不会影响源对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
浅拷贝
#测试浅拷贝和深拷贝
import copy

def testCopy():
'''测试浅拷贝'''
a = [10, 20, [5, 6]]
b = copy.copy(a)

print("a", a)
print("b", b)
b.append(30)
b[2].append(7)
print("浅拷贝......")
print("a", a)
print("b", b)

#
a [10, 20, [5, 6]]
b [10, 20, [5, 6]]
浅拷贝......
a [10, 20, [5, 6, 7]]
b [10, 20, [5, 6, 7], 30]

此时b和a是独立的,但是b引用了a的子集
所以修改b本身时a不会变化
但是b引用的a的子集,他们指向的是同一个对象

一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
深拷贝
def testDeepCopy():
'''测试深拷贝'''
a = [10, 20, [5, 6]]
b = copy.deepcopy(a)

print("a", a)
print("b", b)
b.append(30)
b[2].append(7)
print("深拷贝......")
print("a", a)
print("b", b)

#a [10, 20, [5, 6]]
#b [10, 20, [5, 6]]
#深拷贝......
#a [10, 20, [5, 6]]
#b [10, 20, [5, 6, 7], 30]

深拷贝,子集也不共享

另一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import copy
class Mobilephone:
def __init__(self,cpu):
self.cpu = cpu
class CPU:
pass
c = CPU()
m = Mobilephone(c)

print("浅拷贝")
m2 = copy.copy(m)
print("m:",id(m))
print("m:",id(m2))
print("m中的CPU",id(m.cpu))
print("m中的CPU",id(m2.cpu))
# m: 2013639866072
# m: 2013639866352
# m中的CPU 2013639866016
# m中的CPU 2013639866016

print("深拷贝")
m3 = copy.deepcopy(m)
print("m:",id(m))
print("m:",id(m3))
print("m中的CPU",id(m.cpu))
print("m中的CPU",id(m3.cpu)) #递归拷贝
# m: 2013639866072
# m: 2013639930656
# m中的CPU 2013639866016
# m中的CPU 2013640030864

main函数与一些常识

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#定义main函数,主函数是程序的入口,相当于c语言的char main()
#在main上面,要定义函数与类
list1=['wadsad', 'qweqwe']
def show(list_name):
for i in list_name:
print(f'{list_name.index(i) + 1}.{i}')
if __name__ == '__main__': #主函数
show(list1)
#1.wadsad
#2.qweqwe

#函数的帮助文档
#可以通过ctrl+点击打开python的源代码,后面的备注就是函数的说明文档
help(print) #也可以通过help()来查看帮助文档
#可以给自己定义的函数添加备注,说明作用
def show(list_name):
"""
:param list_name:
:return:
"""
for i in list_name:
print(f'{list_name.index(i) + 1}.{i}')
#通过__doc__也可以对帮助文档进行查看
print(show.__doc__)
# :param list_name:
# :return:

#return 函数的返回值
#return是什么,函数就是什么
#return会立即执行,后面的语句不会执行
#可以一次return多个值
def fun():
v1 = 1
v2 = 2
v3 = 3
return v1,v2,v3 #返回多个值
if __name__ == '__main__':
var1,*var2 = fun() #解包
print(var2) #如果没有return,则会自动return一个空值None

传参

参数的类型:

  • 位置参数——调用时实参按顺序传送,必须一对一匹配
  • 默认值参数——在定义时就放在函数中的参数,且必须在位置参数后面
  • 命名参数——在调用时指定,通过名字来区分
  • 可变参数:——要放到所有参数最后
  1. param(一个星号),将多个参数收集到一个“元组”对象中。
  2. *param(两个星号),将多个参数收集到一个“字典”对象中。
  • 强制命名参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#形参与实参
def fun(var): #形参(举例)
print(f'{var}')
if __name__ == '__main__':
var1 = input('press:')
fun(var1) #真实传入的是实参

#位置参数————按照顺序一一对应的传递参数,最基础的传参形式
#形参和实参位置要一一对应、且数量要一致
def fun(var1,var2):
v1 = var1 * 2
v2 = var2 + 10
return v1,v2
if __name__ == '__main__':
fun(123,456) #按照顺序把123传入var1,把456传入var2

#关键字参数————参数名=值的方式传递参数
def fun(var1,var2,var3):
print(var1,var2,var3)
if __name__ == '__main__':
fun(var2=456,var1=123,var3=789) #直接将var1作为关键字在函数里进行赋值,来传参
fun(123,456,var3=789) #混合使用,需要把关键字参数的部分放到后面,不推荐使用

#默认值参数————给形参指定一个默认值
def fun(var3,var2,var1=3): #默认值参数需要放在形参列表的最后
print(var1,var2,var3)
if __name__ == '__main__':
fun(1, 2, 4)
#4 2 1
fun(1, 3)
#3 3 1

#参数收集————当参数个数不确定时,统一把参数收集起来,封装为元组
#每个函数只能有一个这种参数
#参数收集类函数尽量放到最后,后面的参数必须用关键字参数
def fun(v1,v2,*args,v3): #使用*,参考封包解包,一般使用args
print(args)
if __name__ == '__main__':
fun(123,12345,'qwe',True,v3=123)
#('qwe', True)

#关键字参数收集——不确定个数的关键字参数,统一收集,封装成字典
#每个函数只能有一个这种参数
#必须放到最后
def fun(v1,**kwargs):
print(kwargs)
if __name__ == '__main__':
fun(True,name='ws',age=24,passwd='ws111')
#{'name': 'ws', 'age': 24, 'passwd': 'ws111'}

#逆向参数收集——将序列对象作为实参传递,序列被拆分并赋值给形参
#实参为列表或元组时,数量要相同
def fun(var1,*var2): #
print(var1,var2)
if __name__ == '__main__':
list1 = ['ws','xhy',111,222]
fun(*list1)
#ws ('xhy', 111, 222)

#实参为字典时,key必须与形参名相同
def fun(var1,var2,**var3):
print(var1,var2,var3)
if __name__ == '__main__':
dict1 = {'var1':'ws111','var2':True,'v1':'v1','v2':'v2'}
fun(**dict1)
#ws111 True {'v1': 'v1', 'v2': 'v2'}

#函数传递可变类型的对象时产生的影响
def fun(add):
add.append('1')
return add
if __name__ == '__main__':
list1=[]
list2=fun(list1)
print(list1)
print(list2)
#['1']
#['1']
#通过引用,可以让局部改变全局的对象

lambda表达式和匿名函数

用以在一行中定义函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#主体只能放一个语句、自动返回return主体语句的结果
#支持各种参数与传参形式
#主要用以简化代码,可以将函数赋值到变量
fun = lambda v1,v2 : v1*v2
print(fun(11,45))
#495
fun = lambda v1,v2=20 : v1 * v2
print(fun(10))
#200

g = [lambda a:a*2,lambda b:b*3,lambda c:c*4]
print(g[0](6),g[1](7),g[2](8))

#12 21 32

eval函数

功能:将字符串str当成有效的表达式来求值并返回计算结果。

语法: eval(source[, globals[, locals]]) -> value

参数:

  1. source:一个Python表达式或函数compile()返回的代码对象
  2. globals:可选。必须是dictionary
  3. locals:可选。任意映射对象

将一个字符串当做代码来执行

eval函数会将字符串当做语句来执行,因此会被注入安全隐患。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#测试eval()函数

s = "print('abcde')"
eval(s)

a = 10
b = 20
c = eval("a+b")
print(c)

dict1 = dict(a=100,b=200)

d = eval("a+b",dict1)
print(d)

递归函数

递归(recursion)是一种常见的算法思路,在很多算法中都会用到。比如:深度优先搜索(DFS:Depth First Search)等。

  1. 终止条件

    表示递归什么时候结束。一般用于返回值,不再调用自己。

  2. 递归步骤

    把第n步的值和第n-1步相关联。

递归函数由于会创建大量的函数对象、过量的消耗内存和运算能力。在处理大量数据时,谨慎使用。

1
2
3
4
5
6
7
8
9
10
11
def my_recursion(n):
print("start:" + str(n))
if n == 1:
print("recursion over!")
else:
my_recursion(n - 1)

print("end:" + str(n))

my_recursion(3)

嵌套函数

在函数里定义函数

1
2
3
4
5
6
7
8
9
10
def outer():
print('outer running...')

def inner():
print('inner running...')

inner()

outer()

应用场景:

  1. 封装 - 数据隐藏

    外部无法访问“嵌套函数”。

  2. 贯彻 DRY(Don’t Repeat Yourself) 原则

  3. 嵌套函数,可以让我们在函数内部避免重复代码。

  4. 闭包

1
2
3
4
5
6
7
8
9
10
11
12
def printName(isChinese,name,familyName):
def inner_print(a,b):
print("{0} {1}".format(a,b))

if isChinese:
inner_print(familyName,name)
else:
inner_print(name,familyName)

printName(True,"小七","高")
printName(False,"George","Bush")

__del__析构方法和垃圾回收机制

Python实现自动的垃圾回收,当对象没有被引用时(引用计数为0),由垃圾回收器调用__del__()

__del__()称为“析构方法”,用于实现对象被销毁时所需的操作。比如:释放对象占用的资源,例如:打开的文件资源、网络连接等。

也可以通过del删除对象,从而保证调用__del__()

1
2
3
4
5
6
7
8
9
10
11
class User:
def __del__(self):
print("User deleted:{0}".format(self))
p1 =User()
p2 =User()
del p1
print("结束")

User deleted:<__main__.User object at 0x000001E536E17208>
结束
User deleted:<__main__.User object at 0x000001E536E17160>

__call__方法和可调用对象

Python 中,凡是可以将 () 直接应用到自身并执行,都称为可调用对象。

可调用对象包括自定义的函数、Python 内置函数、以及实例对象

该方法使得实例对象可以像调用普通函数那样,以“对象名()”的形式使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def f1():
print("f1")
f1() #本质也是调用了__call__()方法
# f1
class F2():
pass
f2 = F2()
f2()
# 报错,无法被调用

class F3():
def __call__(self):
print("f3")
f3 = F3()
f3()
# f3
这里就使用了call方法

文言一心举例:
在实例化之后,就可以直接调用实例,此时就会调用实例的父类的__call__方法
class CallableClass:
def __init__(self, value):
self.value = value

def __call__(self, *args, **kwargs):
print(f"Called with {args} and {kwargs}")
return self.value * 2 # 示例:将实例的值乘以2并返回

# 创建一个CallableClass的实例
instance = CallableClass(5)

# 调用实例(就像调用函数一样)
result = instance(3, 4, key='value') # 这会调用instance的__call__方法
print(result) # 输出:10(因为5 * 2 = 10,而不是与传入的参数3, 4有关)

面向对象编程

面向过程和面向对象的区别

面向过程和面向对象都是对软件分析、设计和开发的一种思想,它指导着人们以不同的方式去分析、设计和开发软件。C语言是一种典型的面向过程语言,Java是一种典型的面向对象语言。

面向过程适合简单、不需要协作的事务,重点关注如何执行。面向过程时,我们首先思考“怎么按步骤实现?”

面向对象(Oriented-Object)思想更契合人的思维模式。我们首先思考的是”怎么设计这个事物?”。比如思考造车,我们就会先思考“车怎么设计?”,而不是“怎么按步骤造车的问题”。这就是思维方式的转变。

面向对象可以帮助我们从宏观上把握、从整体上分析整个系统。 但是,具体到实现部分的微观操作(就是一个个方法),仍然需要面向过程的思路去处理。

对象的组成

对象由三部分组成:标识、类型、值

对象的标识是这个对象在内存中的地址,通过id方法,来获取对象的标识
类型 变量类型

1
2
3
#返回数据类型的函数type
var=123
print(type(var))

引用的本质

在Python中,变量也称为:对象的引用(reference)。

变量存储的就是对象的地址:
变量位于栈stack内存(按顺序存放),对象位于堆heap内存(乱放)

面向对象的基本概念

类class:为了做区分而创建的一些特性的群体
子类:通过继承创建,为了在类下做更清晰的区分
对象object:建立在类的基础上,在某一类或子类下的个体
实例instance:约等于对象,xx对象是xx类的实例化
在python中,一切都是对象;凡是对象,都是某个类的实例

使用面向对象编程,在做好细分之后,能够更快实现功能,更容易维护,类越细分,对象就越精细

类1 → 对象1,对象2

子类1 → 对象1,对象2
子类2…
子类3…

通过类来定义数据类型的属性(数值)和方法,类将属性和方法打包在一起

类、类的属性、类的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
举例:
类————管理员、用户
类的属性————是类里的变量。反映该类对象的信息,例如账号、密码、名字等
类的方法————是类里的函数。反映该类对象能做的事,例如账号权限、添加用户、修改密码等,是一种方法,比如list.append,是自己定义的函数

#创建类————使用class关键字
#class 类名() 括号代表要继承,在规范上,类名的首字母要大写
class Ws:
pass
class XhyClass:
pass

#创建对象——根据类创建一个属于这个类的对象
v1 = Ws() #v1是由Ws这个类实例化生成的对象,v1这个对象是Ws类的实例

#添加类的属性,一般都是类的可变属性
#添加一个不变的属性
class User_info: #通过__init__构造方法
def __init__(self): #self代表要被实例化的对象,必填
self.username = 'ws' #为其赋值
self.age = '24'
self.password = 'ws111'
user1 = User_info()
print(user1.password)
#ws111
#添加类的可变属性,这样可以适用更多user
class User_info:
def __init__(self,username,age,password):
self.username = username
self.age = age
self.password = password

user1 = User_info('ws',24,'ws111')
user2 = User_info('xhy',23,'xhy111')
print(user1.username)
print(user2.password)
#ws
#xhy111

#添加类的方法,在创建了对象之后,就可以直接使用类的方法
class User_info:
def __init__(self,name,password,age):
self.name = name
self.password = password
self.age = age
def show(self):
print(self.name, self.password)
def show_age(self):
print(self.age)
user1 = User_info('ws','ws111',age=24)
user1.show() #这个类实例化之后,可以直接使用方法
#ws ws111
user1.show_age()
#24
#在方法中定义的类的属性,可以在全局进行调用
class User_info:
def __init__(self,name):
self.name = name
def show_age(self):
self.age = 24 ####在方法中定义类的属性
print(self.age)
user1.show_age()
#24

也可以在实例中新添加类的属性,这个类的属性仅对于那个实例有效

实例方法

实例方法的本质是调用实例自己,如果没有实例则无法调用,这也是最普遍的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#实例方法————首个参数为self的方法,绑定给该类的实例调用,类本身不能调用
class User_info:
def __init__(self,name):
self.name = name
def show(self,c):
self.c = c
print(c)
user1 = User_info('ws') #在实例方法中,必须要创建一个实例,才能使用类中的方法
user1.show(1)

user1.show(1)的本质是User_info.show(user1,1)

其他操作:
dir(obj)可以获得对象的所有属性、方法
obj.__dict__ 对象的属性字典
pass 空语句
isinstance(对象,类型) 判断“对象”是不是“指定类型”

print(dir(User_info))
# ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'show']
print(user1.__dict__)
# {'name': 'ws', 'c': 1}
print(isinstance(user1,User_info))
# True

类对象、类方法的内存分析

1.类对象A的本质是一个type为’type’的对象,A的value包括它的属性和方法

2.A实例化时,通过构造方法__init__,以A为模板进行创建实例B

3.实例对象B的type为A,B通过A的模板生成属性和方法,作为B的value,其中变量是新的,函数依然指向A的函数

4.在A中定义的属性如果没有被构造,那么这些属性就是A的类属性

实例化的对象,都是通过这一个类对象的信息进行创建

类方法

在类中,无法直接调用实例中的属性和方法;如果要调用,需要将其实例化之后才能调用

类方法可以直接通过类调用方法,因此支持在不实例化的情况下创建并返回类的对象

类方法————首个参数为cls,并且以classmethod装饰的方法
cls就是class的缩写,表示这个类本身
前面的self表示就是实例本身

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#可以直接使用类来调用如User_info.show,但无法调用类的参数
class User_info:
def __init__(self,name):
self.name = name
@classmethod
def show(cls):
print('ws')
User_info.show()
#ws

class User_info:
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod #类方法的装饰器
def show_name(cls, age):
print(age)
User_info.show_name(24)
#24

静态方法

静态方法,使用staticmethod装饰,可以直接调用,基本和类无关了。

无法使用类的属性和方法,也无法使用实例属性和方法

1
2
3
4
5
6
7
8
9

class User_info:
def __init__(self, name):
self.name = name
@staticmethod
def show_name(): #需要传参则定义形参
print('11111')
User_info.show_name()
#11111

方法没有重载

如果我们在类体中定义了多个重名的方法,只有最后一个方法有效。
不要使用重名的方法

方法的动态性

Python是动态语言,可以动态的为类添加新的方法,或者动态的修改类的已有的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class User:
def work(self):
print('work')
def play1(s):
print('play')
def work2(s):
print('work2')

User.play = play1 # 新加方法
User.work = work2 # 重新定义方法

p1 = User()
p1.work()
p1.play()
'''
work2
play
如果不在函数里加一个s,添加到类中时,就需要添加self参数
如果定义的时候没有添加参数,就会报错
'''

私有属性和私有方法

python对类的成员没有严格的访问控制限制。所谓私有其实是约定的

通常我们约定,两个下划线开头的属性是私有的(private)。其他为公共的(public)。

  1. 类内部可以访问私有属性(方法)
  2. 类外部不能直接访问私有属性(方法)
    3.类外部可以通过_类名__私有属性(方法)名”访问私有属性(方法)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Wangsheng:
__name = 'ws' # 它的私有属性
address = 'sx'
# 解释器运行时,将__name转成了_Wangsheng__name
def __init__(self,name,age):
self.name = name
self.age = age
def output(self):
print(Wangsheng.__name)
def __work(self): # 私有属性
print('work')

a = Wangsheng('wangsheng','25')
print(Wangsheng._Wangsheng__name) # ws
print(Wangsheng.address) # 外部调用公有属性 sx
a.output() # 类内部调用私有属性 ws
a._Wangsheng__work() # 外部调用私有方法 work

@property装饰器

如果要限制一些数值的大小,可以使用@property装饰器,将一个方法的调用变成属性调用

在对属性做读操作和写操作的时候使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
修改前:
class Example:
def __init__(self,name,age):
self.name = name
self.age = age
example1 = Example('ws',25)
print(example1.age) # 25
example1.age = 18
print(example1.age) # 18
这里可以直接进行修改就很不安全
所以可以将其转变为私有属性

使用私有属性:
class Example:
def __init__(self,name,age):
self.name = name
self.__age = age

def age(self):
return self.__age
example1 = Example('ws',25)
print(example1.age())

使用装饰器:
class Example:
def __init__(self,name,age):
self.name = name
self.__age = age
@property
def age(self):
return self.__age
@age.setter
def age(self,age): # 传参进去修改
if 18<age<25:
self.__age = age
else:
print('录入错误')
example1 = Example('ws',25)
print(example1.age) #25 通过函数age而不是值去读取
example1.age = 19
print(example1.age) #19 #通过函数age去录入方法

属性和方法命名

  • _xxx:保护成员,不能用from module import *导入,只有类对象和子类对象能访问这些成员
  • __xxx__:系统定义的特殊成员
  • __xxx: 类中的私有成员,只有类对象自己能访问,子类对象也不能访问。(但,在类外部可以通过对象名. _类名__xxx这种特殊方式访问。Python不存在严格意义的私有成员)

类相关:

  1. 类名首字母大写,多个单词之间采用驼峰原则。
  2. 实例名、模块名采用小写,多个单词之间采用下划线隔开
  3. 每个类,应紧跟“文档字符串”,说明这个类的作用
  4. 可以用空行组织代码,但不能滥用。在类中,使用一个空行隔开方法;模块中,使用两个空行隔开多个类

None变量

  • None在python中也是对象,type为NoneType,可以将None赋值给任何对象,多个None的对象指向同一个id
  • None和无论什么想比都是FALSE
  • if判断时,空列表空字典空元组0等都会转换成FALSE
  • 不能直接创建None类型的对象

继承

子类继承父类之后可以直接使用父类定义好的属性和方法

创建类时,默认的类是object
User001—>User—>object

  • 子类不重写__int__,实例化子类时,会自动调用父类的__int__
  • 子类重写__int__,要使用父类的构造方法,可以使用super关键字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#最简单的继承性质说明
class User():
def __init__(self,name):
self.name = name
def show(self):
print('111')
#class 子类名(父类名)
class U001(User):
def age(self): #可以在子类里定义新的方法,只属于这个子类可用的
print('123')
def show(self): #可以在子类里重写父类的方法,会盖掉父类的方法
print('222')
if __name__ == '__main__':
user1 = U001('ws')
user1.show()
#222

#super函数————调用父类的 方法
#继承父类方法————可以在子类的方法中调用父类的任意方法
class Person:
def __init__(self,name,age):
print("Person构造方法")
self.name = name
self.age = age
def say_age(self):
print(self.name,"的年龄是:",self.age)
class Student(Person):
def __init__(self,name,age,score):
Person.__init__(self,name,age) # 调用时只能带父类的构造方法
print("Student的构造方法")
self.score = score
def say_score(self):
print(self.name,'的分数是:',self.score)
s1 = Student('王盛',25,80)
s1.say_age()
s1.say_score()
'''
Person构造方法
Student的构造方法
王盛 的年龄是: 25
王盛 的分数是: 80
'''

类成员的继承与重写

成员继承:子类继承了父类除构造方法之外的所有成员。包括私有属性私有方法

方法的继承与重写与属性基本一致,如果子类重写了父类的方法,使用时就会使用子类的方法

可以通过mro()来查看继承属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#继承父类属性————分多种情况
1、全部保留属性,不新增
2、全部替换,只用自己的
3、保留部分属性,不新增
4、保留部分属性,有新增

#全部保留属性,不新增
class User_info:
def __init__(self, username, password, email):
self.username = username
self.password = password
self.email = email
class User(User_info):
pass #最简单,直接什么都不用动,不重写则全部保留

user1 = User('ws','ws111','1320')
print(user1.username)
#ws

#全部替换,只用自己的
class User_info:
def __init__(self, username, password, email):
self.username = username
self.password = password
self.email = email
class User(User_info):
def __init__(self,age,job): #重写构造函数
self.age = age
self.job = job
user1 = User('ws','ws111')
print(user1.age)
#ws
print(user1.username)
# 报错

#保留部分属性,不新增
class User_info:
def __init__(self, username, password, email):
self.username = username
self.password = password
self.email = email
class User(User_info):
def __init__(self,username,password):
# 使用super,重跑一遍父类构造函数的形参,并且只赋值自己要的,不要的空着
super().__init__(username=username,password=password,email=None)
#这样就只传了name和password两个属性
if __name__ == '__main__':
user1 = User('ws','ws111')
print(user1.username)
#ws

#保留部分属性,有新增
class User_info:
def __init__(self, username, password, email):
self.username = username
self.password = password
self.email = email
class User(User_info):
def __init__(self,username,password,job):
self.job = job #新增一个job属性
super().__init__(username=username,password=password,email=None)
if __name__ == '__main__':
user1 = User('ws','ws111','student')
print(user1.username,user1.job)
#ws student

object根类

object类是所有类的父类,因此所有的类都有object类的属性和方法。我们显然有必要深入研究一下object类的结构
dir()可以查看类

所有类都有object类的属性和方法

重写__str__()方法

用于返回一个对对象的描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class User:
def __init__(self,name,age,job):
self.name = name
self.age = age
self.job = job
def __str__(self):
'''对象转化成一个字符串描述'''
print("重写__str__()方法")
return "我是{0},{1},是{2}".format(self.name, self.age, self.job)
User1 = User('王盛',24,'学生')
print(User1)
# 重写__str__()方法
# 我是王盛,24,是学生

s = str(User1) # 将其转成字符串时,本质上就是调用了他的__str__方法
# 重写__str__()方法

多重继承

Python支持多重继承,一个子类可以有多个“直接父类”。这样,就具备了“多个父类”的特点。但是由于,这样会被“类的整体层次”搞的异常复杂,尽量避免使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class A:
def aa(self):
print('aa')
class B:
def bb(self):
print('bb')
class C(A,B):
def cc(self):
print('cc')
c = C()
c.aa()
c.bb()
c.cc()

#aa
#bb
#cc

print(C.mro()) # 打印类的层次结构
[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
如果调用C的一个类 在A和B中都有,会先查找C,然后查找A,然后B

super()获得父类定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
调用父类的构造方法:
super(子类名称,self).__init__(参数列表)

class A:
def __init__(self):
print("A的构造方法")

def say(self):
print("A: ",self)
print("say AAA")

class B(A):
def __init__(self):
#A.__init__(self)
super(B,self).__init__() #调用父类的构造方法
print("B的构造方法")
def say(self):
#A.say(self) 调用父类的say方法
super().say() #通过super()调用父类的方法
print("say BBB")

b = B()
b.say()

多态

多态(polymorphism)是指同一个方法调用由于对象不同可能会产生不同的行为。

多态需要有继承,只发生在父类和子类之间,需要有重写
仅需一个函数,传递不同的对象,就能使用不同子类中的制定函数
可扩展性和可维护性都增加了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

class Version:
def what(self):
print('Version')
class V1(Version):
def what(self):
print('v1')
class V2(Version):
def what(self):
print('v2')
class V3(Version):
def what(self):
print('v3')
def fun(object):
object.what()

if __name__ == '__main__':
将v1-3都实例化
v1 = V1()
v2 = V2()
v3 = V3()
实例引用方法
fun(v1)
fun(v2)
fun(v3)
#v1
#v2
#v3

运算符重载、特殊方法

Python的运算符实际上是通过调用对象的特殊方法实现的。

c = a+b和c = a.add(b)是一致的

所以有时候会自己重新对+进行定义

方法 说明 例子
init 构造方法 对象创建和初始化:p = Person()
del 析构方法 对象回收
repr,str 打印,转换 print(a)
call 函数调用 a()
getattr 点号运算 a.xxx
setattr 属性赋值 a.xxx = value
getitem 索引运算 a[key]
setitem 索引赋值 a[key]=value
len 长度 len(a)
运算符 特殊方法 说明
+ add 加法
- sub 减法
< <= == lt le eq 比较运算符
> >= != gt ge ne 比较运算符
^ & or xor and
<< >> lshift rshift 左移、右移
* / % // mul truediv mod floordiv 乘、浮点除、模运算(取余)、整数除
** pow 指数运算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
重载运算符
class Persion:
def __init__(self,name):
self.name = name
def __add__(self,other):
if isinstance(other,Persion):
return "{0},{1}".format(self.name,other.name)
else:
return "不是同类对象不能相加"
def __mul__(self,other):
if isinstance(other,int):
return self.name*other
else:
return "不是同类对象不能相加"
p1 = Persion('ws')
p2 = Persion('xhy')

x = p1 + p2
print(x)
print(p1*3)

特殊属性

特殊属性 含义
obj.dict 对象的属性字典
obj.class 对象所属的类
class.bases 表示类的父类(多继承时,多个父类放到一个元组中)
class.base 类的父类
class.mro 类层次结构
class.subclasses() 子类列表

集成与组合

除了继承,“组合”也能实现代码的复用。“组合”核心是“将父类对象作为子类的属性”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class CPU:
def calculate(self):
print("Calculating")
class Screen:
def show(self):
print("showing")
# 组合它
class MobilePhone:
def __init__(self,cpu,screen): # 将类作为属性
self.screen = screen
self.cpu = cpu
c = CPU()
s = Screen()
m = MobilePhone(c,s)

m.cpu.calculate()
# Calculating

m.screen.show()
# showing

设计模式-工厂模式与单例模式

设计模式说到底就是使用一种套路

工厂模式

工厂模式实现了创建者和调用者的分离,使用专门的工厂类将选择实现类、创建对象进行统一的管理和控制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 工厂模式
class CarFactory:
def createCar(self,brand):
if brand == '小米':
return Xiaomi()
elif brand == '华为':
return HW()
elif brand == 'BYD':
return BYD()
else:
return "Unknown brand"
class Xiaomi:
pass
class BYD:
pass
class HW:
pass

factory = CarFactory()
c1 = factory.createCar('小米')
c2 = factory.createCar('华为')
c3 = factory.createCar('BYD')

单例模式

单例模式(Singleton Pattern)的核心作用是确保一个类只有一个实例,并且提供一个访问该实例的全局访问点。

单例模式只生成一个实例对象,减少了对系统资源的开销。当一个对象的产生需要比较多的资源,如读取配置文件、产生其他依赖对象时,可以产生一个“单例对象”,然后永久驻留内存中,从而极大的降低开销。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MySingleton:
__obj = None
__init_flag = True
def __new__(cls,*args,**kwargs):
if cls.__obj == None:
cls.__obj = object.__new__(cls)
return cls.__obj

def __init__(self,name):
if MySingleton.__init_flag is True:
print("初始化第一个对象")
self.name = name
MySingleton.__init_flag = False # 修改标记,确保只会被调用一次
a = MySingleton('aa')
print(a)
b = MySingleton('bb')
print(b)

# <__main__.MySingleton object at 0x0000015BD35EF668>
# <__main__.MySingleton object at 0x0000015BD35EF668>

异常处理

常见的异常导致的错误包括:

用户操作相关:类型错误、无效输入、误操作
网络问题:丢包、对方主机无应答
操作系统与硬件问题:重启,断电等

编程中要尽可能面对各种异常情况,编写全面完整的异常处理逻辑

异常信息包括:
文件位置、行数位置、具体语句、错误类型、异常描述

函数体中使用异常处理结构时,尽量不要使用return,在最后使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#异常处理结构————try、except关键字,捕获与处理异常
1
try:
print('1')
a = 3/0
print('2')
except Exception as e:
print('3')
print(e)
print('4')
print('5')
# 1
# 3
# division by zero
# 4
# 5

2
while True: #添加一个循环
try: #捕获异常
num = input('>>') #input输入必定为字符串类型
if '.' in num: #如果内容包含一个.则使用float进行转换
num = float(num)
else: #如果内容为数字,则通过int进行转换
num = int(num)
**** #如果内容两个都不是,则可以用except进行
print(num + 10)
break
except Exception as e: #如果出现异常,则进行处理。并且把故障信息Exception赋值到e中
print('please press number')
print(e) #查看之前发生的异常

3: 多except结构
try:
a = input("被除数")
b = input("除数")
c = float(a)/float(b)
print(c)
except ZeroDivisionError:
print("除数不能为0")
except TypeError:
print("类型错误")
except ValueError:
print("值类型错误")
except BaseException as e:
print(e)
print(type(e))
BaseException类包含上面几个小类,如果把它放在最前面,就会全部捕获,无法达成分类的效果
# 除数不能为0
#被除数qwe
#除数123
#值类型错误

4except...else
try:
a = input("被除数")
b = input("除数")
c = float(a)/float(b)
except BaseException as e:
print(e)
else:
print(c)

#finally分支——无论是否有异常,都会执行finally
#进行资源回收,一些进程会占用资源,比如打开的文件、建立的套接字连接、数据库的连接等
#无论是否异常,都需要做资源的回收
try:
pass
except Exception as e:
pass
finally: #必定执行
pass

#arise主动引发异常,可以指定异常信息与异常类型
#主要用于和try、except结合使用,主动引发异常,输出异常的语句
raise Exception
#Traceback (most recent call last):
# File "C:\Users\13209\PycharmProjects\first\Study\异常处理.py", line 23, in <module>
# raise Exception
# Exception
raise StopIteration
# Traceback (most recent call last):
# File "C:\Users\13209\PycharmProjects\first\Study\异常处理.py", line 23, in <module>
# raise StopIteration
# StopIteration

常见异常
所有异常类都是从BaseException类的子类,不同的异常类记录不同的异常信息
SyntaxError:语法错误
NameError:尝试访问一个没有申明的变量
ZeroDivisionError:除数为0错误(零除错误)
ValueError:数值错误
AttributeError:访问对象的不存在的属性
TypeError:类型错误
IndexError:索引越界异常
KeyError:字典的关键字不存在

#练习:
#让用户输入4位的纯数字,如果不是数字则报异常
#验证是否是4位,如果不是也报错,如果是就输出成功

#我写的
def fun():
while True:
try:
n1 = input('Enter student number:')
n1 = int(n1)
if len(str(n1)) == 4:
print('成功')
break
else:
print('请输入四位数字')
except ValueError as e:
print('请输入数字')

if __name__ == '__main__':
fun()

#GPT写的
def fun():
while True:
try:
n1 = input('Enter student number:')
#判断是否为纯数字,这个函数用以判断字符串
#如果纯数字则返回True,反之则返回False
if not n1.isdigit():
raise ValueError('输入纯数字')
# 检查输入是否为4位数
if len(n1) != 4:
raise ValueError("输入的数字不是4位数")
else:
print('成功')
break
except ValueError as e:
print(e)
if __name__ == '__main__':
fun()

异常名称 说明
ArithmeticError 所有数值计算错误的基类
AssertionError 断言语句失败
AttributeError 对象没有这个属性
BaseException 所有异常的基类
DeprecationWarning 关于被弃用的特征的警告
EnvironmentError 操作系统错误的基类
EOFError 没有内建输入,到达EOF 标记
Exception 常规错误的基类
FloatingPointError 浮点计算错误
FutureWarning 关于构造将来语义会有改变的警告
GeneratorExit 生成器(generator)发生异常来通知退出
ImportError 导入模块/对象失败
IndentationError 缩进错误
IndexError 序列中没有此索引(index)
IOError 输入/输出操作失败
KeyboardInterrupt 用户中断执行(通常是输入^C)
KeyError 映射中没有这个键
LookupError 无效数据查询的基类
MemoryError 内存溢出错误(对于Python 解释器不是致命的)
NameError 未声明/初始化对象 (没有属性)
NotImplementedError 尚未实现的方法
OSError 操作系统错误
OverflowError 数值运算超出最大限制
OverflowWarning 旧的关于自动提升为长整型(long)的警告
PendingDeprecationWarning 关于特性将会被废弃的警告
ReferenceError 弱引用(Weak reference)试图访问已经垃圾回收了的对象
RuntimeError 一般的运行时错误
RuntimeWarning 可疑的运行时行为(runtime behavior)的警告
StandardError 所有的内建标准异常的基类
StopIteration 迭代器没有更多的值
SyntaxError Python 语法错误
SyntaxWarning 可疑的语法的警告
SystemError 一般的解释器系统错误
SystemExit 解释器请求退出
TabError Tab 和空格混用
TypeError 对类型无效的操作
UnboundLocalError 访问未初始化的本地变量
UnicodeDecodeError Unicode 解码时的错误
UnicodeEncodeError Unicode 编码时错误
UnicodeError Unicode 相关的错误
UnicodeTranslateError Unicode 转换时错误
UserWarning 用户代码生成的警告
ValueError 传入无效的参数
Warning 警告的基类
WindowsError 系统调用失败
ZeroDivisionError 除(或取模)零 (所有数据类型)

traceback模块

打印异常信息

1
2
3
4
5
6
7
8
9
10
#coding=utf-8
import traceback

try:
print("step1")
num = 1/0
except:
with open("d:/a.log","a") as f: # 如果遇到异常,就打开一个文件d:/a.log
traceback.print_exc(file=f) # 在文件中输入异常信息

自定义异常类

程序开发中,有时候我们也需要自己定义异常类。自定义异常类一般都是运行时异常,通常继承Exception或其子类即可。命名一般以ErrorException为后缀。

Python的异常处理机制在需要显示异常信息时会调用异常对象的__str__方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Age_Error(Exception): #继承Exception
def __init__(self,errorInfo):
Exception.__init__(self) # 重写init方法
self.errorInfo = errorInfo
def __str__(self):
return str(self.errorInfo)+"年龄错误" # 重写str方法,并且输出异常与提示

if __name__ == "__main__":
print("Starting")
age = int(input("Age:"))
if age<1 or age>150:
raise Age_Error(age) #此时就会将age作为参数传到errorInfo这个变量里
else:
print("正常")

调试与断点

进行调试的核心是设置断点,程序执行到断点时,暂时挂起,停止执行。就像看视频按下停止一样,我们可以详细的观看停止处的每一个细节。

设置断点

调试视图
点击瓢虫按钮

左侧为“浏览帧”:调试器列出断点处,当前线程正在运行的方法,每个方法对应一个“栈帧”。最上面的是当前断点所处的方法。

右侧为变量值观察区

调试方法:
单步调试:step over遇到函数跳过
单步调试:step into遇到函数进入
跳出函数:step out——当单步执行到子函数内时,用step out就可以执行完子函数余下部分,并返回到上一层函数
执行的光标处:run to cursor——到光标处停止,用在循环内部时,点击一次就执行一个循环

文件处理

一个完整的程序一般都包括数据的存储和读取;我们在前面写的程序数据都没有进行实际的存储,因此python解释器执行完数据就消失了。实际开发中,我们经常需要从外部存储介质(硬盘、光盘、U盘等)读取数据,或者将程序产生的数据存储到文件中,实现“持久化”保存。

编码问题

ASCII码

是世界上最早最通用的单字节编码系统,主要用来显示现代英语及其他西欧语言。

ASCII码用7位表示,只能表示128个字符。只定义了27=128个字符,用7bit即可完全编码,而一字节8bit的容量是256,所以一字节ASCII的编码最高位总是0。

GB2312

覆盖了汉字的大部分使用率

GBK

扩展了GB2312,在它的基础上又加了更多的汉字,它一共收录了21003个汉字

GB18030

它主要采用单字节、双字节、四字节对字符编码,它是向下兼容GB2312GBK的,虽然是我国的强制使用标准,但在实际生产中很少用到,用得最多的反而是GBKGB2312

Unicode

Unicode编码设计成了固定两个字节,所有的字符都用16位(2^16=65536)表示,包括之前只占8位的英文字符等,所以会造成空间的浪费,UNICODE在很长的一段时间内都没有得到推广应用。

UTF-8

UTF编码兼容iso8859-1编码,同时也可以用来表示所有语言的字符,不过,UTF编码是不定长编码,每一个字符的长度从1-4个字节不等。其中,英文字母都是用一个字节表示,而汉字使用三个字节。一般项目都会使用UTF-8。

乱码问题

windows操作系统默认的编码是GBK,Linux操作系统默认的编码是UTF-8。当我们用open()时,调用的是操作系统打开的文件,默认的编码是GBK

比如,测试写入中文时,如果不指定打开时候的类型,就会默认使用GBK编码,然后使用utf-8打开时,就会乱码。

文本文件和二进制文件

文本文件存储的是文本,python默认为unicode字符集(两个字节表示一个字符,最多可以表示:65536个)

二进制文件把数据内容用“字节”进行存储,无法用记事本打开。必须使用专用的软件解码。常见的有:MP4视频文件、MP3音频文件、JPG图片、doc文档等等

相关模块

名称 说明
io模块 文件流的输入和输出操作 input output
os模块 基本操作系统功能,包括文件操作
glob模块 查找符合特定规则的文件路径名
fnmatch模块 使用模式来匹配文件路径名
fileinput模块 处理多个输入文件
filecmp模块 用于文件的比较
csv模块 用于csv文件处理
pickle和cPickle 用于序列化和反序列化
xml包 用于XML数据处理
bz2、gzip、zipfile、zlib、tarfile 用于处理压缩和解压缩文件(分别对应不同的算法)

创建文件对象open()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
为了对文件进行读写操作,需要先打开文件
python提供了内置函数open
返回的文件对象(需要一个变量来接收)
def open(file, mode='r', buffering=None, encoding=None, errors=None, newline=None, closefd=True):
一般只改mode

"""
mode:
r 只读,默认参数
w 只写,会清空文件内容,如果没有则会创建文件,类似于echo >
a 追加模式,如果没有则会创建文件,类似于echo >>
x 只写,如果已有文件则会报错,保证文件是第一次创建
+ 读写,可读可写,必须与上面的搭配使用
b 二进制模式,用二进制打开,必须与上面的搭配使用
"""

#文件名需要通过路径能够找到,如果同路径则不需要找
file = open("test1.json",mode='r',encoding="utf-8")
file.close()

file = open("test2.json",mode='a+',encoding="utf-8")
file.close()

file = open("test1.json",mode="wb")
file.close()

file = open("test1.json",mode="w+b")
file.close()

open了之后的对象需要进行close,
close的本质是,将缓冲区的数据写入文件,也可以使用flush()方法

结合with使用,就可以不需要使用close
with open(r"./test.txt","a") as f:
s = """wangsheng
xuehuiying"""
f.write(s)

read方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"""
返回在文件中读取到的内容
# def read(self, n: int = -1) -> AnyStr:
默认值为n=-1,输出所有内容
通过修改值,输出n字节
"""

file = open("test.log",mode='r')
print(file.read())
#所有内容
print(file.read(50))
print(file.read(5))
没有关闭文件时,可以多次读取,每次读取都会从上一次的后面继续读
如果已经读完了,就不会读了
file.close()

readline方法

每次默认读取第一行,可以设置读取多行,用法与read基本一致
最多只能读一行,不能隔行读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
file = open("test.log",mode='r')
print(file.readline())
#DEBUG - 2024-02-19 19:24:14,733 - 第7行:debug
print(file.readline(2))
#DE
print(file.readline(3))
#BUG
file.close()

另一种方法:
with open(r"./test.txt","r",encoding="utf-8") as f:
for line in f:
print(line,end="")

with open(r"./test.txt","r",encoding="utf") as f:
while True:
line = f.readline()
if not line: # 检测是否已经达到文尾,因为非空判断为True,空判断为False
break
else:
print(line,end="")

readlines方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
默认返回文件中每行内容组成的列表
file = open("test.log",mode='r')
print(file.readlines())
#['DEBUG - 2024-02-19 19:24:14,733 - 第7行:debug\n', 'DEBUG - 2024-02-19 19:25:33,445 - 第7行:出现错误\n', 'logger\n', 'logger\n', 'logger\n', 'logger\n', '2024-02-19 20:37:28,984 - INFO - logger\n', '("2024-02-19 20:57:53,867 - INFO - info")\n', '("2024-02-19 20:58:01,037 - INFO - info")\n', '("2024-02-19 20:58:03,685 - INFO - info")\n', '("2024-02-19 20:58:04,704 - INFO - info")\n', '("2024-02-19 20:58:29,477 - INFO - tesst")\n', '("2024-02-19 20:58:36,881 - INFO - test")\n', '("2024-02-19 20:58:51,552 - INFO - test")\n', '2024-02-28 12:17:03,305:debug\n', '2024-02-28-12:17:58:debug\n']

如果添加参数值少于一行的字符,就会只输出一行,如果大于一行,则会返回两行
file = open("test.log",mode='r')
print(file.readlines(45))
#['DEBUG - 2024-02-19 19:24:14,733 - 第7行:debug\n', 'DEBUG - 2024-02-19 19:25:33,445 - 第7行:出现错误\n']
print(file.readlines(20))
#['logger\n', 'logger\n', 'logger\n']

练习 为每行末尾增加 序列的井号
with open('./test.txt', 'r',encoding='utf-8') as f:
lines = f.readlines() # 输出一个列表
lines2 = [line + "#" + str(index) for index,line in zip(range(1,len(lines)+1),lines)] # 让index,line并行遍历,zip代表并行
# 预期为 wangsheng#1,xhehuiying#2
print(lines)
print(lines2)

# ['wangsheng\n', 'xuehuiying\n', 'qwe']
# ['wangsheng\n#1', 'xuehuiying\n#2', 'qwe#3']

write方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
"""
write方法——写入一个字符串,且不会自动换行
writelines方法——将序列中的字符串依次写入(输入为列表),且不会自动换行
"""

#write
file = open("test2.json","w+",encoding="utf-8")
file.write("11111")
file.write("11111\n")
file.write("11111")
file.close()

file = open("test2.json","w+",encoding="utf-8")
str = """
qweqwe
wsad
qews
"""
file.write(str)

#writelines,同理,
with open(r"./test.txt","w") as f:
list1 = ["wangsheng\n","xuehuiying\n","qwe"]
f.writelines(list1)
#wangsheng
#xuehuiying
#qwe

write和read的综合练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 练习 为每行末尾增加 序列的井号

# wangsheng
# xuehuiying
# qwe

with open('./test.txt', 'r',encoding='utf-8') as f:
lines = f.readlines() # 输出一个列表
lines2 = [line.rstrip() + "#" + str(index) + "\n" for index,line in zip(range(1,len(lines)+1),lines)] # 让index,line并行遍历,zip代表并行
# 预期为 wangsheng#1,xhehuiying#2
print(lines)
print(lines2)

# ['wangsheng\n', 'xuehuiying\n', 'qwe\n']
# ['wangsheng#1\n', 'xuehuiying#2\n', 'qwe#3\n']

with open('./test.txt','w',encoding='utf-8') as f:
f.writelines(lines2)

# wangsheng#1
# xuehuiying#2
# qwe#3

二进制文件读写

实现图片文件拷贝

1
2
3
4
二进制文件处理流程与文本文件一致,首先要创建文件对象,并制定二进制模式
with open('Mika.png','rb') as srcFile,open('MikaMika.png','wb') as destFile:
for line in srcFile:
destFile.write(line)

文件对象的常用属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
name —— 名字
mode —— 打开模式
closed —— 如果关闭,则返回True

模式 说明
r 读模式
w 写模式
a 追加模式
b 二进制模式(可与其他模式组合)
+ 读写模式(可以其他模式组合)

方法名 说明
read([size]) 从文件中读取size个字节或字符的内容返回。若省略[size],则读取到文件末尾,即一次读取文件所有内容
readline() 从文本文件中读取一行内容
readlines() 把文本文件中每一行都作为独立的字符串对象,并将这些对象放入列表返回
write(str) 将字符串str内容写入文件
writelines(s) 将字符串列表s写入文件文件,不添加换行符
seek(offset [,whence]) 把文件指针移动到新的位置,offset表示相对于whence的多少个字节的偏移量;offset:off为正往结束方向移动,为负往开始方向移动whence不同的值代表不同含义:0: 从文件头开始计算(默认值)1:从当前位置开始计算2:从文件尾开始计算
tell() 返回文件指针的当前位置
truncate([size]) 不论指针在什么位置,只留下指针前size个字节的内容,其余全部删除;如果没有传入size,则当指针当前位置到文件末尾内容全部删除
flush() 把缓冲区的内容写入文件,但不关闭文件
close() 把缓冲区内容写入文件,同时关闭文件,释放文件对象相关资源

文件的指针seek()

文件的指针是用来标记位置的,用来做读操作和写操作

比如连续使用read和write,都是因为存在指针来进行标记

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
"""
tell方法——查询指针位置,返回指针的位数
位数:在utf-8的编码格式下,1个英文、数字对应1个位数,1个中文对应3个位数

seek方法——移动指针位置
def seek(self, offset: int, whence: int = 0) -> int:
offset为偏移量,可以为负数
whence为偏移起始位置,0表示开头,1表示当前,2表示结尾
——如果要指定whence,则需要以二进制方式打开,否则只能使用0;
移动指针后如果写入内容,会覆盖旧内容;
如果移动指针数不足字符所占位数就会乱码;如果新内容不足旧内容位数也会乱码
"""

#1王盛1王盛1123想1
file = open("test2.json",mode="r+",encoding="utf-8")
print(file.tell())
print(file.read(3))
print(file.tell())
file.close()
#0
#1王盛
#7

#嘿嘿嘿嘿嘿嘿
file = open("test2.json",mode="r+b")
file.seek(3,0) #开头
print(file.tell())
#3
file.seek(0,1)
print(file.tell())
#3
file.seek(3,1)
print(file.tell())
#6
file.write("哈哈".encode()) #以二进制方式写入
#嘿嘿哈哈嘿嘿
相当于将位数6开头的两个字符覆盖为哈哈

乱码情况1
#1王盛1王盛1123想1
同样的方法就会乱码,因为指针位数6时位置在第一个“盛”中间
移动位数不足字符“盛”所占位数

乱码情况2
#1王盛1王盛1123想1
指针位数7时,输入插入“哈哈”占6个指针位,但后面“1王盛”占了7个指针位
未占满,所以也会乱码

pickle序列化和反序列化

序列化指的是将对象转化成串行化的数据形式,存储到硬盘或网络中
反序列化就是相反的过程,将串行数据转换成对象

对象的本质是一个存储数据的内存块 ,有时候需要将其保存在硬盘上,或者发送到网络中传输,此时就需要使用对象的序列化与反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
'''
pickle.dump(obj,file) 将obj写到存储的file中
pickle.undump(file) 从file读取数据,反序列化城对象
'''

import pickle
with open('data.dat','wb') as f:
name = 'ws'
age = 25
score = [100,20,30]
resume = {'name':name, 'age':age, 'score':score}
pickle.dump(resume,f)
# 将resume中的数据,写入f也就是data.dat文件中
# 乱码

with open('data.dat','rb') as f: # 读取这个二进制文件
resume = pickle.load(f) # 载入到resume
print(resume) # 打印resume

CSV文件操作

cvs是逗号分隔符文本文件,常用于数据存储与交换,Excel文件、数据库的导入导出

Excel文件如果另存为csv文件,就是以逗号分隔的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import csv
# 读取
with open(r"d:\a.csv") as a:
a_csv = csv.reader(a) #创建csv对象,它是一个包含所有数据的列表,每一行为一个元素
headers = next(a_csv) #获得列表对象,包含标题行的信息
print(headers)
for row in a_csv: #循环打印各行内容
print(row)

# 写入
with open(r"d:\b.csv","w") as b:
b_csv = csv.writer(b) #创建csv对象
b_csv.writerow(headers) #写入一行(标题)
b_csv.writerows(rows) #写入多行(数据)

os与os.path模块

os模块可以直接对操作系统进行操作,可以直接调用操作系统的可执行文件、命令,直接操作文件、目录等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import os
os.system("ping www.baidu.com")

'''
方法名 描述
remove(path) 删除指定的文件
rename(src,dest) 重命名文件或目录
stat(path) 返回文件的所有属性
listdir(path) 返回path目录下的文件和目录列表
mkdir(path) 创建目录
makedirs(path1/path2/path3/...) 创建多级目录
rmdir(path) 删除目录
removedirs(path1/path2...) 删除多级目录
getcwd() 返回当前工作目录:current work dir
chdir(path) 把path设为当前工作目录
walk() 遍历目录树
sep 当前操作系统所使用的路径分隔符
'''

#测试os模块中,关于文件和目录的操作
import os
#打印基本的信息
print(os.name) #windows-->nt linux-->posix
print(os.sep) #windows-->\ linux-->/
print(repr(os.linesep)) #windows-->\r\n linux-->\n

# repr 把对象转换成字符串
a = '3'
print(a)
print(repr(a)) #repr可以显示数据信息

#获取文件和文件夹的相关信息
print(os.stat("my01.py"))

#获得当前工作目录
print(os.getcwd())
# C:\Users\13209\PycharmProjects\project01

#切换目录chdir-change dir
os.chdir("d:") #当前的工作目录就变成了d:的

#创建目录、删除目录
os.mkdir("ws")
os.rmdir("ws")

#创建多级目录,相当于mkdir -p
os.makedirs("ws/123/qwe")

#重命名
os.rename("ws","xhy")

# 列出子目录,相当于ls
dirs = os.listdir("C:/")
print(dirs)
# ['$360Section', '$Recycle.Bin', '$WINDOWS.~BT', '$Windows.~WS', '$Win...

os.path模块

实现对目录和文件名进行操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
from os import path
#通过__file__属性,可以直接获取当前文件的绝对路径
print(__file__)
#C:\Users\13209\PycharmProjects\first\Study\临时,用以复制粘贴.py

"""
abspath方法——返回指定文件的绝对路径
basename方法——返回指定路径的最后一个部分
dirname方法——返回文件的文件夹
exist方法——判断文件或目录是否存在
isdir方法——判断目录是否存在
isfile方法——判断文件是否存在
split方法——将路径分割为dirname与basename组成的元组
splitext——将指定的路径分割为分拣和扩展名组成的元组
join方法——拼接路径
getctime——返回创建时间
getatime——返回最后访问时间
getmtime——返回最后修改时间
walk——遍历
"""

#abspath方法——返回指定文件的绝对路径
print(path.abspath('test.log'))
#C:\Users\13209\PycharmProjects\first\Study\test.log
#也可以使用相对路径来指定文件

#basename方法——返回指定路径的最后一个部分
print(path.basename('./test1.json'))
#test1.json
#适用于只想要名字的部分

#dirname方法——返回文件的文件夹
print(path.dirname('../Study/test1.json'))
#../Study

#exist方法——判断文件或目录是否存在
print(path.exists('../Study'))
#True 表示文件存在

#isdir方法——检测目录是否存在
print(path.isdir('../Study'))
#isfile方法——检测文件是否存在
print(path.isfile('函数.py'))
#True

#split方法——将路径分割为dirname与basename组成的元组
print(path.split("/Users/13209/AppData/Local/KOOK/KOOK.exe"))
#('/Users/13209/AppData/Local/KOOK', 'KOOK.exe')

#splitext——将指定的路径分割为分拣和扩展名组成的元组
print(path.splitext('/KOOK/KOOK.exe'))
#('/KOOK/KOOK', '.exe')
#可以用来获取文件的类型,用来做判断

#join方法——拼接路径
print(path.join('/ws','xhy/qwe'))
#/ws\xhy/qwe

#
嵌套,动态获取目录
源文件一般在bin下,但是程序根目录应该在bin之上,所以需要进行嵌套
#
import sys
from os import path

PATH = path.dirname(__file__)
sys.path.append(PATH)

if __name__ == '__main__':
print(PATH)
#C:\Users\13209\PycharmProjects\first\Study

切换到上级目录
PATH = path.dirname(path.dirname(path.dirname(__file__)))
#C:\Users\13209\PycharmProjects

列出指定目录下所有的 .py 文件,并输出文件名
import os
import os.path
path = os.getcwd() # 获得当前目录
file_list = os.listdir(path) # 列出目录下的文件和目录
print(file_list)
for file_name in file_list:
pos = file_name.rfind('.') # 从右往左滑
if file_name.endswith('.py'):
print(file_name)

# 使用推导式
file_list2 = [file_name for file_name in os.listdir(path) if file_name.endswith('.py')]
for file_name in file_list2:
print(file_name)

walk遍历

os.walk() 方法是一个简单易用的文件、目录遍历器,可以帮助我们高
效的处理文件、目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
os.walk(top[, topdown=True[, onerror=None[, followlinks=False]]])
其中, top :是要遍历的目录。 topdown :可选, True ,先遍历 top 目录
再遍历子目录。

返回三元组(root、dirs、files):
root :当前正在遍历的文件夹本身
dirs :一个列表,该文件夹中所有的目录的名字
files :一个列表,该文件夹中所有的文件的名字

例子:
import os
path = os.getcwd()

# 注意这里不是 list_files = os.walk(path, topdown=True),而是直接在 for 循环中使用
for root, dirs, files in os.walk(path, topdown=True):
for name in files:
print(os.path.join(root, name)) # 打印当前目录下的文件完整路径
for name in dirs:
print(os.path.join(root, name)) # 打印当前目录下的子目录完整路径
# 注意:这里只是打印子目录路径,而不是递归地遍历它们

# 递归算法,打印目录下的所有文件
import os
import os.path
def print_file(path,level):
child_files = os.listdir(path)
for file in child_files:
file_name = os.path.join(path,file)
print("\t"*level+file_name)
if os.path.isdir(file_name):
print_file(file_name,level+1)
print_file(os.path.dirname(os.getcwd()),level=0)

shutil模块(拷贝与压缩

shutil 模块是python标准库中提供的,主要用来做文件和文件夹的拷
贝、移动、删除等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import shutil
#copy文件内容
shutil.copyfile("a.txt","a_copy.txt")

递归拷贝(复制目录
import shutil
#"音乐"文件夹不存在才能用。
# 忽略以.html和.htm结尾的文件
shutil.copytree("电影/学习","音乐",ignore=shutil.ignore_patterns("*.html","*.htm"))

压缩与解压缩
import shutil
import zipfile
shutil.make_archive('目标位置','压缩方式','压缩文件')
# 压缩锁哥文件,mode=w代表压缩,r代表解压缩
z = zipfile.Zipfile("a.zip","w")
z.write("a.txt")

z2 = zipfile.ZipFile("目标文件","r")
z.write("a.txt")

模块

为了让一个源码文件中定义的函数,可以被另外一个源码文件所引用,就需要使用模块。一个python源文件就是一个模块

模块:提供现成的函数和对象给需要的源码使用
包:多个模块或模块包的集合,包也可以说是一个模块
成员:组成模块或模块包的其中一部分,比如属性、函数都可以称为xx模块包的成员

模块的类型:
1、自己写的源码
2、标准模块(开发者定义的)
3、第三方模块(其他人开发的)

模块化编程的流程

模块化编程的一般流程:

  1. 设计API,进行功能描述。(设计)
  2. 编码实现API中描述的功能。(编码)
  3. 在模块中编写测试代码,并消除全局代码。(测试)
  4. 使用私有函数实现不被外部客户端调用的模块函数。

下面是一个API的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#encoding=utf-8
"""
本模块用于计算公司员工的薪资
"""
company = "xxxxx"

def yearSalary(monthSalary):
"""根据传入的月薪,计算出年薪"""
pass

def daySalary(monthSalary):
"""根据传入的月薪,计算出每天的薪资"""
pass

"""
测试
"""

import加载的模块分为四种类型:

  1. 使用python编写的代码.py文件
  2. 已被编译为共享库或DLL的C或C++扩展
  3. 一组模块的包
  4. 使用C编写并链接到python解释器的内置模块

包(package)的概念和结构

当一个项目中有很多个模块时,需要再进行组织。我们将功能类似的模块放到一起,形成了“包”。本质上,“包”就是一个必须有__init__.py的文件夹。

在pycharm中创建包时,会自动创建带有__init__.py文件的包,在不同级的目录下,可以使用不同的级的模块。

导入包的过程本质上是导入了包的__init__.py文件,也就是执行了__init__.py文件,如果在里面import了包,也会被一并导入

1
2
3
4
5
6
7
import * 语句理论上是希望文件系统找出包中所有的子模块,然后导入它们。这可能会花长时间
Python 解决方案是提供一个明确的包索引:

在__init__.py中定义__all__变量:
__all__=['moduleA','moduleB']

不建议使用import *

Python中库是借用其他编程语言的概念,没有特别具体的定义。

模块和包侧重于代码组织,有明确的定义。库强调的是功能性,而不是代码组织。

通常将某个功能的“模块的集合”,称为库。

Python标准库的主要功能有

  1. 文本处理,包含文本格式化、正则表达式匹配、文本差异计算与合并、Unicode支持,二进制数据处理等功能
  2. 文件处理,包含文件操作、创建临时文件、文件压缩与归档、操作配置文件等功能
  3. 操作系统功能,包含线程与进程支持、IO复用、日期与时间处理、调用系统函数、日志(logging)等功能
  4. 网络通信,包含网络套接字,SSL加密通信、异步网络通信等功能
  5. 网络协议,支持HTTP,FTP,SMTP,POP,IMAP,NNTP,XMLRPC等多种网络协议,并提供了编写网络服务器的框架
  6. W3C格式支持,包含HTML,SGML,XML的处理。
  7. 其它功能,包括国际化支持、数学运算、HASH、Tkinter等

使用模块

下面代码块对以下一些内容进行了说明:模块的基础操作、主函数的作用、python查找模块的顺序、init.py文件的作用、项目目录的创建以及添加环境变量的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#导入模块
#import 模块名
import test1
test1.fun1()
#ws

#可以给模块设置别名 as 关键字
import test1 as ws
ws.fun1()
#ws

#一次导入多个模块,也可以添加别名
import test1 as ws,os as OS ,json as JSON
ws.fun1()
#ws
#import中,默认会导入模块中的所有成员

#在模块中导入指定的成员
#from ... import ...
from test1 import fun1
#直接导入后,可以直接执行,当成自己的函数或属性
fun1()
#ws
#多个导入
from test1 import fun1,fun2
#在模块包中导入某个模块
from asyncio import protocols,tasks
#在模块包中导入某个模块内的成员
from asyncio.protocols import BaseProtocol
#在模块包中导入某个模块内的成员,并设置别名
from asyncio.protocols import BaseProtocol as xx

主函数的作用————将主逻辑与实现区分开
在main中定义的函数,只有当源码作为程序执行时才会执行,在导入时就不会被执行
当文件被当成模块导入时,只会执行main函数以外的函数

python寻找模块的顺序————如何让模块被源码找到
内置模块
环境变量PYTHONPATH指定的模块
标准库,包含了很多有用的模块,例如os、sys等
当前目录:在当前脚本文件所在的目录中查找模块
安装的第三方库,通常是使用pip或其他包管理工具安装的

#__init__.py文件 ———— 是模块的入口,直接导入模块包时将读取这个文件
自己定义模块时,就需要添加__init__.py
可以放入一些备注信息,也可以提供可调用的对象,比如如果在__init__.py定义了函数,那么导入后可以直接使用。
可以将模块包内的成员都引入,使用时就可以使用顶层的模块包进行调用,不需要在用from mod.xxx import xxx的方式了
# 用__init__.py之前
from v1_1 import v1,v2
from v1_1.n1_1 import n1,n2 #n1_1是二层模块
v1.fun()
v2.fun()
v1_1.n1()

#用__init__.py之后,简化源码中的调用过程
#__init__py文件:
from .n1_1 import * #调用所有n1_1内的内容
#源码
import v1_1
v1_1.n1()
#在顶层模块包的__init__.py,可以对二层、三层的模块的__init__.py文件进行引入
#使源码可以直接从顶层模块对底层的模块对象进行调用

#做项目的注意点
#做项目时,首先要创建工程目录
#工程目录的标准化创建思路:
1.将功能不同的代码区分开。比如bin,db,logs,data等目录
bin目录要包含main.py,写程序主体逻辑
2.要有readme文件,作为变量、函数、类的一些说明文件
3.要写帮助文档,即在函数下加""""""

#动态将项目目录添加到环境变量列表
#文件中导入另外的模块,需要将这些项目目录添加到环境变量列表,不然无法找到(参考前面“python寻找模块的顺序”)
#如果环境变量中没有项目目录,那么import引入就会导致错误
#查看操作系统环境变量列表
import sys
print(sys.path)
#['C:\\Users\\13209\\PycharmProjects\\first\\Study', 'C:\\Users\\13209\\PycharmProjects\\first', 'C:\\Users\\13209\\.conda\\envs\\demo\\python37.zip', 'C:\\Users\\13209\\.conda\\envs\\demo\\DLLs', 'C:\\Users\\13209\\.conda\\envs\\demo\\lib', 'C:\\Users\\13209\\.conda\\envs\\demo', 'C:\\Users\\13209\\.conda\\envs\\demo\\lib\\site-packages']
#将路径添加到环境变量中,以桌面举例
PATH='C:\Users\13209\Desktop' #把桌面的路径赋值给PATH
sys.path.append(PATH) #用sys的函数添加,当代码在运行时,临时生效
#再import引入就不会导致异常

常用模块

re模块

#re模块,正则表达式————用于查找和验证
其实正则之前在grep那篇博客里也稍微讲过,这里就比较完整

正则表达式规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#预定义字符
""""
\d 匹配所有十进制数字
\D 匹配所有非数字,包含下划线
\s 匹配所有空白字符,匹配空格
\S 匹配所有非空字符,包括下划线
\w 匹配所有字母,汉字和数字
\W 匹配所有非字母,汉字和数字,包括下划线
"""
eg:
import re
str1 = '[qwesad王]3 214'
chazhao = re.search('\d',str1) #使用re.search方法进行检索
print(chazhao.group())

#特殊字符
"""
$ 结尾
^ 开头
* 可以出现n次
+ 可以出现1次及以上
?使用勉强模式——使其匹配的尽量少,可以为0
. 匹配除了\n以外的所有单个字符
| 或符,二选一进行匹配
[]
"""
eg:
import re
str1 = '[11sw11王11wss1111ws1123]3214'
chazhao = re.findall('ws*',str1) #输出一个列表
print(chazhao)
#['w', 'wss', 'ws']

('ws+',str1)
#['wss', 'ws']

('ws+?',str1)
#['ws', 'ws']

('ws?',str1)
#['w', 'ws', 'ws']

('s.',str1)
#['sw', 'ss', 's1']

('王.|32?',str1)
#['王1', '3', '32']

('[a-z]',str1)
#['s', 'w', 'w', 's', 's', 'w', 's']

('[a-z0-9A-Z]+',str1)
#['11sw11', '11wss1111ws1123', '32W4']

('[^a-z0-9A-Z]+',str1)
#['[', '王', ']']

str1 = '[11sw11王11wss1111ws1123]3214'
chazhao = re.findall('1{1,3}',str1)
#['11', '11', '11', '111', '1', '11']
('1{3,}',str1)
#['1111']
('s1{,3}',str1)
#['s', 's', 's111', 's11']

#合理字符
"""
\t 制表符
\n 换行
\r 回车
\f 分页符
\a 报警符
\e 空格符
"""

#原始字符串r
#解决反斜杠问题,如果想要匹配一个字符串中的\,在re.findall('s1{,3}',str1)需要添加多个斜杠来表示
#例如:
import re
str1='\\234 sdqs'
chazhao = re.search('\\\\\w+',str1)
print(str1)
#\234 sdqs
print(chazhao.group())
#\234
#使用原始字符串r
str2=r'\213 3124'
chazhao2 = re.search(r'\\\w+',str2)
print(str2)
#\213 3124
print(chazhao2.group())
#\213

正则表达式方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import re
#match方法——只从开头进行匹配
str1 = "/data/NFSdata 192.168.8.0/24(rw,sync)"
chazhao = re.match('NFS',str1)
print(chazhao)
#None
chazhao2 = re.match('/data',str1)
print(type(chazhao2)) #如果有,就会返回match类型的对象
#<class 're.Match'>
print(chazhao2.group())
#/data

#search方法——进行单个匹配
chazhao3 = re.search('/NFS\w+',str1)
print(type(chazhao3)) #如果有,就会返回match类型的对象
#<class 're.Match'>
print(chazhao3.group())
#/NFSdata

#findall方法——返回所有匹配的字符串的列表
chazhao4 = re.findall('[a-z]+',str1)
print(chazhao4)
#['data', 'data', 'rw', 'sync']

#finditer方法——返回字符串组成的迭代器
chazhao4 = re.finditer('[a-z]+',str1) #输出一个迭代器
for i in chazhao4: #通过循环进行遍历,进行查看
print(i)
# <re.Match object; span=(1, 5), match='data'>
# <re.Match object; span=(9, 13), match='data'>
# <re.Match object; span=(29, 31), match='rw'>
# <re.Match object; span=(32, 36), match='sync'>

#fullmatch方法——必须完全匹配
chazhao5 = re.fullmatch('\s+',str1) #不完全匹配则返回空值
print(chazhao5)
#None
str2 = '1234542134'
chazhao6 = re.fullmatch('\d+',str2) #完全匹配
print(chazhao6.group())
#1234542134

#sub方法-将匹配的文本替换为其他文本
str1 = '/data/NFSdata 192.168.8.0/24(rw,sync)'
#def sub(pattern, repl, string, count=0, flags=0): 匹配条件、替换值、目标文本、替换次数
str1 = re.sub('\d+','1',str1)
print(str1)
#/data/NFSdata 1.1.1.1/1(rw,sync)

#split方法-从匹配文本处切割,返回切割后的文本的列表
str1 = '/data/NFSdata 192.168.8.0/24(rw,sync)'
#def split(pattern, string, maxsplit=0, flags=0): 匹配条件、目标文本、最大切割次数
list1 = re.split('192.168.8.0/24',str1,maxsplit=0)
print(list1)
#['/data/NFSdata ', '(rw,sync)']

#分组字符()
str1 = '/data/NFSdata 192.168.8.0/24(rw,sync)'
chazhao = re.search('(.*)\s(.*)\((.*)\)',str1) #在筛选条件中我加了(),将其分成了三个分组
print(chazhao)
#<re.Match object; span=(0, 37), match='/data/NFSdata 192.168.8.0/24(rw,sync)'>
#此时re.search返回的是match类型的数据

#group方法——匹配对象的方法,直接查看匹配到的内容
print(chazhao.group(0)) #默认,其本身
#/data/NFSdata 192.168.8.0/24(rw,sync)
print(chazhao.group(1)) #分组索引1
#/data/NFSdata
print(chazhao.group(2)) #分组索引2
#192.168.8.0/24
print(chazhao.group(3))
#rw,sync

#groups方法——匹配对象的方法,以元组方式返回
print(chazhao.groups())
#('/data/NFSdata', '192.168.8.0/24', 'rw,sync')

#groupdict方法——匹配对象的方法,以字典、键值对方式返回,但需要命名
#命名方式(?P<name>条件)
chazhao2 = re.search('(?P<path>.*)\s(?P<ip>.*)\((?P<mod>.*)\)',str1)
print(chazhao2.groupdict())
#{'path': '/data/NFSdata', 'ip': '192.168.8.0/24', 'mod': 'rw,sync'}

#start方法——匹配的文本的起始位索引
#end方法——匹配的文本的结束位索引
#span方法——返回起始位与结束位索引组成的元组
str1 = '1234567qwerty'
chazhao = re.search('\d+',str1)
print(chazhao.start())
print(chazhao.end())
print(chazhao.span())
# 0
# 7
# (0, 7)
chazhao2 = re.search('[a-z]+',str1)
print(chazhao2.start())
print(chazhao2.end())
print(chazhao2.span())
# 7
# 13
# (7, 13)

常用模块random

random伪随机数模块

常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import random

#random-随机产生介于0和1之间的浮点数
print(random.random())
#0.34633936404062204

#uniform-随机产生介于指定值之间的浮点数
print(random.uniform(1.5,1.6)) #大小和前后无所谓;在函数内部,转换为浮点数进行计算
#1.5390638024943262

#randint-随处产生介于指定值之间的整数
print(random.randint(1234,42156)) #要求a>b
#29306

#randrange-随机产生位于范围的数值
print(random.randrange(1,200,10)) #可指定步长
#31
#71
#91

#choice-从有序序列中随机抽取一项
print(random.choice([1,2,5,846,1234,614]))

#shuffle-将一个列表中的元素打乱
list1 = [1,2,3,4,5]
random.shuffle(list1) #shuffle直接在list中修改
print(list1)
#[3, 4, 5, 2, 1]

#sample-从一个序列中取出指定数量的元素进行随机排序
list1 = [1,2,3,4,5,6,7,8,9]
list2 = random.sample(list1,5) #sample需要设置一个对象去进行接受
print(list2)
#[3, 2, 8, 5, 7]

常用模块-JSON

JSON是一种轻量级的数据交换格式,可以用以给程序之间交换数据

JSON为多种语言设计了接口,是一种“通用文本”

JSON的数据存储有两种格式:键值对、数组

常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import json

#dump-将python对象保存为JSON
dict1 = {'name':'王盛'}
filepro=open('test1.json','w') #打开一个文件对象 名为test1.json,给与可写权限
json.dump(dict1,filepro,ensure_ascii=True) #指定一个python对象、指定一个fileproject文件对象
# def dump(obj, fp, *, skipkeys=False, ensure_ascii=True, check_circular=True,
# allow_nan=True, cls=None, indent=None, separators=None,
# default=None, sort_keys=False, **kw):
#{"name": "\u738b\u76db"}
json.dump(dict1,filepro,ensure_ascii=False) #禁止转码
#{"name": "王盛"}
json.dump(dict1,filepro,ensure_ascii=False,indent=2) #顶格2字符
# {
# "name": "王盛"
# }
filepro.close()
#此时查看本目录下的test.json聚能看到已经记录上去

#load-将JSON文件转换成python对象
file=open('test1.json', 'r')
dict1 = json.load(file) #返回一个字典
print(dict1)
#{'name': '王盛'}

#

常用模块-logging

用于在程序中生成日志

日志有等级区分:
1.调试等级 DEBUG —— 反映细节信息,仅诊断问题时用
2.正常等级 INFO —— 确认程序按预期执行
3.告警等级 WARNING —— 表示有意外,但程序依然可以预期执行(默认等级)
4.错误等级 ERROR —— 由于严重问题,某些功能无法正常执行
5.严重错误等级 CRITICAL —— 严重错误,程序无法继续执行

logging.basicConfig方法常用传参

默认会处理WARNING等级以上的错误,1级和2级的错误不会输出到python控制台

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import logging
logging.debug('debug')
logging.warning('warning')
#WARNING:root:warning
#level:user:message

#filename与filemode
logging.basicConfig(filename='test.log',filemode='a') #指定输出文件、指定文件打开方式
logging.warning('warning')
"""
当filename='a',表示为追加进日志文件
当filemode='w',表示清空日志文件后再添加
"""

#level-修改默认日志等级
logging.basicConfig(filename='test.log',filemode='w',level=logging.DEBUG) #指定输出文件、指定文件打开方式
logging.debug('debug')
#DEBUG:root:debug

#format-定义日志格式
"""
%(asctime)s 精确到毫秒
%(name)s 使用的记录器名称,默认root
%(filename)s 日志输出的模块名
%(funcName)s 日志输出的函数名
%(message)s 日志信息
%(levelname)s 日志等级
%(lineno)s 日志所在行号
%(levelno)s 日志等级 10 20 30 40 50
%(pathname)s 日志输出的完整路径
%(process)s 进程ID
%(processName)s 进程名
%(thread)s 线程ID
"""
format1 = '%(levelname)s - %(asctime)s - 第%(lineno)s行:%(message)s'
logging.basicConfig(filename='test.log',filemode='a',level=logging.DEBUG,format=format1)
logging.debug('出现错误')
#DEBUG - 2024-02-19 19:25:33,445 - 第7行:出现错误

#datefmt-传递时间的格式
#样例——默认的时间样式
import logging
logging.basicConfig(
filename="test.log",
format="%(asctime)s:%(message)s",
level=logging.DEBUG
)
logging.debug("debug")
#2024-02-28 12:17:03,305:debug 带了毫秒

logging.basicConfig(
filename="test.log",
format="%(asctime)s:%(message)s",
datefmt="%Y-%m-%d-%H:%M:%S", #改变默认时间的样式
level=logging.DEBUG
)
logging.debug("debug")
#2024-02-28-12:17:58:debug

日志流常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import logging
#日志流
"""
日志流模块组件
记录器Logger——提供api
处理程序Handler——创建的日志发到合适的目的地
过滤器Filter——提供日志的控制工具
格式化程序Formatter——日志的输出格式

logger记录器方法:
xx = logging.getLogger() 创建记录器
logger.setLevel(logging.DEBUG) 设置告警级别
logger.addHandler(handler) 添加处理器
logger.removeHandler(handler) 移除处理器

handler处理器方法:
handler.setLevel(logging.DEBUG) 设置告警级别
handler.setFormatter(formatter) 添加格式化程序
handler.addFilter(filter) 添加过滤器
handler.removeFilter(filter) 移除过滤器

formatter格式化程序方法:
fmt = '%(asctime)s - %(levelname)s - %(message)s'
xx = logging.Formatter(fmt) 创建格式化程
"""

#创建记录器
logger = logging.getLogger()
#设置logger的默认等级
logger.setLevel(logging.DEBUG)
#创建控制台的处理器sh,文件的处理器fh,添加utf-8编码
sh = logging.StreamHandler()
fh = logging.FileHandler(filename='test.log',encoding='utf-8')
#给sh和fh设置消息等级,会覆盖logger的等级
sh.setLevel(logging.ERROR)
fh.setLevel(logging.INFO)
#给logger添加处理器
logger.addHandler(sh)
logger.addHandler(fh)
#创建格式化程序
format1 = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
#给handler添加格式化程序
sh.setFormatter(format1)
fh.setFormatter(format1)

logger.info("logger") #用记录器logger创建debug日志
#此时控制台不会输出,但log内有告警

#logger的复用,可以将其封装为函数,然后return即可
#
#
import logging
def log_fun(filename):
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
fh = logging.FileHandler(filename=filename,encoding='utf-8')
logger.addHandler(fh)
format1 = logging.Formatter('("%(asctime)s - %(levelname)s - %(message)s")')
fh.setFormatter(format1)
return logger

if __name__ == '__main__':
logger = log_fun('test.log')
logger.info('test')
#("2024-02-19 20:58:36,881 - INFO - test")
#("2024-02-19 20:58:51,552 - INFO - test")

常用模块-time和datetime

time模块

是一种主要用来读取和处理时间戳的模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import time

# 常用方法
#time方法——显示当前的时间戳-时间戳:从1970.1.1到现在的总秒数
print(time.time())
#1709039297.5584168
#1709039338.147382
#常用于计算时间差,用操作完的时间戳-开始的时间戳
time1=time.time()
for i in range(100000):
print("")
print(time.time()-time1)
#0.23263192176818848 间隔了0.2秒

#sleep方法——暂停程序,以秒为单位
print("1")
time.sleep(3)
print("2")

#localtime方法——以结构化形式返回时间
"""
将所有信息用关键字的方式返回:
没有传参时,返回当前时间戳的信息
"""
print(time.localtime())
#time.struct_time(tm_year=2024, tm_mon=2, tm_mday=27, tm_hour=21, tm_min=21, tm_sec=31, tm_wday=1, tm_yday=58, tm_isdst=0)
print(time.localtime(1123456))
#time.struct_time(tm_year=1970, tm_mon=1, tm_mday=14, tm_hour=8, tm_min=4, tm_sec=16, tm_wday=2, tm_yday=14, tm_isdst=0)
print(time.localtime(time1))
#time.struct_time(tm_year=2024, tm_mon=2, tm_mday=27, tm_hour=21, tm_min=28, tm_sec=1, tm_wday=1, tm_yday=58, tm_isdst=0)

#mktime方法——将结构化输出变成时间戳进行返回
print(time.mktime(time.localtime()))
#1709040815.0

#strftime方法——将结构化时间转换字符串
#strptime方法——将字符串转变为结构化时间
#def strftime(format, p_tuple=None) #需要输入一个格式来接受这些结构化的时间
#def strptime(string, format): # real signature unknown; restored from __doc__
import time
"""
format参照表:
%y 两位数年份
%Y 四位数年份
%m 月份
%d 月份中的天数
%H 24小时制小时
%I 12小时制小时
%M 分钟数
%S 秒
%a 本地简化星期名,如Tue、Wed等
%A 本地完整星期名
%b 本地简化月份名
%B 本地完整月份名
%c 本地完整的周期、日期、时间、年份表示
%j 一年内的第几天
%p 本地AM或PM表示符,如AM,PM
%U 第几个星期0-53
%w 星期数0-6
%x 本地相应的日期表示
%X 本地相应的时间表示
%Z 时区名称
%% %号
"""
print(time.localtime())
#time.struct_time(tm_year=2024, tm_mon=2, tm_mday=27, tm_hour=22, tm_min=40, tm_sec=35, tm_wday=1, tm_yday=58, tm_isdst=0)
print(time.strftime("%Y-%m-%d %H:%M:%S",time.localtime()))
#2024-02-27 22:41:41

print(time.strptime("2024-02-27", "%Y-%m-%d")) #如果格式不匹配则会报错
#time.struct_time(tm_year=2024, tm_mon=2, tm_mday=27, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=1, tm_yday=58, tm_isdst=-1)

datetime模块

不涉及时间戳的,正常处理时间的模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
"""
datetime中的五个成员类
date——日期对象,常用属性year,month,day
time——时间对象
datetime——是上面两个的集合
timedelta——时间间隔对象
tzinfo——时区信息对象
"""

"""
#import datetime
from datetime import date,time,datetime,timedelta
如果需要同时使用import time,后面导入的函数会覆盖前面导入的
可以设置别名
from datetime import date,time as times,datetime,timedelta
"""

date类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
from datetime import date

#date类
#实例化生成对象
date1 = date(2024,2,27)
print(date1)
#2024-02-27
print(type(date1)) #其本身是一个date类的对象
#<class 'datetime.date'>

#直接通过对象的属性进行访问
date1 = date(2024,2,27)
print(date1.year,date1.month,date1.day)
#2024 2 27

#today——类方法-返回当前日期的日期对象
print(date.today())
#2024-02-27
print(type(date.today()))
#<class 'datetime.date'>

#isoclaendar——返回元组,元素分别返回年、周数、周几(从周一开始算)
print(date1.isocalendar())
#(2024, 9, 2)

#isoweekday-返回对应的星期几
print(date1.isoweekday())
#2

#isoformat-以标准字符串形式返回日期
print(date1.isoformat())
#2024-02-27
print(type(date1.isoformat()))
#<class 'str'>

#replace-替换日期中的属性值“年、月、日”
date1 = date1.replace(year=2077)
print(date1)
#2077-02-27

#fromisoformat-类方法-通过标准日期字符串返回一个日期对象
print(date.fromisoformat("2024-02-27"))
#2024-02-27
print(type(date.fromisoformat("2024-02-27")))
#<class 'datetime.date'>

#timetuple-返回一个结构化时间的元组(可以用来转换为时间戳)
date1 = date(2024,2,27)
print(date1.timetuple())
#time.struct_time(tm_year=2077, tm_mon=2, tm_mday=27, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=5, tm_yday=58, tm_isdst=-1)

datetime类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
datetime同时具有date和time的属性,是他俩的总和
from datetime import date,time,datetime,timedelta
#实例化对象
date1 = datetime(year=2024,month=2,day=27,hour=1,minute=2)
print(date1)
#2024-02-27 01:02:00

#fromtimestamp方法-类方法-通过时间戳返回datetime对象
print(datetime.fromtimestamp(123456456))
#1973-11-30 05:27:36
#utcfromtimestamp方法-类方法-通过时间戳返回格林尼治时间的datetime对象
print(datetime.utcfromtimestamp(123456456))
#1973-11-29 21:27:36 比正常的时间慢八个小时

#now方法-类方法-返回一个现在时间的datetime对象
print(datetime.now())
#2024-02-28 09:35:48.321867
print(datetime.utcnow())
#2024-02-28 01:36:22.404848 格林尼治时间

#conbine方法-类方法-将date对象与time对象结合起来变成datetime对象
date1 = date(2024,2,28)
time1 = time(1,2,3,)
print(datetime.combine(date1,time1))
#2024-02-28 01:02:03

#fromisoformat-类方法-通过一个标准时间日期字符串返回一个datetime对象
print(datetime.fromisoformat("2024-02-28 00:00:00"))
print(type(datetime.fromisoformat("2024-02-28 00:00:00")))
#2024-02-28 00:00:00
#<class 'datetime.datetime'>

#timetuple-返回一个结构化时间元组
date1 = datetime(year=2024,month=2,day=27,hour=1,minute=2)
print(date1.timetuple())
#time.struct_time(tm_year=2024, tm_mon=2, tm_mday=27, tm_hour=1, tm_min=2, tm_sec=0, tm_wday=1, tm_yday=58, tm_isdst=-1)
print(date1.utctimetuple()) #格林尼治时间
#time.struct_time(tm_year=2024, tm_mon=2, tm_mday=27, tm_hour=1, tm_min=2, tm_sec=0, tm_wday=1, tm_yday=58, tm_isdst=0)

#timestamp-返回时间对象的时间戳
date1 = datetime(year=2024,month=2,day=27,hour=1,minute=2)
print(date1.timestamp())
#1708966920.0

#date方法-根据datetime对象返回date对象
#time方法-根据datetime对象返回time对象
date1 = datetime(year=2024,month=2,day=27,hour=1,minute=2)
print(date1.date())
#2024-02-27
print(date1.time())
#01:02:00

#replace方法
date1 = datetime(year=2024,month=2,day=27,hour=1,minute=2)
date1 = date1.replace(second=59)
print(date1)
#2024-02-27 01:02:59

#isoformat方法-将datetime对象转换为字符串
print(date1.isoformat())
#2024-02-27T01:02:59

#today方法=类方法
print(datetime.today())
#2024-02-28 10:34:07.253712

#weekday方法-周一从0开始
#isoweekday方法-周一从1开始
print(date.today().weekday())
#2 是星期三
print(date.today().isoweekday())
#3

#isocalendar-返回一个元组,依次输出年,周数,周几
#同date

#strftime和strptime方法
#对象→字符串 和 字符串→对象
date1 = datetime(year=2024,month=2,day=27,hour=1,minute=2)
print(date1.strftime("%Y-%m-%d"))
#2024-02-27 #字符串类型
date2 = date1.strftime("%Y-%m-%d")
print(date1.strptime(date2, '%Y-%m-%d'))
print(type(date1.strptime(date2, '%Y-%m-%d')))
#2024-02-27 00:00:00
#<class 'datetime.datetime'>

timedelta类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from datetime import date,time,datetime,timedelta
#timedelta类-可以创建时间间隔对象,用以计算间隔的时间
#实例化:可以接收多个参数,且都为float,最好用关键字参数
td1 = timedelta(hours=240,minutes=20,milliseconds=10)
#td类在接受关键字后,会将值自动转换为days,seconds,microseconds这三个属性
print(td1) #列出时间间隔
#10 days, 0:20:00.010000
print(td1.days)
print(td1.seconds)
print(td1.microseconds)
#10
#1200
#10000

datetime1 = datetime(year=2024,month=2,day=28)
datetime2 = datetime(year=2025,month=2,day=28)
print(datetime2 - datetime1) #相同类的对象直接相减,得出时间间隔对象
#366 days, 0:00:00

#用timedelta进行计算
td2=timedelta(hours=10)
print(datetime1 - td2) #用正常时间减去时间间隔
#2024-02-27 14:00:00
print(datetime1 + td2) #用正常时间加上时间间隔
#2024-02-28 10:00:00

并发编程

一些概念

串行并行和并发

  • 串行(serial):一个CPU上,按顺序完成多个任务
  • 并行(parallelism):指的是任务数小于等于cpu核数,即任务一起执行
  • 并发(concurrency):一个CPU采用时间片管理方式,交替的处理多个任务。一般是是任务数多余cpu核数,通过操作系统的各种任务调度算法,实现用多个任务“一起”执行(实际上总有一些任务不在执行,因为切换任务的速度相当快,看上去一起执行而已)

线程、进程和协程

  • 进程(Process):拥有自己独立的堆和栈,既不共享堆,也不共享栈,进程由操作系统调度;进程切换需要的资源很最大,效率低
  • 线程(Thread):拥有自己独立的栈和共享的堆,共享堆,不共享栈,标准线程由操作系统调度;线程切换需要的资源一般,效率一般
  • 协程(coroutine):拥有自己独立的栈和共享的堆,共享堆,不共享栈,协程由程序员在协程的代码里显示调度;协程切换任务资源很小,效率高

同步与异步

同步(synchronous):A调用B,等待B返回结果后,A继续执行

异步(asynchronous ):A调用B,A继续执行,不等待B返回结果;B有结果了,通知A,A再做处理。

线程

  1. 线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位
  2. 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;
  3. 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;
  4. 拥有自己独立的栈和共享的堆,共享堆,不共享栈,标准线程由操作系统调度;
  5. 调度和切换:线程上下文切换比进程上下文切换要快得多。

线程的创建方式

Python的标准库提供了两个模块:_thread和threading,_thread是低级模块,threading是高级模块,对_thread进行了封装。绝大多数情况下,我们只需要使用threading这个高级模块。

创建方式有两种

1.方法包装
2.类包装

线程的执行统一通过start()方法

方法包装创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 线程
#coding=utf-8
from threading import Thread
from time import sleep
def fun1(name):
print(f"{name}启动!")
for i in range(3):
print(f"{name}启动{i}秒")
sleep(1)
print(f"{name}结束")
if __name__ == '__main__':
print("主线程")
# 方法包装——创建线程
t1 = Thread(target=fun1,args=("线程1",))
t2 = Thread(target=fun1,args=("线程2",))
# 启动线程
t1.start()
t2.start()
print("主线程结束")

主线程启动
线程1启动!
线程1启动0秒
线程2启动!主线程结束

线程2启动0秒
线程2启动1秒线程1启动1秒

线程2启动2秒线程1启动2秒

线程1结束线程2结束 # 线程抢夺IO流

可以看出这三个线程都是独立的,互不干扰

类包装创建线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#coding=utf-8
from threading import Thread
from time import sleep
# 通过类创建
class NewThread(Thread):
def __init__(self,name):
Thread.__init__(self)
self.name = name
def run(self):
print(f"{self.name}启动!")
for i in range(3):
print(f"{self.name}启动{i}秒")
sleep(1)
print(f"{self.name}结束")
if __name__ == '__main__':
print("主线程开始")
t1 = NewThread("t1")
t2 = NewThread("t2")
t1.start()
t2.start()
print("主线程结束")

主线程开始
t1启动!
t1启动0秒
t2启动!
t2启动0秒
主线程结束
t1启动1秒t2启动1秒

t1启动2秒t2启动2秒

t2结束t1结束

join()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
等待子线程结束后,再结束主线程
#coding=utf-8
from threading import Thread
from time import sleep
# 通过类创建
class NewThread(Thread):
def __init__(self,name):
Thread.__init__(self)
self.name = name
def run(self):
print(f"{self.name}启动!")
for i in range(3):
print(f"{self.name}启动{i}秒")
sleep(1)
print(f"{self.name}结束")
if __name__ == '__main__':
print("主线程开始")
t1 = NewThread("t1")
t2 = NewThread("t2")

t1.start()
t2.start()
# 与主线程关联
t1.join()
t2.join()
print("主线程结束")

主线程开始
t1启动!
t1启动0秒
t2启动!
t2启动0秒
t1启动1秒
t2启动1秒
t1启动2秒
t2启动2秒
t1结束
t2结束
主线程结束

守护线程

它的生命周期与主线程一致。主线程死亡,它也就随之死亡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#coding=utf-8
from threading import Thread
from time import sleep
# 通过类创建
class NewThread(Thread):
def __init__(self,name):
Thread.__init__(self)
self.name = name
def run(self):
for i in range(3):
print(f"{self.name}{i}")
sleep(1)

if __name__ == '__main__':
print("主线程开始")
# 通过类创建线程
t1 = NewThread(name="t1")
# 设置t1为守护线程
t1.daemon = True
t1.start()
print("主线程结束")


主线程开始
t10主线程结束

GIL全局锁问题

Python的线程无论是几核的CPU,都是只有单线程的。这是一个设计的缺陷

Python Global Interpreter Lock

Python代码的执行由Python 虚拟机(也叫解释器主循环,CPython版本)来控制,Python 在设计之初就考虑到要在解释器的主循环中,同时只有一个线程在执行,即在任意时刻,只有一个线程在解释器中运行。对Python 虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。

线程同步与互斥锁

处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。 这时候,我们就需要用到“线程同步”。 线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用。

使线程内的内容不发生冲突

不加锁的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#coding=utf-8
from threading import Thread
from time import sleep
class Count:
def __init__(self,money,name): # 实例化账户
self.money = money
self.name = name
class Draw(Thread):
def __init__(self,change,account):
Thread.__init__(self)
self.account = account # 账户
self.change = change # 变化的量
self.expenseTotal = 0 # 总额支出
def run(self):
if self.account.money < self.change:
return
sleep(1)
self.account.money -= self.change # 取钱
self.expenseTotal += self.change # 取钱总数量
print(f"账户:{self.account.name},余额:{self.account.money},共取了:{self.expenseTotal}\n")
if __name__ == '__main__':
a1 = Count(100,"王盛") # 实例化账户
change1 = Draw(80,a1) # 指定账户为a1,定义一个取钱线程
change2 = Draw(80,a1) # 指定账户为a1,定义一个取钱线程
change1.start()
change2.start()

# 账户:王盛,余额:20,共取了:80
# 账户:王盛,余额:-60,共取了:80
钱直接被扣成负的了

使用互斥锁

互斥锁: 对共享数据进行锁定,保证同一时刻只能有一个线程去操作。
多个线程一起去抢互斥锁,抢到锁的线程先执行,没有抢到锁的线程需要等待,等互斥锁使用完释放后,其它等待的线程再去抢这个锁。
threading模块中定义了Lock变量,这个变量本质上是一个函数,通过调用这个函数可以获取一把互斥锁。

互斥锁特点

  • 必须使用同一个锁对象
  • 互斥锁的作用就是保证同一时刻只能有一个线程去操作共享数据,保证共享数据不会出现错误问题
  • 使用互斥锁的好处确保某段关键代码只能由一个线程从头到尾完整地去执行
  • 使用互斥锁会影响代码的执行效率
  • 同时持有多把锁,容易出现死锁的情况
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
在上面的案例中,就要保证判断和取钱要在一个线程中不能中断

#coding=utf-8
from threading import Thread,Lock
from time import sleep
class Count:
def __init__(self,money,name): #
self.money = money
self.name = name
class Draw(Thread):
def __init__(self,change,account):
Thread.__init__(self)
self.account = account # 账户
self.change = change # 变化的量
self.expenseTotal = 0 # 总额支出
def run(self):
lock1.acquire() # 要求拿到一个锁
if self.account.money < self.change: # 如果钱不够,就返回钱不够
print("余额不足")
return
sleep(1)
self.account.money -= self.change # 取钱
self.expenseTotal += self.change # 取钱总数量
lock1.release() # 取完钱后释放锁
print(f"账户:{self.account.name},余额:{self.account.money},共取了:{self.expenseTotal}\n")
if __name__ == '__main__':
a1 = Count(100,"王盛") # 实例化账户
lock1 = Lock() # 实例化一个锁
change1 = Draw(80,a1) # 指定账户为a1,定义一个取钱线程
change2 = Draw(80,a1) # 指定账户为a1,定义一个取钱线程
change1.start()
change2.start()

# 账户:王盛,余额:20,共取了:80
# 余额不足

acquire和release方法之间的代码同一时刻只能有一个线程去操作
如果在调用acquire方法的时候 其他线程已经使用了这个互斥锁,那么此时acquire方法会堵塞,直到这个互斥锁释放后才能再次上锁。

死锁问题

在多线程程序中,死锁问题很大一部分是由于一个线程同时获取多个锁造成。比如一个线程同时需要A和B锁才能执行,但B锁被被人使用了,并且也需要A锁。

死锁是由于“同步块需要同时持有多个锁造成”的,要解决这个问题,思路很简单,就是:同一个代码块,不要同时持有两个对象锁。

信号量semaphore

应用场景:
如果用互斥锁,就是限制同一时刻只能有一个线程读取文件。但现在想要让一个线程写,而多个线程同时读文件,就可以用到信号量

底层原理:
信号量底层就是一个内置的计数器。每当资源获取时(调用acquire)计数器-1,资源释放时(调用release)计数器+1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#coding=utf-8
from threading import Semaphore,Thread # 引入线程和信号量
from time import sleep
def home(name,se):
se.acquire() # 获取锁
print(f"{name}进入房间")
sleep(2)
print(f"{name}离开房间")
se.release() # 释放锁

if __name__ == '__main__':
se = Semaphore(2) # 实例化信号量对象,指定信号量
for i in range(1,5):
t = Thread(target=home, args=(f"第{i}个人",se))
t.start()

第1个人进入房间
第2个人进入房间
第1个人离开房间
第3个人进入房间
第2个人离开房间
第4个人进入房间
第4个人离开房间第3个人离开房间

事件event对象

Event 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在初始情况下,event 对象中的信号标志被设置假。如果有线程等待一个 event 对象,而这个 event 对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。

方法名 说明
event.wait(timeout=None) 调用该方法的线程会被阻塞,如果设置了timeout参数,超时后,线程会停止阻塞继续执行;
event.set() 将event的标志设置为True,调用wait方法的所有线程将被唤醒
event.clear() 将event的标志设置为False,调用wait方法的所有线程将被阻塞
event.is_set() 判断event的标志是否为True

生产者与消费者模型

生产者指的是负责生产数据的模块(这里模块可能是:方法、对象、线程、进程)

消费者指的是负责处理数据的模块(这里模块可能是:方法、对象、线程、进程)

消费者不能直接使用生产者的数据,它们之间有个“缓冲区”。生产者将生产好的数据放入“缓冲区”,消费者从“缓冲区”拿要处理的数据。

缓冲区是实现并发的核心,缓冲区的设置有3个好处:

实现线程的并发协作

有了缓冲区以后,生产者线程只需要往缓冲区里面放置数据,而不需要管消费者消费的情况;同样,消费者只需要从缓冲区拿数据处理即可,也不需要管生产者生产的情况。 这样,就从逻辑上实现了“生产者线程”和“消费者线程”的分离。

缓冲区、线程通信——Queue对象

使用queue 库中的队列。创建一个被多个线程共享的 Queue 对象,这些线程通过使用 put() 和 get()操作来向队列中添加或者删除元素

通过这样的方式进行线程之间的通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#coding=utf-8
from queue import Queue
from time import sleep
from threading import Thread

def producer(): # 定义一个生产者
num = 1 # 编号
while True:
if queue.qsize()<=5: # 如果queue缓冲区没满
print(f"生产第{num}个")
queue.put(f"第{num}个") # 用put向队列中添加元素
num += 1
else:
print("缓冲区已满")
sleep(2)
def consumer(): # 定义一个消费者
while True:
print(f"消费{queue.get()}")
sleep(3)

if __name__ == '__main__':
queue = Queue()
t1 = Thread(target=producer)
t2 = Thread(target=consumer)
t1.start()
t2.start()

生产第1个
消费第1个
生产第2个
消费第2个
生产第3个
消费第3个

进程

进程(Process):拥有自己独立的堆和栈,既不共享堆,也不共享栈,进程由操作系统调度;进程切换需要的资源很最大,效率低。对于操作系统来说,一个任务就是一个进程。

特点

  1. 可以使用计算机多核,进行任务的并行执行,提高执行效率
  2. 运行不受其他进程影响,创建方便
  3. 空间独立,数据安全
  4. 进程的创建和删除消耗的系统资源较多

进程的创建

也分为类包装与方法包装两种

Python的标准库提供了模块multiprocessing

方法创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#coding=utf-8
import os
from multiprocessing import Process
from time import sleep

def fun1(name): #使用方法进行进程的创建
print(f"进程{name}开始")
print(f"当前进程ID{os.getpid()}")
print(f"父进程ID{os.getppid()}")
sleep(3)
print(f"进程{name}结束")
if __name__ == '__main__':
print(f"当前进程ID{os.getpid()}")
# 创建进程
p1 = Process(target=fun1,args=("p1",)) # 创建
p2 = Process(target=fun1,args=("p2",)) # 创建第二个进程
p1.start()
p2.start()
'''
当前进程ID27652
进程p1开始
当前进程ID26476
进程p2开始
当前进程ID27660
父进程ID27652
父进程ID27652
进程p1结束
进程p2结束
'''

类创建

与Thread类创建线程差不多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#coding=utf-8
from multiprocessing import Process
from time import sleep
class MyProcess(Process):
def __init__(self,name): # 重写构造方法
Process.__init__(self) # 引用Process的构造方法
self.name = name
def run(self):
print(f"进程{self.name}Running")
sleep(3)
print(f"进程{self.name}stoping")
if __name__ == '__main__':
p1 = MyProcess("p1")
p2 = MyProcess("p2")
p1.start()
p2.start()
'''
进程p1Running
进程p2Running
进程p1stoping
进程p2stoping
'''

进程通信

使用queue队列实现进程通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#coding=utf-8
from multiprocessing import Process, Queue
class MyProcess(Process):
def __init__(self,name,mq):
Process.__init__(self)
self.name = name
self.mq = mq
def run(self):
print(f"进程{self.name}开始")
print(f"取得数据{self.mq.get()}")
self.mq.put(self.name)
print(f"进程{self.name}结束")

if __name__ == '__main__':
mq = Queue() # 实例化一个queue对象,名字叫mq
mq.put("1")
mq.put("2")
mq.put("3") # 主进程中在mq中放入数据
p_list = [] # 进程列表
for i in range(3):
p = MyProcess(f"p{i}",mq) # 把mq传进去,不然不同进程就无法找到这个对象
p_list.append(p)
for p in p_list:
p.start()
for p in p_list: # 等待子进程结束后再结束主进程
p.join()
print(mq.get()) # 取mq中的值
print(mq.get())
print(mq.get())
"""
进程p0开始
取得数据1
进程p0结束
进程p1开始
取得数据2
进程p1结束
进程p2开始
取得数据3
进程p2结束
p0
p1
p2
"""

pipe管道实现进程通信

Pipe方法返回(conn1, conn2)代表一个管道的两个端。

若duplex参数为True(默认值),那么这个参数是全双工模式
若duplex为False,conn1只负责接收消息,conn2只负责发送消息

例:在全双工模式下,可以调用conn1.send发送消息,conn1.recv接收消息。如果没有消息可接收,recv方法会一直阻塞。如果管道已经被关闭,那么recv方法会抛出EOFError。

总的来说用得不多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#coding=utf-8
import multiprocessing
from time import sleep

def func1(conn1):
sub_info = "这是conn1端" # 定义或者引用一个要传送的信息
print(f"进程1--{multiprocessing.current_process().pid}发送数据:{sub_info}")
sleep(1)
conn1.send(sub_info) # conn1发送数据
print(f"来自进程2:{conn1.recv()}") # 收取conn2的数据
sleep(1)
def func2(conn2):
sub_info = "这是conn2端" # 定义或者引用一个要传送的信息
print(f"进程2--{multiprocessing.current_process().pid}发送数据:{sub_info}")
sleep(1)
conn2.send(sub_info)# conn2发送数据
print(f"来自进程1:{conn2.recv()}\n") # 收取conn2的数据
sleep(1)

if __name__ == '__main__':
#创建管道,分别是两端
conn1,conn2 = multiprocessing.Pipe()
# 创建子进程
process1 = multiprocessing.Process(target=func1,args=(conn1,))
process2 = multiprocessing.Process(target=func2,args=(conn2,))
# 启动子进程
process1.start()
process2.start()

'''
进程1--32748发送数据:这是conn1端
进程2--14432发送数据:这是conn2端
来自进程2:这是conn2端
来自进程1:这是conn1端
'''

manager管理器实现进程通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#coding=utf-8
from multiprocessing import Manager, Process
def fun1(name,m_list,m_dict): # 子进程中修改共享对象
m_dict['name'] = "ws"
m_list.append("hello")
if __name__ == '__main__':
with Manager() as mgr: # 创建一个Manager实例
m_list = mgr.list() # 定义一个共享列表m_list
m_dict = mgr.dict() # 定义一个共享字典m_dict
m_list.append('Hello!!')
# 两个进程不能直接互相使用对象,需要互相传递
p1 = Process(target=fun1, args=('p1', m_list, m_dict)) # 创建一个子进程
p1.start()
p1.join() # 等p1进程结束,主进程继续执行
print(m_list)
print(m_dict)

进程池

进程池可以提供指定数量的进程给用户使用,即当有新的请求提交到进程池中时,如果池未满,则会创建一个新的进程用来执行该请求;反之,如果池中的进程数已经达到规定最大值,那么该请求就会等待,只要池中有进程空闲下来,该请求就能得到执行。

相当于定义了进程的上限数量

这几个进程的进程号是一致的,用完就会还回去

  1. 提高效率,节省开辟进程和开辟内存空间的时间及销毁进程的时间
  2. 节省内存空间
类/方法 功能 参数
Pool(processes) 创建进程池对象 processes表示进程池中有多少进程
pool.apply_async(func,args,kwds) 异步执行 ;将事件放入到进程池队列 func 事件函数 args 以元组形式给func传参kwds 以字典形式给func传参 返回值:返回一个代表进程池事件的对象,通过返回值的get方法可以得到事件函数的返回值
pool.apply(func,args,kwds) 同步执行;将事件放入到进程池队列 func 事件函数 args 以元组形式给func传参 kwds 以字典形式给func传参
pool.close() 关闭进程池
pool.join() 回收进程池
pool.map(func,iter) 类似于python的map函数,将要做的事件放入进程池 func 要执行的函数 iter 迭代对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#coding=utf-8
from multiprocessing import Pool
import os
from time import sleep
def func1(name):
print(f"当前进程的ID:{os.getpid()},{name}")
sleep(2)
return name

def func2(args):
print(args)

if __name__ == "__main__":
pool = Pool(5) # 实例化一个进程池,设置最多包含五个进程

pool.apply_async(func = func1,args=('sxt1',),callback=func2) # 结束时执行func2
pool.apply_async(func = func1,args=('sxt2',),callback=func2)
pool.apply_async(func = func1,args=('sxt3',),callback=func2)
pool.apply_async(func = func1,args=('sxt4',))
pool.apply_async(func = func1,args=('sxt5',))
pool.apply_async(func = func1,args=('sxt6',))
pool.apply_async(func = func1,args=('sxt7',))
pool.apply_async(func = func1,args=('sxt8',))

pool.close()
pool.join()

'''
当前进程的ID:525344,sxt1
当前进程的ID:524032,sxt2
当前进程的ID:523960,sxt3
当前进程的ID:493052,sxt4
当前进程的ID:523844,sxt5
当前进程的ID:525344,sxt6 # sxt6和sxt1的进程号是一致的
sxt1
当前进程的ID:524032,sxt7
sxt2
sxt3
当前进程的ID:523960,sxt8
'''

使用with管理线程池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#coding=utf-8
from multiprocessing import Pool
import os
from time import sleep
def func1(name):
print(f"当前进程的ID:{os.getpid()},{name}")
sleep(2)
return name
if __name__ == "__main__":
with Pool(5) as pool: # 使用with管理
# pool.map函数式编程,将后面的参数以此赋值给func1
# 会将返回的数据放到args中
args = pool.map(func1,('sxt1,','sxt2,','sxt3,','sxt4,','sxt5,','sxt6,','sxt7,','sxt8,'))
for a in args:
print(a)

协程

协程,全称是“协同程序”,用来实现任务协作。是一种在线程中,比线程更加轻量级的存在,由程序员自己写程序来管理。

当出现IO阻塞时,CPU一直等待IO返回,处于空转状态。这时候用协程,可以执行其他任务。当IO返回结果后,再回来处理数据。充分利用了IO等待的时间,提高了效率。

控制流的让出和恢复

  1. 每个协程有自己的执行栈,可以保存自己的执行现场
  2. 可以由用户程序按需创建协程(比如:遇到io操作)
  3. 协程“主动让出(yield)”执行权时候,会保存执行现场(保存中断时的寄存器上下文和栈),然后切换到其他协程
  4. 协程恢复执行(resume)时,根据之前保存的执行现场恢复到中断前的状态,继续执行,这样就通过协程实现了轻量的由用户态调度的多任务模型

协程的优点

  1. 由于自身带有上下文和栈,无需线程上下文切换的开销,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级;
  2. 无需原子操作的锁定及同步的开销;
  3. 方便切换控制流,简化编程模型
  4. 单线程内就可以实现并发的效果,最大限度地利用cpu,且可扩展性高,成本低(注:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理)

协程的缺点

  1. 无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU的多个核用上,协程需要和进程配合才能运行在多CPU上。
  2. 当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。

asyncio异步IO实现协程

  1. 正常的函数执行时是不会中断的,所以要写一个能够中断的函数,需要加async
  2. async 用来声明一个函数为异步函数,异步函数的特点是能在函数执行过程中挂起,去执行其他异步函数,等到挂起条件(假设挂起条件是sleep(5))消失后,也就是5秒到了再回来执行
  3. await 用来用来声明程序挂起,比如异步程序执行到某一步时需要等待的时间很长,就将此挂起,去执行其他的异步程序。
  4. asyncio是python3.5之后的协程模块,是python实现并发重要的包,这个包使用事件循环驱动实现并发。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#coding=utf-8
import asyncio
import time
async def func1(): #定义一个异步方法
for i in range(3):
print(f'北京:第{i}次打印')
await asyncio.sleep(1) # 挂起一秒
return "func1执行完毕"
async def func2():
for k in range(3):
print(f'上海:第{k}次打印' )
await asyncio.sleep(1) # 挂起一秒
return "func2执行完毕"
async def main(): # main也需要定义成异步
res = await asyncio.gather(func1(), func2()) # 将func1和2传入,表示交替执行fun1和2
#await异步执行func1方法
#返回值为函数的返回值列表,返回给res
print(res)
if __name__ == '__main__':
start_time = time.time()
asyncio.run(main()) # 执行异步函数main
end_time = time.time()
print(f"耗时{end_time-start_time}")

网络编程

基础概念

TCP

TCP(Transmission Control Protocol,传输控制协议)。TCP方式就类似于拨打电话,使用该种方式进行网络通讯时,需要建立专门的虚拟连接,然后进行可靠的数据传输,如果数据发送失败,则客户端会自动重发该数据。

UDP

UDP(User Data Protocol,用户数据报协议)

UDP是一个非连接的协议,传输数据之前源端和终端不建立连接, 当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。 在发送端,UDP传送数据的速度仅仅是受应用程序生成数据的速度、 计算机的能力和传输带宽的限制; 在接收端,UDP把每个消息段放在队列中,应用程序每次从队列中读一个消息段。

UDP 因为没有拥塞控制,一直会以恒定的速度发送数据。即使网络条件不好,也不会对发送速率进行调整。这样实现的弊端就是在网络条件不好的情况下可能会导致丢包,但是优点也很明显,在某些实时性要求高的场景(比如电话会议)就需要使用 UDP 而不是 TCP

TCP和UDP的区别

这两种传输方式都在实际的网络编程中使用,重要的数据一般使用TCP方式进行数据传输,而大量的非核心数据则可以通过UDP方式进行传递,在一些程序中甚至结合使用这两种方式进行数据传递。

由于TCP需要建立专用的虚拟连接以及确认传输是否正确,所以使用TCP方式的速度稍微慢一些,而且传输时产生的数据量要比UDP稍微大一些。

  • TCP是面向连接的,传输数据安全,稳定,效率相对较低。
  • UDP是面向无连接的,传输数据不安全,效率较高。
UDP TCP
是否连接 无连接 面向连接
是否可靠 不可靠传输,不使用流量控制和拥塞控制 是否可靠
连接对象个数 支持一对一,一对多,多对一和多对多交互通信 连接对象个数
传输方式 面向报文 传输方式
首部开销 首部开销小,仅8字节 首部开销
适用场景 适用于实时应用(IP电话、视频会议、直播等) 适用场景

TCP建立连接与断开连接

三次握手

第一步:客户端发送一个包含SYN即同步(Synchronize)标志的TCP报文,SYN同步报文会指明客户端使用的端口以及TCP连接的初始序号。

第二步:服务器在收到客户端的SYN报文后,将返回一个SYN+ACK的报文,表示客户端的请求被接受,同时TCP序号被加一,ACK即确认(Acknowledgement)

第三步:客户端也返回一个确认报文ACK给服务器端,同样TCP序列号被加一,到此一个TCP连接完成。然后才开始通信的第二步:数据处理。

四次挥手

第一步: 当主机A完成数据传输后,将控制位FIN置1,提出停止TCP连接的请求 ;

第二步: 主机B收到FIN后对其作出响应,确认这一方向上的TCP连接将关闭,将ACK置1;

第三步: 由B 端再提出反方向的关闭请求,将FIN置1 ;

第四步: 主机A对主机B的请求进行确认,将ACK置1,双方向的关闭结束.。

数据包结构

套接子编程概述

socket编程

TCP协议和UDP协议是传输层的两种协议。Socket是传输层供给应用层的编程接口,分为TCP编程和UDP编程两类。

Socket编程封装了常见的TCP、UDP操作,可以实现非常方便的网络编程。

socket()函数

通过使用socket模块提供的socket对象,可以在计算机网络中建立可以互相通信的服务器与客户端。在服务器端需要建立一个socket对象,并等待客户端的连接。客户端使用socket对象与服务器端进行连接

1
2
3
4
5
6
7
8
9
10
11
12
13
Python 中,通常用一个Socket表示“打开了一个网络连接”
socket.socket([family[, type[, protocol]]])

family: 套接字家族可以使AF_UNIX或者AF_INET;
AF 表示ADDRESS FAMILY 地址族
AF_INET(又称 PF_INET)是 IPv4 网络协议的套接字类型;
AF_UNIX 则是 Unix 系统本地通信

type: 套接字类型
如果是TCP 用SOCK_STREAM
如果是UDP 用SOCK_DGRAM

protocol: 一般不填,默认为0

一些常用socket函数列举

函数 功能
服务器端套接字函数
s.bind() 绑定地址(host,port)到套接字, 在AF_INET下,以元组(host,port)的形式表示地址。
s.listen() 开始TCP监听。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。
s.accept() 被动接受TCP客户端连接,(阻塞式)等待连接的到来
客户端套接字
s.connect() 主动初始化TCP服务器连接,。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
s.recv() 接收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。
s.send() 发送TCP数据,将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。
s.sendall() 完整发送TCP数据,完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
s.recvfrom() 接收UDP数据,与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。
s.sendto() 发送UDP数据,将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。
s.close() 关闭套接字
s.getpeername() 返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
s.getsockname() 返回套接字自己的地址。通常是一个元组(ipaddr,port)
s.setsockopt(level,optname,value) 设置给定套接字选项的值。
s.getsockopt(level,optname[.buflen]) 返回套接字选项的值。
s.settimeout(timeout) 设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())
s.gettimeout() 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。
s.fileno() 返回套接字的文件描述符。
s.setblocking(flag) 如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。
s.makefile() 创建一个与该套接字相关连的文件

UDP编程

简单例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
服务端
#coding=UTF-8
from socket import *
# 服务端接收数据
s = socket(AF_INET,SOCK_DGRAM) # 创建UDP类型的套接子
s.bind(("127.0.0.1",8000)) # 绑定接受信息的端口,传入一个元组
print("等待接收中")
recv_data = s.recvfrom(1024) # 接收数据,最大接受1024字节
# recv_data是一个返回的元组,因为发送的信息也是元组
print(f"收到{recv_data[0].decode('gbk')},来自{recv_data[1]}") # 接受的时候用gbk解码
s.close()

客户端
#coding=UTF-8
from socket import *
s = socket(AF_INET, SOCK_DGRAM) # 创建一个套接子
address = ("127.0.0.1",8000) # 定义目标地址和端口
data = input("要发送的数据:") # 输入一个发送的数据
s.sendto(data.encode("gbk"),address) # 发送数据,同时对data进行编码处理
s.close() # 关闭接口

运行结果如图

UDP持续通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#coding=UTF-8
# 服务端
from socket import *
s = socket(AF_INET, SOCK_DGRAM)
s.bind(("127.0.0.1",8000)) # 绑定ip端口,以元组形式传入
print("Waiting for connection...")
while True:
recv_data = s.recvfrom(1024) #最大接受字节数1024
print(f"收到信息:{recv_data[0].decode('gbk')},从{recv_data[1]}")
if recv_data[0].decode('gbk') == 'EOF':
print("结束")
break

#coding=UTF-8
# 客户端
from socket import *
s = socket(AF_INET, SOCK_DGRAM)
addr = ("127.0.0.1",8000) # 目标地址和端口
while True:
data = input(f"向{addr[0]}发送信息:")
s.sendto(data.encode("gbk"),addr)
if data == "EOF":
print("结束")
break

UDP多线程双工通信

在这种情况下服务端和客户端的区分就不是很明确了,只要调换发送口和接收口即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#coding=UTF-8
# 服务器端
from socket import *
from threading import Thread
# 定义收发函数的方法,将其放到线程中运行
def recv_data(): #
while True:
recv_data = s.recvfrom(1024)
print(f"收到信息:{recv_data[0].decode('gbk')},from{recv_data[1]}")
def send_data():
while True:
data = input("发送信息:")
address = ("127.0.0.1",8002)
s.sendto(data.encode('gbk'),address)
if __name__ == '__main__':
s = socket(AF_INET, SOCK_DGRAM)
s.bind(("127.0.0.1",8000))
t1 = Thread(target=recv_data)
t2 = Thread(target=send_data)
t1.start()
t2.start()
t1.join()
t2.join()

#coding=UTF-8
# 客户端
from socket import *
from threading import Thread
# 定义收发函数的方法,将其放到线程中运行
def recv_data(): #
while True:
recv_data = s.recvfrom(1024)
print(f"收到信息:{recv_data[0].decode('gbk')},from{recv_data[1]}")
def send_data():
while True:
data = input("发送信息:")
address = ("127.0.0.1",8000)
s.sendto(data.encode('gbk'),address)

if __name__ == '__main__':
s = socket(AF_INET, SOCK_DGRAM)
s.bind(("127.0.0.1",8002))
t1 = Thread(target=recv_data)
t2 = Thread(target=send_data)
t1.start()
t2.start()
t1.join()
t2.join()

TCP编程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
在UDP中,因为没有连接,所以在每次发送都需要添加对端的ip和port

创建Socket服务器程序的步骤:
(1)创建Socket对象
(2)绑定端口号
(3)监听端口号
(4)等待客户端Socket的连接
(5)读取客户端数据
(6)向客户端发送数据
(7)关闭客户端socket
(8)关闭服务端socket

#coding=UTF-8
from socket import *
# 服务器端
server_socket = socket(AF_INET, SOCK_STREAM) # tcp socket
server_socket.bind(("127.0.0.1",8999)) # 监听本机8999端口
server_socket.listen(5) # 等待5秒
print("等待连接")
# accept是一个阻塞的方法,等待连接,将连接信息赋值给
client_socket,client_info = server_socket.accept()
# client_socket代表新客户端,info代表ip和port
recv_data = client_socket.recv(1024) # 最大接受1024字节
print(f"收到信息{recv_data.decode('gbk')},来源{client_info}")
client_socket.close()
server_socket.close()

#coding=UTF-8
from socket import *
# 客户端
client_socket = socket(AF_INET, SOCK_STREAM)
#发起一个连接
client_socket.connect("127.0.0.1",8999)
# 发送消息
client_socket.send("wangsheng".encode("utf-8"))
client_socket.close()

运行之后

TCP双向通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#coding=UTF-8
from socket import *
# 服务器端
server_socket = socket(AF_INET, SOCK_STREAM) # tcp socket
server_socket.bind(("127.0.0.1",8999)) # 监听本机8999端口
server_socket.listen(5) # 等待5秒
print("等待连接")
client_socket,client_info = server_socket.accept()
print("客户端连接成功")
while True: # 搞一个循环来收发消息客户端消息
recv_data = client_socket.recv(1024).decode("gbk")
print(f"收到消息{recv_data},来源{client_info}")
if recv_data == "EOF":
print("结束")
break
msg = input("发送消息:")
client_socket.send(msg.encode("gbk"))

client_socket.close()
server_socket.close()

#coding=UTF-8
from socket import *
# 客户端
client_socket = socket(AF_INET, SOCK_STREAM)
#发起一个连接
client_socket.connect(("127.0.0.1",8999))
print("成功连接")
# 发送消息
while True:
msg = input("发送消息:")
client_socket.send(msg.encode("gbk"))
if msg == "EOF":
break
# 接收信息
recv_data = client_socket.recv(1024).decode("gbk")
print(f"服务器端消息:{recv_data}")
client_socket.close()

这种情况只能一问一答,

TCP多线程双工通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#coding=UTF-8
from socket import *
from threading import Thread
# 服务器端
# 定义两个函数,分别管收发
def recv_data():
while True:
re_data = tcp_client_socket.recv(1024).decode("gbk")
print(f"B:{re_data}")
if re_data == 'EOF':
break
def send_data():
while True:
msg = input(">")
tcp_client_socket.send(msg.encode("gbk"))
if msg == "EOF":
break
if __name__ == '__main__':
tcp_server_socket = socket(AF_INET, SOCK_STREAM)
tcp_server_socket.bind(("127.0.0.1",9000)) # 绑定端口
tcp_server_socket.listen(5) # 等待五秒
print("等待连接")
tcp_client_socket,host = tcp_server_socket.accept()
print("连接成功")
t1 = Thread(target=recv_data)
t2 = Thread(target=send_data)
t1.start()
t2.start()
t1.join()
t2.join()
tcp_client_socket.close()
tcp_server_socket.close()

#coding=UTF-8
from socket import *
from threading import Thread
# 客户端
# 定义两个函数,分别管收发
def recv_data():
while True:
recv_data = tcp_client_socket.recv(1024).decode("gbk")
print(f"A:{recv_data}")
if recv_data == 'EOF':
break
def send_data():
while True:
msg = input(">")
tcp_client_socket.send(msg.encode("gbk"))
if msg == 'EOF':
break
if __name__ == '__main__':
tcp_client_socket = socket(AF_INET, SOCK_STREAM)
tcp_client_socket.connect(("127.0.0.1",9000))
print("连接成功")
t1 = Thread(target=recv_data)
t2 = Thread(target=send_data)
t1.start()
t2.start()
t1.join()
t2.join()
tcp_client_socket.close()

两边都往对方发送EOF时,双线程都会结束

函数式编程

函数式编程的一些关键概念

偏函数

作用就是把一个函数某些参数固定住(也就是设置默认值),返回一个新的函数,调用这个新的函数会更简单。

1
2
3
4
5
6
7
8
9
10
比如int8进制数字,需要使用
int("123",base=8)
全都要转8进制就要全都带base参数

现在有functools.partial偏函数就可以直接固定base值
#coding=utf-8
import functools
int2 = functools.partial(int, base=2) #定义一个偏函数,使用base=2的参数
print(int2('1000000')) #64
print(int2('1000000', base=10)) #也可以修改base的值

闭包

  • 局部变量:如果名称绑定再一个代码块中,则为该代码块的局部变量,除非声明为nonlocal或global
  • 全局变量:如果模块绑定在模块层级,则为全局变量
  • 自由变量:如果变量在一个代码块中被使用但不是在其中定义,则为自由变量

闭包是一个函数,而且存在于另一个函数当中,可以访问另一个函数的作用域

闭包可以访问到父级函数的变量,且该变量不会销毁

闭包的内存分析

在inner中调用了outer的a变量,返回inner时,a对象不会随着outer关闭而消失,而是由inner继续指向

优点:

1.隐藏变量,避免全局污染

2.可以读取函数内部的变量

使用不当的缺点:

1.导致变量不会被垃圾回收机制回收,造成内存消耗

2.可能会造成内存泄漏的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
使用闭包在不侵入代码的前提下添加功能
# coding=utf-8

def outfunc(func):
def infunc(*args,**kwargs):
print("日志纪录 start...")
func(*args,**kwargs)
print("日志纪录 end...")
return infunc

def fun1():
print("使用功能1")

def fun2(a,b,c):
print("使用功能2",a,b,c)

print(id(fun1))
fun1 = outfunc(fun1)
print(id(fun1))
fun1()
fun2 = outfunc(fun2)
fun2(100,200,300)

1853641106240
1853641546208
日志纪录 start...
使用功能1
日志纪录 end...
日志纪录 start...
使用功能2 100 200 300
日志纪录 end...

map函数

是一个内置函数,接收两种参数,函数与序列。map将传入的函数依次作用到序列的每个元素,并把结果作为新的list返回

如果要将函数作用在序列上,就可以这么操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def f(x):
return x * x
L = []
for n in [1, 2, 3, 4, 5, 6, 7, 8, 9]:
L.append(f(n))
print(L)

# [1, 4, 9, 16, 25, 36, 49, 64, 81]

def f2(x,y):
return x+y
L=map(f2,[1,2,3,4],[10,20,30])
print(list(L))

# [11, 22, 33]

reduce函数

属于funtools模块

reduce把一个函数作用在一个序列[x1, x2, x3…]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算

1
2
3
4
5
6
# 序列求和
from functools import reduce
def add(x, y):
return x + y
sum=reduce(add, [1, 3, 5, 7, 9])
print(sum)

filter函数

内置函数,用于过滤序列,把传入的函数依次作用于每个元素,然后根据返回值是True还是False, 决定保留还是丢弃该元素。

1
2
3
4
5
6
7
8
#coding=utf-8
def is_odd(n):
return n % 2 == 1 #
L=filter(is_odd, [1, 2, 4, 5])
print(list(L))

# [1, 5]
删掉偶数,只留奇数

装饰器

装饰器来自 Decorator 的直译。什么叫装饰,就是装点、提供一些额外的功能。在 python 中的装饰器则是提供了一些额外的功能。

装饰器本质上是一个Python函数(其实就是闭包),它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。

装饰器用于有以下场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
不适用装饰器:
def outfunc(func):
def infunc():
func()
print("日志纪录")
return infunc
def fun1():
print("使用功能1")
def fun2():
print("使用功能2")

log1 = outfunc(fun1)
log2 = outfunc(fun2)
fun1()
fun2()

使用装饰器:
def log_module(func):
def inner():
func()
print("日志")
return inner
@log_module
def log1():
print("使用功能1")
@log_module
def log2():
print("使用功能2")
# 调用使用装饰器的方法
fun1()
fun2()

多个装饰器执行顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@mylog
@cost_time
def fun1():
pass
等同于
fun2 = cost_time(fun1)
fun2 = mylog(fun1)
等同于
fun2 = mylog(cost_time(fun2))

先执行cost_time函数,再执行mylog函数

#coding=utf-8
import time
def mylog(func):
def inner():
print("开始记录日志")
func()
print("结束记录日志")
return inner
def cost_time(func):
print("cost_time start")
def inner():
print("开始计时")
start = time.time()
func()
end = time.time()
print(f"花费时间{end-start}")
return end-start
print("cost_time end")
return inner

@mylog
@cost_time
def fun1():
print("开始测试")
time.sleep(2)
print("结束测试")
fun1()

'''
计时结束
开始记录日志
开始计时
开始测试
结束测试
花费时间2.001138925552368
结束记录日志
'''

带参数的装饰器

在两层封装的装饰器中,因为固定有一层需要写被返回的函数。必须多封装一层,用以传递额外的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# coding=utf-8
def mylog(type):
def decorator(func):
def infunc(*args,**kwargs):
if type=="文件":
print("文件中:日志纪录")
else:
print("控制台:日志纪录")
return func(*args,**kwargs)
return infunc
return decorator

@mylog("文件")
def fun2(a,b):
print("使用功能2",a,b)

if __name__ == '__main__':
fun2(100,200)

wraps装饰器

一个函数不止有他的执行语句,还有着 name__(函数名),__doc (说明文档)等属性,我们之前的例子会导致这些属性改变。

functool.wraps可以将原函数对象的指定属性赋值给包装函数对象,默认有module、name、doc,或者通过参数选择。

1
2
3
4
5
6
7
8
9
10
11
在使用装饰器的过程中,最外层的函数(在主函数中调用的函数)并不是实际上执行的函数
在内存中都是指向了装饰器中的函数。
可以使用wraps装饰器:

def mylog(func):
@wraps(func)
def infunc(*args,**kwargs):
print("日志纪录...")
print("函数文档:",func.__doc__)
return func(*args,**kwargs)
return infunc

内置装饰器property、staticmethod、classmethod

property装饰器

用于像访问属性一样来获取一个函数的返回值

1
2
3
4
5
6
7
8
9
10
11
12
class User:
def __init__(self,name,month_salary):
self.name = name
self.month_salary = month_salary

@property
def year_salary(self):
return int(self.month_salary)*12

if __name__ == '__main__':
u1 = User("ws","1800")
print(u1.year_salary) # 返回年薪

staticmethod装饰器

用于类中的静态方法,不经实例化也可以直接调用

1
2
3
4
5
6
class Person:
@staticmethod
def say_hello():
print("hello world!")
if __name__ == '__main__':
Person.say_hello()

classmethod装饰器

用于类方法,传入cls。不经过实例化也可以直接使用类来调用。和staticmethod的区别就是

需要传入参数,所以也需要在最开始的类初始化方法中定义参数

1
2
3
4
5
6
7
8
9
10
#coding=UTF-8
class Person:
def __init__(self,name):
self.name = name
@classmethod
def say_hello(cls,name):
print(f"我是{name}")
print("hello world!")
if __name__ == '__main__':
Person.say_hello("王盛")

类装饰器

类能实现装饰器的功能, 是由于当我们调用一个对象时,实际上调用的是它的 call 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Demo:
def __call__(self):
print('我是 Demo')
demo = Demo()
demo() # 直接调用对象,实质是调用了他的__call__()

类装饰器的使用
fun1 函数是作为参数传递给了 NewClass 类的构造函数 __init__ 方法
而不是 __call__ 方法。但当之后调用 fun1() 时,调用NewClass 实例的 __call__ 方法
#coding=UTF-8
class NewClass():
def __init__(self,func): # 写构造方法
self.func = func
def __call__(self,*args,**kwargs): # 写call方法
print("记录")
return self.func(*args,**kwargs)
@NewClass # fun1 = NewClass(fun1)
def fun1():
print(f"使用fun1")
if __name__ == '__main__':
fun1()

案例:缓存装饰器、计时装饰器

思路:通过time设计计时器,通过定义cache字典来做缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
对于同个输入没有必要重新执行,直接使用缓存输出结果即可
#coding=utf-8
from time import sleep,time
def cost_time(func):
def inner(*args, **kwargs):
start = time()
result = func(*args,**kwargs)
end = time()
print(f"耗时为{end-start}")
return result
return inner
class Cache():
__cache={} # 创建一个字典,把方法名为key,输出值为value
def __init__(self,func):
self.func = func
def __call__(self,*args,**kwargs):
# 如果缓存中有方法名,则返回对应值
if self.func.__name__ in Cache.__cache:
return Cache.__cache[self.func.__name__]
else: # 如果没有则进行计算
result = self.func(*args, **kwargs)
# 将计算出来的值记录到缓存中
Cache.__cache[self.func.__name__] = result
return result
@cost_time
@Cache
def long_time():
print("启动 1")
sleep(3)
print("结束 2")
return "函数结束"
if __name__ == '__main__':
r1 = long_time()
r2 = long_time()
print(r1)
print(r2)

启动 1
结束 2
耗时为3.000478744506836 # 可以看到第一次花费时间,
耗时为0.0 # 第二次不花时间直接输出了
函数结束
函数结束

生成器

在Python中,一边循环一边计算的机制,称为生成器:generator

比如如果仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都浪费了。如果列表元素按照某种算法推算出来,那我们就可以在循环的过程中不断推算出后续的元素,这样就不必创建完整的list,从而节省大量的空间。

是一种以时间换空间的方式

1
2
3
4
5
6
7
8
生成器表达式很简单,只要把一个列表推导式的[]改成(),就创建了一个生成器(generator)
L = [x * x for x in range(10)]
print(L)
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

g = (x * x for x in range(10))
print(g)
# <generator object <genexpr> at 0x000002A7BF7D3850>

生成器函数

如果一个函数中包含yield关键字,那么这个函数就不再是一个普通函数,调用函数就是创建了一个生成器(generator)对象。

生成器仅仅保存了一套生成数值的算法,并且没有让这个算法现在就开始执行,根据调用来进行计算与返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#coding=utf-8
def test():
print("start")
i = 0
while i <3:
yield i
#print(f"temp:{temp}")
print(i)
i += 1
print("end")
return "结束"

if __name__ == '__main__':
a = test()
print(a)
a.__next__() # start
a.__next__() # 0
a.__next__() # 1
a.__next__() # StopIteration: 结束
'''
相当于每次调用它的__next__,都会执行这个yield
yield执行完之后会让程序挂起
return代表生成器结束,就会返回报错
'''

send()函数,将值100发到生成器内部
#coding=utf-8
# send的作用是唤醒并继续执行,发送一个信息到生成器内部
def foo():
print("start")
i = 0
while i<4:
temp = yield i
print(f"temp:{temp}")
i=i+1
print("end")

g = foo()
print(next(g)) # 同g.__next__
print("******")
print(g.send(100))
print(next(g)) #调用第二次

# for a in g: for循环调用同理
# print(a)

start
0
******
temp:100
1
temp:None
2

迭代器

特点

  1. 迭代器是一个可以记住遍历的位置的对象。
  2. 迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。
  3. 迭代器只能往前不会后退。
  4. 迭代器有两个基本的方法:iter() 和 next()

实现了iter方法和next方法的对象就是迭代器。

生成器都是Iterator对象,但listdictstr虽然是Iterable(可迭代对象),却不是Iterator(迭代器)

1
2
3
4
5
6
7
8
9
10
from collections.abc import Iterator
from collections.abc import Iterable
a = isinstance([],Iterable)
print(a)
a = isinstance([],Iterator)
print(a)

True
False
[]是可迭代对象,但不是迭代器

Python的Iterator对象表示的是一个数据流。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。

所以,生成器一定是迭代器。

Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

for循环的本质

是使用next()来实现的

1
2
3
4
5
6
7
8
9
10
11
12
13
for x in [1, 2, 3, 4, 5]:
pass
本质上是
# 首先获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环:
while True:
try:
# 获得下一个值:
x = next(it)
except StopIteration:
# 遇到StopIteration就退出循环
break

如果要自己创建一个迭代器,就需要分别重写__tier__方法和__next__方法

动态语言特性

动态语言指的是运行时可以改变其结构的语言 :例如新的函数、 对象、甚至代码可以被引进, 已有的函数可以被删除或是其他结构上的变化。

给对象添加属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#coding=utf-8
import types
class Person():
def __init__(self,name,age):
self.name = name
self.age = age
p1 = Person("wangsheng",24)
p2 = Person("xuehuiying",24)
p1.height = 176 # 直接添加属性
print(p1.height) # 176

def fun1(self):
print(f"{self.name} is {self.age}")
p2.fun1 = types.MethodType(fun1,p2) # 通过types.MethodType添加方法
p2.fun1() # xuehuiying is 24

给类添加属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
**添加静态方法与类方法:
#coding=utf-8
import types
class Persion():
pass

def static1():
print("static~")
Persion.static = staticmethod(static1) #直接添加静态方法
Persion.static() # 调用
# static~
@classmethod # 类方法装饰器
def cls1(cls,name):
cls.name = name
print(f'cls~{cls.name}')
Persion.clsfunc = cls1 # 直接添加
Persion.clsfunc("ws")
# cls~ws**

__slots__ 属性**

  1. __slots__对动态添加成员变量、成员方法有限制。对动态添加类属性、类方法没有限制。
  2. __slots__只对本类有限制,不限制子类

为的是不让别人乱加

变量的类型标注

变量类型注解是用来对变量和函数的参数返回值类型做注解(暗示),帮助开发者写出更加严谨的代码,让调用方减少类型方面的错误,也可以提高代码的可读性和易用性。

常用的数据类型

Type Description
int 整型 integer
float 浮点数字
bool 布尔(int 的子类)
str 字符 (unicode)
bytes 8 位字符
object 任意对象(公共基类)
List[str] 字符组成的列表
Tuple[int, int] 两个int对象的元组
Tuple[int, …] 任意数量的 int 对象的元组
Dict[str, int] 键是 str 值是 int 的字典
Iterable[int] 包含 int 的可迭代对象
Sequence[bool] 布尔值序列(只读)
Mapping[str, int] 从 str 键到 int 值的映射(只读)
Any 具有任意类型的动态类型值
Union 联合类型
Optional 参数可以为空或已经声明的类型
Mapping 映射,是 collections.abc.Mapping 的泛型
MutableMapping Mapping 对象的子类,可变
Generator 生成器类型, Generator[YieldType、SendType、ReturnType]
NoReturn 函数没有返回结果
Set 集合 set 的泛型, 推荐用于注解返回类型
AbstractSet collections.abc.Set 的泛型,推荐用于注解参数
Sequence collections.abc.Sequence 的泛型,list、tuple 等的泛化类型
TypeVar 自定义兼容特定类型的变量
Generic 自定义泛型类型
NewType 声明一些具有特殊含义的类型
Callable 可调用类型, Callable[[参数类型], 返回类型]
NoReturn 没法返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
对于简单的 Python 内置类型,只需使用类型的名称
a:int = 10
b:str = "ws"
c:float = "1.12"
d:bool = True

mypy是Python的可选静态类型检查器,pip安装这个模块之后,在终端中指定mypy就可以对指定
文件进行检查

如果需要标注一些序列的类型,在3.10之前需要引入对应的模块
from typing import List, Set, Dict, Tuple
x:List[int] = [1]
x:Set[int] = {6, 7}
x:Dict[str, float] = {'field': 2.0} # 字典要对应key和value的值类型
# 元组
x:Tuple[int,str,float] = (3, "yes", 7.5) # 对于固定大小的元组,指定所有元素的类型
x:Tuple[int, ...] = (1, 2, 3) # 对于可变大小的元组,使用一种类型和省略号

函数参数返回值添加类型标注

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 注释函数定义的方式
def stringify(num:int) -> str:
return str(num)
print(stringify(10))
# 10

def plus(num1: int, num2: int) -> int:
return num1 + num2
print(plus(10,20))
# 30

#迭代器
from typing import Iterator
def g(n: int) -> Iterator[int]:
i = 0
while i < n:
yield i
i += 1

混合类型标注

1
2
3
4
5
6
7
8
9
# 旧版
from typing import Union
def oldFunc(para:Union[int,float])->Union[int,float]:

# 新版
def newFunc(para:int|float)-> int|float:
return para+10
print(newFunc(10))
print(newFunc(10.10))

给类型添加别名

这样可以直接用别名代替类型名,增加代码的可读性

通过typing的TypeAlias方法进行修改别名

1
2
3
4
5
from typing import TypeAlias
newname: TypeAlias = str
def newFunc(param:newname) -> newname:
return param + param
user = create_user("john_doe")

散装新特性

字典的三个方法新增mapping属性

1
2
3
4
5
6
7
8
9
mydict = {"一":1,"二":2,"三":3}
#旧版本
#print(mydict.keys(),mydict.values(),mydict.items())

#新版本
keys = mydict.keys()
values = mydict.values()
items = mydict.items()
print(keys.mapping,values.mapping,items.mapping)

函数zip()新增strict参数

strict表示严格检查要zip的两个对象元素数量是否一致

1
2
3
4
5
6
7
8
9
10
11
12
13
14
keys = ['one','two','three','four']
values = [1,2,3,4,5]
#旧版本
print(dict(zip(keys,values)))
#新版本
print(dict(zip(keys,values,strict=True)))

报错:
#Traceback (most recent call last):
# File "C:\Users\13209\PycharmProjects\project01\66.py", line 7, in <module>
# print(dict(zip(keys,values,strict=True)))
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
#ValueError: zip() argument 2 is longer than argument 1
#{'one': 1, 'two': 2, 'three': 3, 'four': 4}

dataclass装饰器

这个装饰器是Python 3.7及以后版本中引入的,可以很容易地定义一个数据类,而无需手动编写那些常见的特殊方法。这不仅可以减少代码量,还可以提高代码的可读性和可维护性。

dataclass装饰器将根据类属性生成数据类和数据类需要的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
from dataclasses import dataclass
from typing import ClassVar
@dataclass
class Player: # 定义构造函数中的属性
name:str
number:int
postion:str
age:int=field(default=19,repr=False) # 自定义变量的参数
# repr=False表示打印时不会输出
country:ClassVar[str] # 类属性

p1 = Player('SXT',18,'PG',26)
print(p1)

dataclasses.dataclass(*, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)

key 含义
init 指定是否自动生成__init__,如果已经有定义同名方法则忽略这个值,也就是指定为True也不会自动生成
repr 同init,指定是否自动生成__repr__;自动生成的打印格式为class_name(arrt1:value1, attr2:value2, …)
eq 同init,指定是否生成__eq__;自动生成的方法将按属性在类内定义时的顺序逐个比较,全部的值相同才会返回True
order 自动生成__lt__,__le__,__gt__,__ge__,比较方式与eq相同;如果order指定为True而eq指定为False,将引发ValueError;如果已经定义同名函数,将引发TypeError
unsafehash 如果是False,将根据eq和frozen参数来生成__hash__:1. eq和frozen都为True,__hash__将会生成2. eq为True而frozen为False,__hash__被设为None3. eq为False,frozen为True,__hash__将使用超类(object)的同名属性(通常就是基于对象id的hash) 当设置为True时将会根据类属性自动生成__hash__,然而这是不安全的,因为这些属性是默认可变的,这会导致hash的不一致,所以除非能保证对象属性不可随意改变,否则应该谨慎地设置该参数为True
frozen 设为True时对field赋值将会引发错误,对象将是不可变的,如果已经定义了__setattr__和__delattr__将会引发TypeError
1
2
3
例如:
@dataclass(init=False, repr=False, eq=False)

字典合并

python3.9之后的新特性

1
2
3
4
5
6
7
8
9
10
11
12
13
# 旧版本
dict1.update(dict2)
print(dict1)

# 新版本
dict1 = {'a': 1, 'b': 2}
dict2 = {'b': 3, 'c': 4}
dict3 = dict1 | dict2 # {'a': 1, 'b': 3, 'c': 4}

#或者
dict1 |= dict2
等价于
dict1 = dict1 | dict2

match新用法

3.10后的新特性,用来取代if_else的语法,效率更高

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
语句的匹配
#coding=utf-8
status = 400
match status:
case 200:
print('访问成功')
case 404:
print('资源不存在')
case 502:
print('网关有问题')
case 500:
print('服务有问题')
case _: # 其他任何值
print('不能识别')


模式的匹配
#coding=utf-8
#模式匹配
person1 = ('ws',25,'male')
person2 = ('xhy',24,'female')
person3 = ('zhz',25,'male')
def func(person):
match person:
case (name,_,'female'): # _代表无所谓,不重要
print(f'{name} is woman')
case (name,_,'male'):
print(f'{name} is man')
case (name,age,gender):
print(f'{name} is {age} old')
func(person1)
func(person2)
func(person3)

#ws is man
#xhy is woman
#zhz is man

内存管理机制(八股文)

Python是由C语言开发的,底层操作都是基于C语言实现,Python中创建每个对象,内部都会与C语言结构体维护一些值。

1.在创建对象时,每个对象至少内部4个值,PyObject结构体(上一个对象、下一个对象、类型、引用个数)。

1
2
3
4
5
6
7
8
#define _PyObject_HEAD_EXTRA       \
struct _object *_ob_next; \
struct _object *_ob_prev;
typedef struct _object {
_PyObject_HEAD_EXTRA
Py_ssize_t ob_refcnt;
PyTypeObject *ob_type;
} PyObject;

2.有多个元素组成的对象使用PyVarObject,里面由:PyObject结构体(上一个对象、下一个对象、类型、引用个数)+Ob_size(items=元素,元素个数)。

1
2
3
4
typedef struct {
PyObject ob_base;
Py_ssize_t ob_size; /* Number of items in variable part */
} PyVarObject;

在python程序中创建的任何对象都会被放在refchain双向列表链表中。

1
2
3
4
5
6
7
8
9
10
11
12
13
例:
f = 3.14
'''
内部会创建:
1.开辟内存
2.初始化
ob_fval = 3.14 值
ob_type = float 类型
ob_refcnt = 1 引用数量
3.将对象加入到双向链表refchain中
_ob_next = refchain中的上一个对象-上一个指针
_ob_prev = refchain中的下一个对象-下一个指针
'''

Python的缓存机制

小整数对象池

一些整数在程序中的使用非常广泛,Python为了优化速度,使用了小整数对象池, 避免为整数频繁申请和销毁内存空间。

Python 对小整数的定义是 [-5, 257) 这些整数对象是提前建立好的,不会被垃圾回收。

1
2
3
4
5
6
7
8
9
a = -5
b = -5
a is b
# True

-6 -> False
256 -> True
257 -> False
每个大整数,均创建一个新的对象

intern机制

每个单词(字符串),不夹杂空格或者其他符号,默认开启intern机制,共享内存,靠引用计数决定是否销毁

1
2
3
4
5
6
7
a = 'helloworld'
b = 'helloworld'
a和b是同一个对象,如果使用下划线也是同个对象

a = 'hello world'
b = 'hello world'
不是同一个对象

free_list机制

当一个对象的引用计数器为0时,按理说应该回收,但内存不会直接回收,而是将对象添加到free_list链表中缓存。以后再去创建对象时,不再重新开辟内存,而是直接使用free_list。

比如float,list,tuple,dict都有free_list机制

1
2
3
4
5
6
f = 3.14
print(id(f))
ff = 3.14
del f # 删除f,此时引用计数器为0,照理来说应该删除
# 但实际上f这个3.14已经被添加到free_list中了
print(id(ff)) # 可见其id与f是相同的

float类型,维护的free_list链表最多可缓存100个float对象。

list类型,维护的free_list数组最多可缓存80个list对象。

tuple类型,维护一个free_list数组且数组含量20

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
重复创建相同元素数量的元组,放入free_list中后,会占用相同的位置
a = (1,2,3)
print(id(a))
del a
b = (4,5,6)
print(id(b))

a = {'a':1,"b":2}
print(id(a))
del a
b = {'c':3,"d":4}
print(id(b))

1779322404736
1779322404736

↑dict类型,维护的free_list数组最多可缓存80个dict对象。运行方式与元组是一致的

垃圾回收机制

Python 内部采用 引用计数法 ,为每个对象维护引用次数,并据此回收不再需要的垃圾对象。由于引用计数法存在重大缺陷,循环引用时有内存泄露风险,因此 python 还采用 标记清除法 来回收存在循环引用的垃圾对象。此外,为了提高垃圾回收( GC )效率,Python 还引入了 分代回收机制 。

引用计数法

引用计数 是计算机编程语言中的一种 内存管理技术 ,它将资源被引用的次数保存起来,当引用次数变为 0 时就将资源释放。

Python 也使用引用计数这种方式来管理内存,每个 Python 对象都包含一个公共头部,头部中的 ob_refcnt 字段便用于维护对象被引用次数。回忆对象模型部分内容,我们知道一个典型的 Python 对象结构如下:

引用计数增加的情况

  1. 对象被创建
  2. 如果有新的对象使用该对象
  3. 作为容器对象的一个元素
  4. 被作为参数传递给函数

引用计数减少的情况

  1. 对象的引用被显示的销毁
  2. 新对象不再使用该对象
  3. 对象从列表中被移除,或者列表对象本身被销毁
  4. 函数调用结束

引用计数机制的优点

  • 简单
  • 实时性:一旦没有引用,内存就直接释放了。

引用计数机制的缺点

  • 维护引用计数消耗资源
  • 循环引用的问题无法解决——如下所示
1
2
3
4
5
6
7
8
a = [1,2]
b = [3,4]
a.append(b) #b的计数器2
b.append(a) #a的计数器2
del a
del b

相互引用的这种情况下,计数器a和b都无法归零

标记清除法

引用计数法能够解决大多数垃圾回收的问题,但是遇到两个对象相互引用的情况,del语句可以减少引用次数,但是引用计数不会归0,对象也就不会被销毁,从而造成了内存泄漏问题。针对该情况,Python引入了标记-清除机制

主动思路一般分为两步:垃圾识别 和 垃圾回收 。垃圾对象被识别出来后,回收就只是自然而然的工作了,因此垃圾识别是解决问题的关键。

遍历活跃对象,第一步需要找出 根对象 ( root object )集合。所谓根对象,就是指被全局引用或者在栈中引用的对象,这部对象是不能被删除的。

根对象本身是 可达的 ( reachable ),不能删除;被根对象引用的对象也是可达的,同样不能删除;以此类推。我们从一个根对象出发,沿着引用关系遍历,遍历到的所有对象都是可达的,不能删除。

而没有被标色的对象就是 不可达 ( unreachable )的垃圾对象,可以被安全回收。循环引用的致命缺陷完美解决了!

也就是它的关键是“遍历来标记不可达的对象”,并且将其删除

分代回收机制

如果每次执行标记清除法时,都需要遍历所有对象,多半会影响程序性能。

对象存活时间越长,它们被释放的概率越低,可以适当降低回收频率;相反,对象存活时间越短,它们被释放的概率越高,可以适当提高回收频率。

Python 内部根据对象存活时间,将对象分为 3 代

随着时间的推进,程序冗余对象逐渐增多,达到一定阈值,系统进行回收。

这 3 个代分别称为:初生代中生代 以及 老生代。当这 3 个代初始化完毕后,对应的 gc_generation 数组大概是这样的:

第一代链表:

当第一代达到700,就开始检测哪些对象引用计数变成0了,把不是0的放到第二代链表里,此时第一代链表就是空了,当再次达到700时,就再检测一遍。

第二代链表:

当第二代链表达到10,就检测一次。

第三代链表:

第三代链表检测10之后,第三代链表检测一次。

1
2
3
4
5
6
7
8
9
10
11
import gc
#python 中内置模块gc触发
print(gc.get_threshold()) #查看gc默认值
#输出(700, 10, 10)

#返回一个元组,分别获取这三代当前计数
gc.get_count()
#设置阈值
gc.set_threshold()
#关闭gc垃圾回收机制
gc.disable()
CATALOG
  1. 1. Anaconda开发环境介绍与搭建
    1. 1.1. 模块 —— 实现具体功能的工具包
    2. 1.2. 虚拟环境 —— 给开发环境做用途上的区分
    3. 1.3. pip和conda包管理工具的对比:
    4. 1.4. anaconda的conda与pip命令
    5. 1.5. windows安装anaconda与pycharm
  2. 2. 标识符与数据类型
    1. 2.1. python程序的构成
    2. 2.2. 注释
    3. 2.3. 标识符定义规则
    4. 2.4. Python标识符命名规则
      1. 2.4.1. 关键字和内置函数
    5. 2.5. 变量
    6. 2.6. 变量删除与垃圾回收机制
      1. 2.6.1. 链式赋值
      2. 2.6.2. 解包赋值
    7. 2.7. 数据类型
      1. 2.7.1. 整型int
      2. 2.7.2. 浮点型float
      3. 2.7.3. 布尔值
  3. 3. 运算符
    1. 3.1. 基本运算符
    2. 3.2. 运算符
    3. 3.3. 位运算符
  4. 4. 整数缓存问题
  5. 5. 字符串
    1. 5.1. 编码
    2. 5.2. 字符串创建
    3. 5.3. 字符串的拼接
    4. 5.4. 转义字符
    5. 5.5. 字符串操作
    6. 5.6. 可变字符串
    7. 5.7. 字符串驻留机制
    8. 5.8. 字符串的格式化format
    9. 5.9. 数字格式化
    10. 5.10. 类型转换
  6. 6. 序列
    1. 6.1. 列表
      1. 6.1.1. 列表的创建
      2. 6.1.2. 列表操作
      3. 6.1.3. 列表遍历len max min
      4. 6.1.4. 统计次数、指定元素:count、index方法
      5. 6.1.5. 列表切片
    2. 6.2. 字典
      1. 6.2.1. 字典的创建
      2. 6.2.2. 字典的操作
    3. 6.3. 元组
    4. 6.4. 封包与解包
    5. 6.5. 集合
    6. 6.6. 集合的关系测试
    7. 6.7. 不可变集合
  7. 7. 流程控制方法
    1. 7.1. 分支
    2. 7.2. 循环
    3. 7.3. 跳出
    4. 7.4. 练习
    5. 7.5. 通过循环来遍历可迭代对象
    6. 7.6. 嵌套循环练习
    7. 7.7. 循环中的优化
  8. 8. 函数
    1. 8.1. LEGB规则——查找顺序
    2. 8.2. 函数的格式
      1. 8.2.1. 函数变量
      2. 8.2.2. 函数参数的传递
    3. 8.3. 浅拷贝和深拷贝
      1. 8.3.1. main函数与一些常识
    4. 8.4. 传参
    5. 8.5. lambda表达式和匿名函数
    6. 8.6. eval函数
    7. 8.7. 递归函数
    8. 8.8. 嵌套函数
    9. 8.9. __del__析构方法和垃圾回收机制
    10. 8.10. __call__方法和可调用对象
  9. 9. 面向对象编程
    1. 9.1. 对象的组成
    2. 9.2. 引用的本质
    3. 9.3. 面向对象的基本概念
    4. 9.4. 类、类的属性、类的方法
      1. 9.4.1. 实例方法
      2. 9.4.2. 类对象、类方法的内存分析
      3. 9.4.3. 类方法
      4. 9.4.4. 静态方法
      5. 9.4.5. 方法没有重载
      6. 9.4.6. 方法的动态性
      7. 9.4.7. 私有属性和私有方法
      8. 9.4.8. @property装饰器
      9. 9.4.9. 属性和方法命名
      10. 9.4.10. None变量
    5. 9.5. 继承
      1. 9.5.1. 类成员的继承与重写
      2. 9.5.2. object根类
      3. 9.5.3. 重写__str__()方法
      4. 9.5.4. 多重继承
      5. 9.5.5. super()获得父类定义
    6. 9.6. 多态
    7. 9.7. 运算符重载、特殊方法
    8. 9.8. 特殊属性
    9. 9.9. 集成与组合
  10. 10. 设计模式-工厂模式与单例模式
    1. 10.1. 工厂模式
    2. 10.2. 单例模式
  11. 11. 异常处理
    1. 11.1. traceback模块
    2. 11.2. 自定义异常类
    3. 11.3. 调试与断点
  12. 12. 文件处理
    1. 12.1. 编码问题
    2. 12.2. 文本文件和二进制文件
      1. 12.2.1. 相关模块
      2. 12.2.2. 创建文件对象open()
      3. 12.2.3. read方法
      4. 12.2.4. readline方法
      5. 12.2.5. readlines方法
      6. 12.2.6. write方法
      7. 12.2.7. write和read的综合练习
      8. 12.2.8. 二进制文件读写
    3. 12.3. 文件对象的常用属性和方法
    4. 12.4. 文件的指针seek()
    5. 12.5. pickle序列化和反序列化
    6. 12.6. CSV文件操作
    7. 12.7. os与os.path模块
      1. 12.7.1. os.path模块
      2. 12.7.2. walk遍历
    8. 12.8. shutil模块(拷贝与压缩
  13. 13. 模块
    1. 13.1. 模块化编程的流程
    2. 13.2. 包(package)的概念和结构
    3. 13.3.
    4. 13.4. 使用模块
  14. 14. 常用模块
    1. 14.1. re模块
      1. 14.1.1. 正则表达式规则
      2. 14.1.2. 正则表达式方法
    2. 14.2. 常用模块random
      1. 14.2.1. 常用方法
    3. 14.3. 常用模块-JSON
      1. 14.3.1. 常用方法
    4. 14.4. 常用模块-logging
      1. 14.4.1. logging.basicConfig方法常用传参
      2. 14.4.2. 日志流常用方法
    5. 14.5. 常用模块-time和datetime
      1. 14.5.1. time模块
      2. 14.5.2. datetime模块
        1. 14.5.2.1. date类
        2. 14.5.2.2. datetime类
        3. 14.5.2.3. timedelta类
  15. 15. 并发编程
    1. 15.1. 一些概念
    2. 15.2. 线程
      1. 15.2.1. 线程的创建方式
    3. 15.3. GIL全局锁问题
    4. 15.4. 线程同步与互斥锁
    5. 15.5. 死锁问题
    6. 15.6. 信号量semaphore
    7. 15.7. 事件event对象
    8. 15.8. 生产者与消费者模型
    9. 15.9. 进程
      1. 15.9.1. 进程的创建
    10. 15.10. 进程通信
      1. 15.10.1. 使用queue队列实现进程通信
      2. 15.10.2. pipe管道实现进程通信
      3. 15.10.3. manager管理器实现进程通信
    11. 15.11. 进程池
    12. 15.12. 协程
      1. 15.12.1. asyncio异步IO实现协程
  16. 16. 网络编程
    1. 16.1. 基础概念
      1. 16.1.1. TCP建立连接与断开连接
      2. 16.1.2. 数据包结构
    2. 16.2. 套接子编程概述
      1. 16.2.1. socket编程
      2. 16.2.2. socket()函数
    3. 16.3. UDP编程
      1. 16.3.1. 简单例子
      2. 16.3.2. UDP持续通信
      3. 16.3.3. UDP多线程双工通信
    4. 16.4. TCP编程
      1. 16.4.1. TCP双向通信
      2. 16.4.2. TCP多线程双工通信
  17. 17. 函数式编程
    1. 17.1. 偏函数
    2. 17.2. 闭包
      1. 17.2.1. 闭包的内存分析
    3. 17.3. map函数
    4. 17.4. reduce函数
    5. 17.5. filter函数
    6. 17.6. 装饰器
      1. 17.6.1. 多个装饰器执行顺序
      2. 17.6.2. 带参数的装饰器
      3. 17.6.3. wraps装饰器
      4. 17.6.4. 内置装饰器property、staticmethod、classmethod
    7. 17.7. 类装饰器
    8. 17.8. 案例:缓存装饰器、计时装饰器
    9. 17.9. 生成器
      1. 17.9.1. 生成器函数
    10. 17.10. 迭代器
      1. 17.10.1. for循环的本质
    11. 17.11. 动态语言特性
      1. 17.11.1. 给对象添加属性和方法
      2. 17.11.2. 给类添加属性和方法
      3. 17.11.3. __slots__ 属性**
  18. 18. 变量的类型标注
    1. 18.0.1. 常用的数据类型
    2. 18.0.2. 函数参数返回值添加类型标注
    3. 18.0.3. 混合类型标注
    4. 18.0.4. 给类型添加别名
  • 19. 散装新特性
  • 20. 内存管理机制(八股文)
    1. 20.1. Python的缓存机制
      1. 20.1.1. 小整数对象池
    2. 20.2. intern机制
    3. 20.3. free_list机制
    4. 20.4. 垃圾回收机制
      1. 20.4.1. 引用计数法
      2. 20.4.2. 标记清除法
      3. 20.4.3. 分代回收机制