王铭东-知识库-it项目网 (itprojects.cn)

(一)基础知识

一、变量

变量定义:是一个存储数据的容器。
变量特性: 1️⃣引用着某个具体数值;
2️⃣并且可以 改变这个引用。

变量由三部分组成:

标识id:表示对象所存储的 内存地址,使用内置函数id(obj)来获取
类型type:表示对象的数据类型,使用内置函数type(obj)来获取
值value:表示对象所存储的具体数据,使用print(obj)将值打印输出
当给变量多次赋值之后,变量名会指向新的内存空间

定义:变量一定要先定义:变量名=数值/数值表达式 (先运算后赋值)

赋值运算符:把=号右边的结果 赋给 左边的变量 a,b,c=1,2,3两边顺序和数量要对应 高级应用:拆包

标识符:只由字母、下划线和数字组成,且数字不能开头(1=2 ×,所以不让数字开头)

Python中的标识符是区分大小写的

变量名、函数名都使用下划线命名法 如: send_msg
类名用大驼峰命名法 如: FirstName、LastName
全局变量用全部是大写,且单词之间用下划线链接的方式,例如 SUM_SCORE

关键字:是python已经使用的了,所以不允许开发者自己定义和关键字相同的名字的标识符

False,None,True,and,as,assert,async,await,break,class,continue,def,delelif,else,except,finally,for,from,global,if,import,in,is,lambda,nonlocalnot,or,pass,raise,return,try,while,with,yield

六大数据类型:

type(变量/常量)    # 可查询变量所指的对象类型
  1. Number(数字)

  • 数字类型:

类型转换:

int(x)   # 将x转换为一个整数。
float(x) # 将x转换到一个浮点数。
complex(x, y) #复数,实数部分为x,虚数部分为y。
#complex(x) #实数部分为x,虚数部分为 0。
  • 数值运算:算术运算符

混合运算时优先级: ** 高于 * / % // 高于 + - ,为了避免歧义,建议使用 () 来处理运算符优先级。
复合运算 举例c += a 等效于 c = c + a
不同类型的数字在进行混合运算时,整数将会转换成浮点数进行运算。
/结果为float类型,带小数

  • 数学函数:

exp(x)  # e的x次幂
random() # 随机生成下一个实数,它在[0,1)范围内
randrange ([start,] stop [,step]) # 从指定范围内,按指定基数递增的集合中获取一个随机数,基数默认值为 1

2.String(字符串)

定义字符串的方式是引号:'' 或 " "

2.1字符串运算:

  • + & *运算:

print ('Hello World!' + 'Runoob!') # Hello Runoob!
print ('Hello World!'* 2)          # HelloHello 
  • 索引&切片:

my_str [头下标 : 尾下标:步长] 获取起始--结束前一位(不包括尾)间的元素

从左往右以 0 开始,从右往左以 -1 开始(因为-0=0所以从-1开始)

 name = 'Runoob'
print(name[0])      # 0-R
print(name[3:5])    # 34-oo  等于 print(name[3:-1])
print(name[1:5:2])  # 13-uo
print(name[5:1:-2]) # 53-bo
print(name[:3])     # 头-2 Run
print(name[2:])     # 2-尾 unoob
print(name[::])     # 头-尾 Runoob
print(name[::-1])   # 尾-头 boonuR

不写步长时,默认为1;步长为正数,表示从左向右取数据;步长为负数,表示从右向左取数据
当开始位置、结束位置都没有设定时,要看步长:
如果步长为正数,此时开始位置理解为最左 结束位置为最右;
如果步长为负数,此时开始位置理解为最右 结束位置为最左。

  • 成员运算符: in & not in

in      如果字符串中包含给定的字符返回 True    'H' in a 输出结果 True
not in  如果字符串中不包含给定的字符返回 True   'M' not in a 输出结果 True

2.2字符串函数:

my_str.find(str start=0, end=len(mystr)) # 检测str1是否包含在 my_str中,如果是返回开始的索引值,否则返回-1
my_str.rfind(str, start=0, end=len(my_str)) # 从右边开始查找
my_str.count(str, start=0, end=len(mystr)) # 返回 str在start和end之间在my_str里面出现的次数
my_str.replace(str1, str2,  mystr.count(str1)) # 把 my_str 中的 str1 替换成 str2,如果 count 指定,则替换不超过 count 次
my_str.split(str=" ", maxsplit)  #以 str为分隔符切片 my_str,如果 maxsplit有指定值,则仅分隔 maxsplit 个子字符串
my_str.startswith(hello) # 检查字符串是否是以 hello 开头, 是则返回 True,否则返回 False
my_str.endswith(obj)# 检查字符串是否以obj结束,如果是返回True,否则返回 False.
my_str.lower()# 转换 my_str 中所有大写字符为小写
my_str.upper()# 转换 my_str 中所有小写字母为大写
my_str.strip()# 删除字符串两端的空白字符
my_str.partition(str)# 把my_str以str分割成三部分,str前,str和str后
my_str.splitlines()# 按照行分隔,返回一个包含各行作为元素的列表
my_str.isalpha() # 如果 my_str 所有字符都是字母 则返回 True,否则返回 False
my_str.isdigit() # 如果 my_str 只包含数字则返回 True 否则返回 False.
my_str.isalnum() # 如果 my_str 所有字符都是字母或数字则返回 True,否则返回 False
my_str.join(str) # my_str 中每个元素后面插入str,构造出一个新的字符串
list = [1,2,3,4,5]
str=".join(list)结果即为:12345
str=','.join(list)结果即为:1,2,3,4,5

3.List(列表)

定义列表的方式是[ ]

  • 列表VS数组

相同点:都能实现多个数据的存储
不同点:列表可以存储不同类似的数据,而数组只能存储相同数据类型。

  • 索引&切片&遍历

stu_names = ['wanglaoshi', 'dongge', 'dong4716138'] # 定义一个列表
print(stu_names[0])  #索引 此时只输出wanglaoshi
stu_names[1:3]       #切片 此时得到一个新列表['wanglaoshi', 'dongge']#i=0
#while i<len(names): #需要长度 while遍历不如for遍历简洁
#  print(names[i])
#  i+=1for name in stu_names: #不需要长度print(name)       #遍历 wanglaoshi dongge dong4716138
  • 函数:增删改查

# 增
列表.append(新元素数据)   #向列表末尾添加一个新元素(追加单个)
列表.extend(另外一个列表) #将另一个列表中的元素逐一添加到列表中(追加多个)
列表.insert(index, object) #在指定位置前插入元素object
#删
del 列表[2]    # 根据下标进行删除
列表.pop()     # 删除最后一个元素
列表.remove(data) # 删除指定的数据data
#改
列表[下标] = 新数据
#查
数据 in 列表
数据 not in 列表
列表.count("要查询的数据")  # 结果就是找到的数据的个数#排序
列表.sort()#将列表按特定顺序重新排列,默认为由小到大,参数reverse=True可改为倒序,由大到小。改的是原列表,不生成新的
列表.reverse()#将列表倒叙
#清空
列表.clear() #清空列表
len(list) #列表元素个数
max(list) #返回列表元素最大值
min(list) #返回列表元素最小值
  • 列表嵌套:

一个列表中的元素又是一个列表 [ [ ],[ ]... ]

a = ['a', 'b', 'c']
n = [1, 2, 3]x = [a, n] # [['a', 'b', 'c'], [1, 2, 3]]
print(x[0])       # ['a', 'b', 'c']
print(x[0][1])    # 'b'

4.Tuple(元组)

定义元组的方式是()

元组里只有一个元素时,要写上逗号 (3 ,) 用于区分数值运算中处理运算符优先级的 ( 3 )

  • 元组VS列表

相同点:都能实现多个数据的存储,很像列表
不同点: 元组的元素不能修改 ( 添加、删除、修改都不可以,保证了数据的安全性), 因此成为了python的 默认形式

  • 索引&切片&遍历

nums = (100, 200, 300)# 定义一个元组
print(nums[0])# 获取元组中的元素
# 遍历
for temp in nums:print(temp)

5.Set(集合)

定义集合的方式是 { }

  • 集合VS列表VS元组

列表可以存储多个数据,支持 增删改查
元组可以存储多个数据,数据 不能修改 列表、元组在定义时的顺序是怎样的,那么顺序就是怎样的
集合可以存储多个数据,数据 不能重复 自动去重,所以集合的实际存储顺序与定义的顺序没有关系

列表、元组、集合互转:不是对原数据进行修改,而是得到一个新的数据

快速去重:先转为集合再转回

  • 遍历:

nums = {100, 200, 300}# 定义一个集合,不能有重复数据,会自动去重,所以无序
# 遍历
for temp in nums:print(temp)
  • 支持增删改查

  • 集合运算:

6.Dictionary(字典)

定义字典的方式是 { Key:Value}

增删改查等使数字下标易错乱,改为自定义标识(不会发生改变), 下标==Key
Key相当于集合中的下标值012...,所以Key值不能相同
相当于我把012...下标值换成自定义值了,无序号了所以可以无序存放

  • 字典VS集合:

相同点:集合也是用{} 字典也是用的{},都是无序 (一个要去重一个没序号)
不同点:字典须用 键值对key: value组成 ,而字典是数字下标

#为了不容易混乱,当在定义一个空集合或者空字典的时候,推荐使用如下方式:
blank_set=set()#定义了一个空的集合
blank_dict=dict()#定义了一个空的字典
  • 使用场景总结:

Python发明的列表、元组、集合各有特点,不同场合使用不同特色存储。

一般使用列表,当确定数据不修改/不可重复时,再转换为元组/集合即可。

1)列表:推荐使用
使用场景:如果要存储多个相同数据类型的时候,就用列表
例如:存储多个姓名,存储多个年龄等,只要数据类型相同,且可能需要对数据进行操作(增删改查)
2)元组:使用情况一般
使用场景:如果想要一些数据,这些数据的类型相同,且不能修改这些数据,此时一定要优先考虑元组
例如:存储每年国家领布的银行存款利率
3)集合:使用情况非常少
使用场景:往往在需要对数据进行去重的时候使用
4)字典:使用场景非常多
使用场:如果要存储多个数据,且这些数据是不同类型的,且这些数据都是相当于一个整体的
例如:存储一个班级的信息(班号、班主任的名字、学生人数、。。。。)

  • 索引&遍历:

teacher_wang = {"name": "王老师","age": 18,"home": "山东省青岛市","web_site": "www.codetutor.top"
}# 根据key访问value
print(teacher_wang['name'])  # 不能用0来获取姓名
#遍历
for key in teacher_wang.keys():  #默认,等同于for key in teacher_wanprint(key)  # 遍历键
for val in teacher_wang.values():print(val)  # 遍历值
for temp in teacher_wang.items():print(temp)  # 此时temp是元组(key,val)print("%s:%s"%(temp[0],temp[1]))
  • 函数:增删改查

# 增
字典['新键'] = 新数据 #这个“键”在字典中不存在,那么就会新增这个元素。
#删
del 字典[Key]  #del删除指定的键值对
del 字典       #del删除整个字典
字典.clear()   #clear清空整个字典数据,但不删除
#改
字典['键'] = 新数据 #只要通过key找到,即可修改
#查
字典[key]                   #若访问不存在的键,则会报错
字典.get(key, default=None) #返回指定键的值,如果键不在字典中返回 default 设置的默认值,也可以将none修改为想要返回的内容
len(dict) #计算字典元素个数,即键的总数。
str(dict) #输出字典,可以打印的字符串表示。key in dict #如果键在字典dict里返回true,否则返回false
  • 字典嵌套:

一个字典中的元素又是一个字典

students ={"1001":{"1d":1001,"name":"王x","age":24},"1002":{"id":1002,"name":"马x","age":23},"1003":{"id":1003,"name":"宋x","age":24},
}print(students["1002"]["age"]) #23

7.推导式 for ... in ...

推导式就是一种能够快速生成数据的方式,可以用一行代码生成有规律的列表

nums=[]#定义一个空列表,用来存储生成的数据
for i in range(100):if i%2!=0:nums.append(i)
#这种方式不够简洁,它等价于
nums=[x for x in range(100) if x%2!=0]
  • 列表推导式 [xxx for 变量 in 可迭代对象 ]

a= [x for x in range(4)]    #[0,1,2,3]
a= [x*2 for x in range(4)]  #[0,2,4,6]a=[x for x in range(3,10)]  #[3,4,5,6,7,8,9]
a=[x for x in range(3,10,2)]#[3,5,7]
#等同于
a=[x for x in range(3,10)if x%2!=0]a=[(x,x+1)for x in range(1,3)]#[(1,2),(2,3)]
a=[(x,y)for x in range(1,3)for y in range(3)]#[(1,0),(1,1),(1,2),(2,0),(2,1),(2,2)] 嵌套nums=[1,2,3,4,5,6,7,8,9]
a=[nums[x:x+3] for x in range(0,len(nums),3)]#[[1,2,3],[4,5,6],[7,8,9]]
  • 注意:没有元组推导式,而是生成器

  • 集合推导式 {xxx for 变量 in 可迭代对象}

a = {x for x in range(1, 11) if x % 2 == 0} #{2, 4, 6, 8, 10} 无重复数据且乱序
  • 字典推导式 {xxx:xxx for 变量 in 可迭代对象}

{x:x**2 for x in range(1, 5)} #{1: 1, 2: 4, 3: 9, 4: 16}
{x:(x+1) for x in range(1,11)}#{1: 2, 2: 3, 3: 4, 4: 5}#应用1:快速更换key和value
dict1={'a':10,'b':34}
dict2={v:k for k,v in dict1.itemp()}#应用2:大写key与小写key转换
dict3={'a':10,'b':34}
dict4={k.upper():v for k,v in dict3.items()}

8.序列拆包(解包)

拆包就是一种能够快速提取数据的方式

nums=[11,22,33]
a=nums[0]
b=nums[1]
c=nums[2]
#这种方式不够简洁,它等价于
nums2=[44,55,66]
aa,bb,cc=nums2

注意点: =右边要拆的数据元素的个数 要与=左边存的变量个数相同 aa,bb,cc=[44,55,66]

拆列表

拆元组

拆集合

  • 拆字典

a, b ={"name":"王老师","web_site":"http://www.codetutor.top"}
print(a) # name or web_site
print(b) # web_site or name 乱序

默认取到的是字典的key,而不是value,一般采用下面这种方式:

teacher_wang ={"name":"王老师","web_site":"http://www.codetutor.top"}
for k, v in teacher_wang.items():print("k=%s, v=%s"%(k, v)) #k=name, v=王老师  k=web_site, v=http://www.codetutor.top   相当于对元组拆包

经典面试题(交换2个变量的值)

a =4,b =5


二、语句

  • 一行多句:语句之间使用分号 ; 分割

  • 多行一句:

末尾+反斜杠 \

在 [], {}, 或 () 中的多行语句,不需要使用反斜杠 \

  • 注释语句:

单行注释以 # 开头

多行注释用三个单引号 '''

多行注释用三个双引号 """

  • 行与缩进:使用缩进来表示代码块,不需要使用大括号 {}

1.条件选择

if – elif ......elif– else:

Python 中没switch...case

而用的match...case语句,

case _: 类似于default

比较运算符:< ≤ > ≥ == !=

逻辑运算符: and or not

符号左右两边不是True/False时:

继承C语言:非0为真,0为假; 熔断机制

不全执行。结果只能是True或者False;

try/except...else…finally

异常监测

三元/三目运算符:result=条件成立返回的数据 if 条件 else 条件不成立返回的数据

2.循环重复

python中只有while和for,没有do while

while 循环

for 语句

for循环可以遍历任何序列的项目,如一个列表或者一个字符串等

range(x) 范围:0...x-1

range(x, y, z) x...y-1每次间隔z

while适合控制循环次数i,for适合遍历,有明确循环次数二者都可以,没有明确循环次数用while True

判断条件成立后:

  • break结束所属的整个循环,跳过当前循环块中剩余语句,然后不进行下一轮循环。

  • continue 结束所属本次循环,跳过当前循环块中剩余语句,然后继续进行下一轮循环。

条件不成立,使用else的情况:

break会让else中的代码不执行,而continue没有这个功能

一般情况下break、continue在判断中使用,不该循环时结束

break让循环结束;return让函数结束;exit让程序结束

3.输入输出

  • 编码&解码

不管是中文还是英文,在计算机当中叫做字符,一个字符对应一个整数,参考ASCII编码

my_str.encode(encoding='UTF-8') # 指定的编码格式 my_str字符串<--->bytes二进制

a=bytearray([...])

bytearray() # bytearray(b'')
bytearray([0x01,2,3]) # bytearray(b'\x01\x02\x03')
bytearray('runoob', 'utf-8') # bytearray(b'runoob')
  • print() 可以直接将变量、数、'字符串'输出到终端

print() #空行
print(变量名)
print(表达式)
#一行多句:想要一次性输出多个数据,可以用英文逗号,进行间隔 显示时,变成空格
print(a,b,c) #a b c
#一句多行:转义字符\  \n new换行 \t tab间距
print(a\nb\nc)
#格式化输出%
print("我的姓名是:%s, 年龄是:%d岁, 身高是:%.2f米" % (name, age, hight))
print("* ", end = '')# end = '' Print不自动换行  end = '\t'对齐

格式化输出:%s%d%f %(a,b,c) 用实际值abc依次替换占位符sdf, 个数相同 位置对应

  • 格式字符串:%

print()输出的内容包含一些辅助字符时,在特定位置插入变量值

  • 新格式字符串:字符串.format() = print(f'.....')

a = 1
b = 2
c = 3
print(f'a={a}, b={b}, c={c}')
# 等价于print('a={}, b={}, c={}'.format(a, b, c))

注意:f后面一定要紧跟字符串,不能隔有空格,否则会报错
{:.nf}控制小数点位数
print(f'a={a:.1f}, b={b:.2f}, c={c:.3f}')
# 等效于print('a={:.1f}, b={:.2f}, c={:.3f}'.format(a, b, c))

# 1.通过位置
print('{o},{1}'.format('laowang',20)) # laowang,20
print('{0]-{1}'.format('laowang',20)) # laowang-20
print('{},{}'.format('laowang',20))      # 1aowang,20
print('{1},{0},{1}'.format('laowang',20)) # 20,1aowang,20
print('{name},{age}'.format(age=18,name='laowang')) # laowang,18
# 2.通过关键字参数
class Person:def _init_(self,name,age):self.name = nameself.age = agedef _str_(self):return 'name is {a.name},age is {a.age} old'.format(a=self)print(Person('laowang',18)) # name is laowang,age is 18 old
# 3.通过映射list
a_list=['laowang',20,'China']
print('my name is {0[0]},from {0[2]},age is {0[1]}'.format(a_list)) # my name is laowang,from China,age is 20
# 4.通过映射dict
b_dict ={'name':'laowang','age':20,'province':'shandong'}
print('my name is {name},age is {age},from {province}'.format(**b_dict))
print('my name is {info[name]},age is {info[age]},from {info[province]}'.format(info=b_dict))
# print('my name is {info["name"]},age is {info["age"]},from {info["province"]}'.format(info=b_dict)) # 错误的
#5.填充与对齐
print('****{:>8}****'.format('189'))  # ****     189****
print('****{:<8}****'.format('189'))  # ****189     ****
print('****{:0>8}****'.format('189')) # ****00000189****
print('****{:a>8}****'.format('l89')) # ****aaaaa189****
#精度与类型f
print('{:.2f}'.format(321.33345)) # 保留两位小数 321.33
#用来做金额的干位分隔符
print('{:,}'.format(1234567890)) # 1,234,567,890
#其他类型主要就是进制了,b、d、0、X分别是二进制、十进制以、八进制、十六进制。
print('{:b}'.format(18)) # 二进制10010
print('{:d}'.format(18)) #十进制18
print('{:o}'.format(18)) #八进制22
print('{:x}'.format(18)  #十六进制12
  • 转义字符:\ 字符前+\意义就变了,不表示字符而表示其他含义

r,表示原始字符串

  • input() 会把用户输入的任何值都作为字符串来对待

str = input("请输入:")

如果一个程序需要获取多个数据,那么就要用多个input,即一个变量存储一个输入数据

  • 文件操作

目的:为了保存程序运行过程中产生的数据,方便在以后直接使用这些已经保存的数据

f=open(file, mode='r')#打开文件 文件路径file和模式r/w,如果要以二进制模式,加上b  返回文件对象
f.write(str)#将字符串写入文件,返回的是写入的字符长度。
content = f.read([size])#从文件读取指定的字节数,如果未给定或为负则读取所有。
f.close()#关闭文件。关闭后文件不能再进行读写操作。
#进阶中采用 with 进行文件关闭
#os模块中walk能够遍历出文件夹中所有文件、文件夹
import os
for temp in os.walk("./aa")print(temp)

walk是深度遍历,先遍历完一个再换下一个

(空文件夹[] 空文件[])


三、函数

函数是一个独立的功能块,只需定义函数1次就可以使用N次,可以大大减少了代码的冗余

  • 内置函数

import random
a = random.uniform(1, 5)    #2.018423712655862
b = random.randint(10, 50)  #14
c = random.randrange(0, 51, 2)#14
random.shuff1e(nums)#shuffle翻泽为中文是“洗牌"的意思,打乱nums列表顺序
  1. 定义&调用

定义完并不会立刻执行代码,而是在被调用时才会被从头开始执行,相当于

2.参数&返回

函数参数:形参-实参

  • 必需参数 顺序数量要正确

  • 缺省参数 形参名=XXX 传则替换,不传则用默认

  • 命名参数 形参名=实参 实参指名道姓传

(注:整体不带缺省/命名的在左,整体带的在右,不可夹杂)

  • 不定长参数 参数个数不确定

* args会以元组的形式导入(*表特殊,变量仍是args)

** kwargs会以字典的形式导入 以形参名=实参传

(args中数据通过遍历元组/字典读取)

函数返回值:return [表达式]

  • 返回数据给调用方 默认none

  • 结束函数的执行,剩余函数体语句不执行

一个函数中只能有1个return被执行,可通过return返回列表、元组、集合、字典等从而实现一次性返回多个数据

return [a,b,c]
return a,b,c #相当于(a,b,c)a,b,c=函数名(args) #函数返回值拆包

*args在前,**kwargs在后。多余的未命名参数给args,再有多余的 带形参名=XXX格式的 给**kwargs

列表当作形参

1.如果一个函效有缺省参数,且默认值是一个列表,那么不管调用多少次这个函数,每次都是用的同一个列表

2.如果某次调用这个函数时,向这个列表中添加、修改、删除了数据,那么当下一次调用这个函数的时候,这个列表中的数据将会发送改变

3.嵌套&变量

函数嵌套:

全局变量VS局部变量

  • 全局变量,在函数外边定义的变量

能在所有函数中使用(公共)

  • 局部变量,在函数内部定义的变量,包括形参

只能在这个函数中使用(私有)

1.每次调用函数时,局部变量都会重新使用,而不是用上一次调用函数时留下的数据

2.因为其作用范围只是在自己的函数内部,所以不同的函数可以定义相同名字的局部变量,也可以和全局变量相同,但在函数内修改不了其他函数及全局变量的值,即同名不同用,global 全局变量 = 数据 才可在函数内修改全局变量

4.函数拆包

函数拆包和数据拆包用法相同,不同于一个是序列数据,一个是函数的返回值拆包/形参赋值

def test():return 11, 22, 33# 通过返回值拆包,快速使用每个数据
a, b, c = test()
print(a + b + c)
# 没有通过返回值拆包,数据多了就会复杂
ret = test()
print(ret[0]+ret[1]+ret[2])
def test(a, b, c):print(a + b + c)
# 使用*拆包列表元组集合,用于实参
nums = [11, 22, 33]
test(*nums) #即test(11,22,33)
nums = (11, 22, 33)
test(*nums) #拆包元组
nums = {11, 22, 33}
test(*nums) #拆包集合 无序
def test(name, age):print(name)print(age)
# 使用**拆包键值对,用于实参
info = {"name": "王老师","age": 18}
test(**info) #拆包字典 无序
#即test(name="王老师",age=128)
#若name在形参中没有,无法赋值则报错,因此不推荐
#如test(name123, age) name≠name123

集合和字典乱序,所以拆包时一般只拆列表和元组。下面是拆包字典的一个举例:

列表里包含两种类型元素,一个列表一个字典,由于调用Test1()传参时没采用形参名=XXX的格式,所以两个都传给了arg,而拆包后的数据则避免了这个问题,仍以形参名=XXX的格式进行传递。

5.引用

引用:就是地址 查询地址:id(变量)

Python中的变量并不是真正存储数据,而是存储的数据所在内存中的地址,我们一般称之为引用
知道了地址,那么就意味着可以直接对指向的存储数据进行

赋值即引用

实参也即引用

定义一个函数可以理解为定义了一个全局变量,其指向存放函数体的地址

改变的是引用

函数名也是引用,

只不过⬜存放的是函数语句,不是值

实际就是变量名它存储了函数的引用

test1与xx指向了同一个函数,调用结果相同

a=test() 与 a=test 不同
一个调用函数,此时的a=函数的返回值
一个指向函数,此时的a=存储函数所在的地址

6.匿名&递归

  • 匿名函数:是一个没有名字的函数 把def name换成了lambda

lambda 形参1, 形参2, 形参3... : 表达式
#lambda函数能接收任何数量的参数但只能返回一个表达式的值,其默认就是返回的,不用写return

因为匿名函数没有名字,所以调用的时候,并不能使用函数名()的那种方式。

  1. 通过lambda定义匿名函数,然后用一个变量指向这个匿名函数,然后通过变量名()调用这个匿名函数

  1. 直接在调用其它函数实参的位置通过lambda定义匿名函数,会将这个匿名函数的引用当做实参传递

#1.先定义变量指向匿名函数
my_test_func = Lambda a,b:a b#2.通过变量名()调用
num = my_test_func(11,22)
print(num)
stus = [{"name": "wanglaoshi", "age": 18},{"name": "donglaoshi", "age": 19},{"name": "abc", "age": 17} ]stus.sort(key=lambda x: x['age'])#字典排序
print("排序后,stus=", stus)
  • 递归函数:一个函数在函数体中又调用了自己(次数不能太多)

其实递归是一种算法,一般用来解决“树”、“图”等操作,还能解决像“汉罗塔问题”等

#计算阶乘 n! = 1 * 2 * 3 * ... * n=n*(n-1)!


小结实战:学生管理系统

import time
import os
#定义全局变量,用来完成对学生信息的存储
#之所以采用全局变量的原因是:获取的学生信息需要在多个函数中使用,所以全局变量最为方便
# 定一个列表,用来存储所有的学生信息(每个学生是一个字典)
info_list = []def print_menu():print("---------------------------")print("      学生管理系统 V1.0")print(" 1:添加学生")print(" 2:删除学生")print(" 3:修改学生")print(" 4:查询学生")print(" 5:显示所有学生")print(" 6:退出系统")print("---------------------------")def add_new_info():"""添加学生信息"""global info_list# 1.从键盘获取学生信息(姓名、手机号、QQ)new_name = input("请输入姓名:")new_tel = input("请输入手机号:")new_qq = input("请输入QQ:")for temp_info in info_list:if temp_info['name'] == new_name:print("此用户名已经被占用,请重新输入")return  # 如果一个函数只有return就相当于让函数结束,没有返回值# 2.将刚刚取的用户信息组装为字典,这样的以来就更加方便info = {}info["name"] = new_nameinfo["tel"] = new_telinfo["qq"] = new_qq# 3.将数据添加到全局的列表中info_list.append(info)def del_info():"""删除学生信息"""global info_list# 1,获取用户要删除的学生序号''' stu_id = input("请输入要删除的学生序号:")#2.判断是否是纯数字的字符事if stu_id.isdigit()=True: stu_id=int(stu_id)else:#如果输入的数据中有非数字,那么就结束当前函数的执行return  #写else是为了不让下面的代码继续执行,return不执行剩余函数,退出函数'''del_num = int(input("请输入要删除的序号:"))# 3.判断要别除的序号是否在合理的范围之内if 0 <= del_num < len(info_list):  #序号合理del_flag = input("你确定要删除么?yes or no")if del_flag.lower() == "yes":# 4,根据序号,从列表中将这个学生对应的字典删除del info_list[del_num]else:print("输入序号有误,请重新输入")def modify_info():"""修改学生信息"""global info_list# 1,获取要修改的学生序号# 2,判断是否是纯数字的字符串modify_num = int(input("请输入要修改的序号:"))# #3。判断要删除的序号是否在合理的范围之内if 0 <= modify_num < len(info_list):print("你要修改的信息是:")print("name:%s, tel:%s, QQ:%s" % (info_list[modify_num]['name'],info_list[modify_num]['tel'], info_list[modify_num]['qq']))# 4.从健盘中获取新的信息赋值给这个字典,从而实现修改info_list[modify_num]['name'] = input("请输入新的姓名:")info_list[modify_num]['tel'] = input("请输入新的手机号:")info_list[modify_num]['qq'] = input("请输入新QQ:")'''#4,可以通过下标的方式提取这个学生对应的字典stu_dict= info_list[modify_num ]#5,从键盘中获取新的信息赋值给这个字典,从而实现修改stu_dict["name"]=input("请输入新的姓名:")stU-dict["phone_num"]=input("请输入新的手机号:")stU_dict["qq"]=input("请输入新的qg:")'''else:print("输入序号有误,请重新输入")def search_info():"""查询学生信息"""# 1,获取要查询的学生姓名search_name = input("请输入要查询的学生姓名:")# 2。遍历列表,依次判断元素中的字典中的姓名是否是要查询的,如果是则显示for temp_info in info_list:if temp_info['name'] == search_name:print("查询到的信息如下:")print("name:%s, tel:%s, QQ:%s" % (temp_info['name'],temp_info['tel'], temp_info['qq']))breakelse:print("没有您要找的信息....")def print_all_info():"""遍历学生信息"""print("序号\t姓名\t\t手机号\t\tQQ") # 表头i = 0 # 序号 字典无序号,添加个序号就相当于列表的下标了for temp in info_list:# temp是一个字典# print("%d\t%s\t\t%s\t\t%s" % (i, temp['name'], temp['tel'], temp['qq']))print("%d\t%s\t\t%s\t\t%s" % (i, temp.get('name'), temp.get('tel'), temp.get('qq')))i += 1def main():"""用来控制整个流程"""while True:# 1. 打印功能print_menu()# 2. 获取用户的选择num = input("请输入要进行的操作(数字)")# 3. 根据用户选择,做相应的事情if num == "1":# 添加学生add_new_info()elif num == "2":# 删除学生del_info()elif num == "3":# 修改学生modify_info()elif num == "4":# 查询学生search_info()elif num == "5":# 遍历所有的信息print_all_info()elif num == "6":# 退出系统exit_flag = input("亲,你确定要退出么?~~~~(>_<)~~~~(yes or no) ")if exit_flag.lower() == "yes":break  #break让循环结束;return让函数结束;exit让程序结束else:print("输入有误,请重新输入......")input("\n\n\n按回车键继续....") # 避免立刻清屏,看不到显示内容os.system("cls")  # 调用Linux命令clear完成清屏 Windows调用cls# 程序的开始
main()

四、面向对象

  • 面向对象VS面向过程

面向过程:数据和功能分离,下标错位/全局变量等易乱套

面向对象:数据和功能形成整体

面向对象编程有3个特征:封装 继承 多态

1.类&对象

  • 类(Class) 由3个部分构成

  • 类的名称:类名

  • 类的属性:一组数据

  • 类的方法:允许对进行操作的方法 (行为)

#经典类(旧式类)
class 类名: 方法列表
# 新式类定义形式
class 类名(object): #object 是Python 里所有类的最顶级父类;方法列表
  • 实例化对象的过程

变量名 = 类名(参数) #创建对象实例

实例对象.属性名 #实例属性

实例对象.方法名(实参) #实例方法

变量名1 = 类名() # 变量名1指向⬜1

变量名2 = 类名() # 变量名2指向⬜2

同一个类可有N个实例对象,各自独立

self能够自动指向实例对象,

实例对象.方法()==方法(实例对象)

  • self仅仅是一个变量名,也可将self换为其他任意的名字,但是为了能够让其他开发人员能明白这变量的意思,因此一般都会self当做名字

  • self会在类定义方法时自动填写

self.name用于后面方法中

此处name可以与形参名不同

chengyaojin.set_name("程咬金")

||

set_name(chengyaojin,"程咬金")

self=对象名,self.name==对象名.name所以对象的属性也可以放在方法外边用

chengyaojin.set_name("程咬金")

||

chengyaojin.name="程咬金"

  • __init__(self) 在创建一个对象时自动被调用,初始化,给对象设置属性

  • __init__(self, x, y) 带参

self代表的是'类的实例',而self.__class__则指向'类本身'

操作属性有2种方法:

通过方法间接修改 对象名.方法名(数据)

直接通过对象名修改 对象名.属性名 = 数据

在类内部获取 属性 和 实例方法,通过self获取
在类外部获取 属性 和 实例方法,通过对象名获取
如果一个类有多个对象,每个对象的属性是各自保存的,都有各自独立的地址。
但实例方法是所有对象共享的,只占用一份内存空间。类会通过self来判断是哪个对象调用了实例方法

2.私有属性&私有方法

私有 _ _XXX:内部可调用,外部不可调用

在python中的"私有”其实都是假的,python中为了能够实现让外部(即通过对象.属性名)方式失效,
所以对所有的私有(包括私有属性、私有方法)都进行了名字重整(重命名)
格式是将 _ _属性名 改为 _类名_ _属性

_ _属性名 实现数据的隐藏

_ _方法名 实现功能的隐藏

  • 父类中属性名为__名字的,子类不继承,子类不能访问

  • 如果在子类中向__名字赋值,那么会在子类中定义的一个与父类相同名字的属性

3.对象关联

一个对象的属性指向另一个对象即可完成关联

方法1:直接通过对象名.属性名=另外一个对象名,class205.student = stu1

方法2:可以通过定义一个方法,然后调用这个方法的时候将另外一个对象的引用当作实参

#如果A对象中的某个属性指向了B对象,那么调用方式:

A.xxx # 此时就是指的B对象

A.xxx.yyy() #调用B对象中的某方法

关联多个对象:

  • 方法1:在范围大的那个对象中再定义一个新的属性,通过这个属性指向新的对象

  • 方法2:如果关联的新的对象与之前关联的对象类型相同,可以考虑用列表、字典、集合等方式关联

4.继承&重写

格式:定义类的( )中写上父类的名字 #定义类时 Class类名(object):将object改为想继承的父类名即可

单继承,就是一个子类只继承一个父类 在类()中写1个父类的名字

多继承,一个类继承了多个父类 在()中写多个父类的名字而且用英文逗号,进行分割

对象调用过程:先在实例对象中找,再通过每个对象自带的_ _Class_ _跳到自身类Dog中找,再找不到则继续往上父类Animal中找,直到最大的父类object里,若都没有则报错

添加方法:如果一个方法在多个子类中用到,那么就定义在父类的方法中,否则就定义在子类中

重写:子类中定义了与父类中相同名字的方法(大白话就是:子类的方法覆盖了父类中的同名方法)

因为根据对象调用的过程可以知道,先子类后父类,子类重写方法即在子类中找到该方法,所以就不会再去父类中找了,从而达到改写效果

子类继承的父类的方法不能100%满足子类对象的需求,则重写,若父类中并不是全部一点都不能用,则用super().父类方法名()

super().父类方法名()

  • 父类中的方法并不是全部一点都不能用,即子类的需求往往是在父类方法实现的功能基础上提出了更多的需求而已

  • 如果想要在子类方法中调用被重写的父类方法就可以使用super().父类方法名()

name age相当于过一次手,不用而继续往上传,让父类执行,子类只执行自己的代码

5.多态

Python中的多态体现的非常不明显,在面向对象的其他语言中明显,如C++,Java等,因为Python在定义时不需要写类型

此处方法的调用有多种形态:如果是父类创建的对象那么就调用父类的方法 ,如果是子类创建的那么先在子类找没有再调用父类中的方法

想要实现多态,需要的条件如下:1.有继承 2.有重写

6.静态方法

如果不需要用到对象,那么就可以将方法用@staticmethod进行修饰

class 类名:@staticmethoddef A(): #型参不含selfpass实例对象.静态方法()#调用
类名.静态方法()#调用

7.类属性、类方法、类对象

当通过同一个类创建了多个实例对象之后,每个实例对象之间是相互隔离的。

类属性:想要在多个对象之间共享数据,即一些属性需要在多个对象之间共享,这样的属性就是类属性

class 类名:类属性 = ....def __init__(self, name):.....类名.类属性名 #调用 

类里边 方法前定义类属性

类方法:为了更好的对类属性进行操作,Python中提供了另外一种方法类方法

在方法的前面添加@classmethod,同时在方法的第1个形参位置添加cls,其指向类对象

 class 类名:@classmethoddef 类方法名(cls):pass类名.类方法名 #调用
对象名.类方法名 #调用 
  • 类对象(了解)

定义的类其实就是一个对象,为了能够将这个对象与其创建出来的实例对象进行区分,将这个class定义的类叫做类对象

实例对象是类 (即类对象)创建出来的,所以类对象对于实例对象而言是共享的,既然是共享的那么就干脆将实例对象都有的而且不变化的内容存储到类对象即可,这样会减少内容的占用。那哪些东西在类对象中存储呢?

  • 类属性

  • 所有的方法(无论是魔法方法、实例方法、静态方法 、类方法都在类对象中存储),因为方法(即函数)的代码是不变的,变化的仅仅是数据而已

而实例对象存储各自独有的,如实例属性

实例对象在创建时,由Python解释器自动赋予了一个特殊的属性即__class__(魔法属性)该属性的作用是:会找到相对应的实例对象的类对象内存地址。当实例对象在自己的线性地址中找不到该实例方法时,会通过__class__属性去找到创建该实例对象的类对象,直接访问类对象中线性地址存储的属性和方法。这也就是实例对象可以调用实例方法和类方法以及静态方法及类属性的主要根本原因。

所以当调用实例对象.xxx()时,实际上 实例对象.xxx()==实例对象.__class__.xxx()

既然我们知道了实例对象中有默认的__class__,那除了它之外还有哪些呢?怎么查看呢?

dir(实例对象)

['__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__']

五、Python数据结构与算法

10 个数据结构:数组、链表、栈、队列、散列表、二叉树、堆、跳表、图、Trie 树;

10 个算法:递归、排序、二分查找、搜索、哈希算法、贪心算法、分治算法、回溯算法、动态规划、字符串匹配算法。


(二)进阶知识

一、三器一闭

1.迭代器 iterator

迭代:是访问集合元素的一种方式,使用for...in...的循环语法依次拿数据的过程称为遍历,也叫迭代

可迭代对象:只要是可以通过for...in…的形式进行遍历的,那么这个数据类型就是可以迭代的

如列表、元组、字典、字符串,以及一个有__iter__方法的类创建出的对象

迭代器:是一个可记住遍历位置的对象(python要求迭代器本身也是可迭代的)。通过iter(可迭代对象)函数获取可迭代对象的迭代器,然后对迭代器不断使用next(迭代器)函数来获取可迭代对象的下一条数据

如列表、元组、字典、字符串的迭代器,以及一个有__iter__和__next__方法的类创建出的对象

关系:迭代器对象一定是可迭代对象,但是可迭代对象不一定是迭代器

凡是可作用于for 循环的对象都是 Iterable 类型;凡是可作用于 next() 函数的对象都是 Iterator 类型
集合数据类型如 list dict str等是 Iterable 但不是Iterator,不过可通过 iter() 函数获得 Iterator 对象

for...in...循环的本质

  1. 先调用iter()函数,它会自动调用可迭代对象中的__iter__方法,并返回这个可迭代对象的 迭代器对象

  1. 对获取到的迭代器不断调用next()函数,它会自动调用迭代器中的__next__方法来获取下一个值

  1. 当遇到StopIteration异常后循环结束

nums = [11, 22, 33]
nums_iter = iter(nums)num1 = next(nums_iter)
num2 = next(nums_iter)
num3 = next(nums_iter)
print(num1,num2,num3) #11 22 33 
nums = [11, 22, 33]
nums_iter = iter(nums)for num in nums:print(num) #11 22 33

并不是只有for循环能接收可迭代对象 除了for循环能接收可迭代对象,list、tuple等也能接收。

#1ist在创建一个新的列表的时候,只要是可迭代对象就可以放到1ist中当做实参
nums=(77,88,99) #元组变列表 也是依次读取
list(nums)#[77,88,99]nums=1ist(my1ist) #[1,2,3,4]

自定义迭代器 迭代对象+迭代器 两个类分开 调用关系

class MyList(object):"""自定义的一个可迭代对象"""def __init__(self):self.items = []def add(self, val):self.items.append(val)def __iter__(self):#这个方法有2个功能#1,标记用当前类创建出来的对象一定是可迭代对象#2,当调用iter()函数的时候,这个方法会被自动调用,#  它返回自己指定的那个迭代器myiterator = MyIterator(self)return myiterator #返回迭代器class MyIterator(object):"""自定义的供上面可迭代对象使用的一个迭代器"""def __init__(self, mylist):self.mylist = mylistself.current = 0 #current用来记录当前访问到的位置def __next__(self):#实现获取下一个数据#这个方法有2个功能#1,标记当前类创建出来的对象(当然了还必须有iter方法)一定是迭代器#2,当调用next函数的时候,这个方法会被自动调用它返回一个数据if self.current < len(self.mylist.items):item = self.mylist.items[self.current]self.current += 1return itemelse:raise StopIteration#抛出异常#return None#为什么自己设计迭代器获取完数据之后不返回一个None来表示,#非要用一个StopIteration异常呢?#答:自己用next挨个获取时可用return返回none代表结束,但for获取完仍继续获取none,死循环了def __iter__(self):return selfif __name__ == '__main__':mylist = MyList()mylist.add(1)mylist.add(2)mylist.add(3)for num in mylist:print(num)
#   num = list(mylist)

案例 迭代对象返+迭代器 一个类集成

class StuSystem(object):
#学生管理系统def __init__(self):self.stus = []self.current_num = 0def add(self):# 添加一个新的学生name = input("请输入新学生的姓名:")tel = input("请输入新学生的手机号:")address = input("请输入新学生的住址:")new_stu = dict() #字典new_stu["name"] = namenew_stu["tel"] = telnew_stu["address"] = addressself.stus.append(new_stu)def __iter__(self): return self  #返回自己def __next__(self): #实现获取下一个数据if self.current_num < len(self.stus):ret = self.stus[self.current_num]self.current_num += 1return retelse:self.current_num = 0raise StopIteration #抛出异常# 创建管理系统对象
stu_sys = StuSystem()
# 添加3个学生信息到系统中
stu_sys.add()
stu_sys.add()
stu_sys.add()# 问题1:怎样才能实现用for循环遍历系统中所有的学生信息呢?下面的方式能实现吗?
# 方法:原类中增加__iter__和__next__
# for temp in stu_sys:
#     print(temp)# 问题2:如果需要一个列表,这个列表 样子例如 [("张三", "10086"), ("李四", "10010")]
# 方法:stu_list = [ ...列表推导式...]  这个列表推导式改怎样写才能实现呢?
stu_list = [x for x in stu_sys]
print(stu_list)

2.生成器 generator

如果列表元素可按某种算法推算出来,那可在循环的过程中不断推算出后续的元素。这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器。

生成器是一类特殊的迭代器,可按迭代器的使用方法来使用,即使用next()函数、for循环、list()等方法

  • 创建生成器方式1: (XXX for ...in...) 只要把一个列表生成式的 [ ] 改成 ( ) ,元组生成式

nums=[x for x in range(50000000000)]for num in nums:#nums是列表print(num)
#立刻生成数据然后读取,需要事先存储,
#过程缓慢
#范围太大时产生异常 
nums=(x for x in range(5000000000))
#nums是生成器
for num in nums:#使用for循环print(num)
#什么时候要什么时候生成,不需事先存储
num1=next(nums)#使用next,取完触发异常
nums2=list(nums)#使用list()

列表如果需要的数据量大,程序运行效率很低,占用大量内存。

生成器仅仅是生成一个对象,这个对象中记录了怎样生成数据,占用内存很小。

  • 创建生成器方式2: 函数+yield

如果推算的算法比较复杂,用 for 循环无法实现的时候,还可以用之前学习的函数来自己实现一个生成器

def fib_generator():num1 = 1num2 = 1while True:temp_num = num1num1, num2 = num2, num1+num2#return temp_num yield temp_num #只要在函数中有yield,不管是否有return,那么这个函数一定是生成器#如果一个函数中有yield,那么本来看上去fib=generator()是调用函数,实际上已经变成了创建一个生成器对象
fib=fib_generator()#只要在def函数中有yield关键字的 就称为生成器
num1=next(fib)#因为fib此时是生成器,又因为生成器是一个迭代器,所以可通过next函数取到一个数据

yield:使用了yield关键字的函数不再是函数,而是生成器

保存当前运行状态(断点),然后暂停执行,即将生成器(函数)挂起
将yield关键字后面表达式的值作为返回值返回,此时可以理解为起到了return的作用
可以使用next()函数让生成器从断点处继续执行,即唤醒生成器(函数)

  1. 如果是第一次执行,则从def代码块的开始部分执行,直到遇到yield为止,并且把yield关键字后的数值返回,当做next()的返回值

  1. 如果不是第一次执行,则从上一次暂停的位置执行(即从上一次yield关键字的下一个语句开始执行),直到遇到下一次yield为止,并且把yield关键字后的数值返回,当做next()的返回值

return:调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中。

异常:在调用next时,接下来没有遇到yield,或是遇到了return,或者数据取完则会产生异常

return接收一个函数,且有返回值
yield暂停执行一个函数,且有返回值

send():除了可以使用next()函数来唤醒,让生成器继续执行外,还可以使用send()函数来唤醒执行。

使用send()函数的一个好处是:可以在唤醒的同时向断点处传入一个附加数据

第一次的 next(g)不可少,或者替换为 g.send(None),因为传递的值只能当作是yield上一次执行的值

生成器是这样一个函数,它记住上一次返回时在函数体中的位置。对生成器函数的第二次(或第 n 次)调用跳转至该函数中间,而上次调用的所有局部变量都保持不变。生成器不仅“记住”了它数据状态;生成器还“记住”了它在流控制构造(在命令式编程中,这种构造不只是数据值)中的位置。

生成器的特点:存储的是生成数据的方式(即算法),而不是存储生成的数据,因此节约内存

3.闭包 closure

如果一个函数中又嵌套定义了另外一个函数,且内部的这个函数用到了外部函数的局部变量或者形参

def person(name):num = 100def say(content): print("(%s):%s"%(name,content))return say #地址 引用p1=person("张三") #张三给参数name
p1("你努力了吗?") #你努力了吗?给参数content
#为什么不能直接调用say(),因为person是全局变量可直接调用,而say属于局部变量

nonlocal:修改闭包中外部变量的值

def counter(start=0):def add_one():nonlocal startstart += 1return startreturn add_onec1 = counter(5)  # 创建一个闭包
print(c1())#6
print(c1())#7c2 = counter(50)  # 创建另外一个闭包
print(c2())#51
print(c2())#52
#多个闭包间互不影响
print(c1())#8
print(c2())#53

闭包与对象有些类似,数据(属性)+功能(方法)。只不过此时数据是外部函数中的那些局部变量或者形参,而功能则是内部函数。对象适合完成较为复杂的功能,而闭包则更轻量,耗费资源少。

函数、匿名函数、闭包、对象当做实参时 有什么区别?

匿名函数能够完成基本的简单功能,,,传递是这个函数的引用 只有功能
普通函数能够完成较为复杂的功能,,,传递是这个函数的引用 只有功能
闭包能够将较为复杂的功能,,,传递是这个闭包中的函数以及数据,因此传递是功能+数据(形参)
对象能够完成最为复杂的功能,,,传递是很多数据+很多功能,因此传递是功能+数据(形参)

4.装饰器 decorator

在不修改原函数代码的基础上,对函数执行之前和执行之后添加额外功能

@XXX 语法糖
def fun()

def check_login(func):def inner():# 功能段1 #上面增加功能func()   #原函数不动# 功能段2 #下面增加功能return inner@check_login #f1=check_login(f1) 调用check_login并把f1当参数,返回值当做新的f1的值
def f1():print('f1')f1()#实际效果相当于inner函数+f1函数混合执行
  • 普通闭包:内部函数将使用的外部变量当做数据来用

  • 将闭包当做装饰器:内部函数将使用的外部变量当做可调用的对象(例如函数)来调用

装饰器(decorator)功能:
引入日志
函数执行时间统计
执行函数前预备处理
执行函数后清理功能
权限校验等场景
缓存

return:一般情况下为了让装饰器更通用,可以有return,即使这个被装饰的函数默认没有返回值也不会有问题,因为此时相当于return None

  • 多个装饰器对同一个函数进行装饰:装饰时从下往上装,但调用时依然从上往下

  • 带有参数的装饰器

用call_out2(2)的返回值给函数print_hello()进行装饰,即print_hello=call_out1(print_hello)

  • 将类当做装饰器

  • 带有参数的类当作装饰器

用Test(100)的返回值(是一个实例对象)给函数a()进行装饰,即a=实例对象(a)

二、拷贝、with

1.== VS is

  • is是比较两个引用是否指向了同一个对象(引用比较)

  • == 是比较两个对象的值是否相等(值比较)

2.浅拷贝VS深拷贝

  • 浅拷贝:拷贝了引用,并没有拷贝内容

  • 深拷贝:对于一个对象所有层次的拷贝(递归)

  • XX1=XX2,复制的是引用

  • copy.copy() 浅拷贝 和 copy.deepcopy() 深拷贝

  • 切片:如列表切片等,也是浅拷贝

  • 字典的copy方法可以浅拷贝一个字典

  • 浅拷贝对不可变类型和可变类型的copy不同

  1. copy.copy对于可变类型,会进行浅拷贝,即只拷贝最顶层的

  1. copy.copy对于不可变类型,仅仅复制引用,XXX=XXX

  • 元素嵌套时的深拷贝与浅拷贝

如果浅拷贝能用则用,否则再用深拷贝,这样节省内存

3.with与上下文管理器

对于系统资源如文件、数据库连接、socket 而言,应用程序打开这些资源并执行完业务逻辑之后,必须做的一件事就是要关闭(断开)该资源。一般关闭使用XXX.close(),但是这样方式容易在产生异常时不会被自动调用,即可能出现资源没有被正确的关闭,这会导致系统资源一直占用。

  • 上下文context 环境

上下文管理器,简单来说分为2部分,一部分是申请资源,另外一部分是释放资源,而在with中使用资源。with会自动调用申请资源,以及释放资源的代码,所以更便捷。

方式一:任何实现了 __enter__() 和 __exit__() 方法的对象都可称之为上下文管理器,上下文管理器对象可以使用 with 关键字。

方式二: contextmanager 的装饰器更加简化,通过 yield 将函数分割成两部分

三、面向对象提高

  1. 封装、继承、多态

  1. 静态方法和类方法

  • 实例方法:由对象调用;至少一个self参数;执行实例方法时自动将调用该方法的对象赋值给self;

  • 类方法:由类调用; 至少一个cls参数;执行类方法时,自动将调用该方法的类赋值给cls;

  • 静态方法:由类调用;无默认参数

类对象可以调用类方法,类对象可以调用静态方法,实列对象可以调用实例方法、类方法、静态方法

按道理实例对象只可以调用实例方法,但为什么实例对象可以调用类方法和静态方法呢?

原因在于类方法和静态方法前都有装饰器,已经对原方法进行了完善。类方法的装饰器功能是:如果传递的是类对象则直接传递,如果是实例对象则找到他的类传进去;而静态方法的装饰器什么都不传。

具体实现过程在描述符的应用

  • 类属性和实例属性

它们在定义和使用中有所区别,而最本质的区别是内存中保存的位置不同

  • 类属性在内存中只保存一份

  • 实例属性在每个对象中都要保存一份

  • 实例方法、类方法、静态方法均属于类,所以 在内存中也只保存一份

  • 不同点:方法调用者不同、调用方法时自动传入的参数不同(类对象可调用类方法、静态方法,实例对象都可调用)

  1. 多继承以及MRO顺序

super在调用父类的时候,它需要计算出当前到底调用哪个父类,在Pythony中实现这个功能的算法叫做C3算法。 类名.__mro__

1.super().__init__相对于类名 .__init__,在单继承上用法基本无差
2.但在多继承上有区别,super方法能保证每个父类的方法 只会执行一次,而使用类名的方法会导致方法 被执行多次,具体看前面的输出结果
3.多继承时,使用super方法,对父类的传参数,由于super的算法导致的原因,必须把参数全部传递,否则会报错
4.单继承时,使用super方法,则不能全部传递,只能传父类方法所需的参数,否则会报错
5.多继承时,相对于使用类名.__init__方法,要把每个父类全部写一遍, 而使用super方法,只需写一句话便执行了全部父类的方法,这也是为何多继承需要全部传参的一个原因

  1. 内建属性

方法其实也是属性,本质都是一个变量,指向了地址的引用

Python解释器刚开始运行时,会自动加载一些常用的变量、函数以及类,不需要额外导入引用。

dir(类名) 魔法属性

  • __new__用来创建类并返回这个类的实例,在__init__(self) 方法之前调用,因为要先创建好对象

在实际开发中,很少会用到 __new__ 方法,除非你希望能够控制类的创建。

  • __init__ 初始化方法,通过类创建对象时,自动触发执行

class Person:def __init__(self, name):self.name = nameself.age = 18obj = Person('laowang')  # 自动执行类中的 __init__ 方法
  • __doc__表示类的描述信息

class Foo:""" 描述类信息,这是用于看片的神奇 """def func(self):passprint(Foo.__doc__) #输出:类的描述信息
  • __del__当对象在内存中被释放之前,自动触发执行

注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,__del__的调用是由解释器在进行垃圾回收前 自动触发,执行一些完善工作。

classFoo:def__del__(self):pass
  • __call__ 对象后面加括号,触发执行

注:__init__方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即: 对象() 或者 类()

class Foo:def __init__(self):passdef __call__(self, *args, **kwargs):print('__call__')obj = Foo()  # 执行 __init__
obj()        # 执行 __call__
  • __dict__

  • __module__ 表示当前操作的对象在那个模块 即哪个.py

  • __class__ 表示当前操作的对象的类是什么

  • __getattribute__

  • __getitem__、__setitem__、__delitem__用于索引操作,如字典。获取、设置、删除数据

# -*- coding:utf-8 -*-
class Foo(object):def __getitem__(self, key):print('__getitem__', key)def __setitem__(self, key, value):print('__setitem__', key, value)def __delitem__(self, key):print('__delitem__', key)obj = Foo()result = obj['k1']      # __getitem__ k1         自动触发执行 __getitem__
obj['k2'] = 'laowang'   # __setitem__ k2 laowang 自动触发执行 __setitem__
del obj['k1']           # __delitem__ k1         自动触发执行 __delitem__
  • __getslice__、__setslice__、__delslice__用于分片操作,注意在Python2中有效,Python3已被移除

  1. 内建函数

Python解释器刚开始运行时,会自动加载一些常用的变量、函数以及类,不需要额外导入引用。

可以直接使用那些函数,而不是指在类中方法。dir(__builtin__),这些函数数量众多,用help(function)

range(start, stop[, step])#范围
map(function, sequence[, sequence, ...])->list#根据提供的函数对指定序列做映射
filter(function or None, sequence)->list tuple string#对指定序列执行过滤,true保留
reduce(function, sequence[, initial]) ->value#对参数序列中元素进行重复操作
  1. Python的动态绑定

Python可以在运行的过程中,修改程序的运行结构,例如可以修改调用的函数等,开发更方便。相对于动态语言,静态语言具有严谨性!如c、c++

  • 添加属性:实例属性 、类属性

  • 删除属性

  1. del 对象.属性名

  1. delattr(对象, "属性名")

  • 添加方法:实例方法、类方法、静态方法

  • __slots__

注意:__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的

  1. property属性

问题:给一个实例对象的属性赋值时,容易出现错误,或者数据类型不对的情况

学习了property属性,通过实例对象.属性=XXX的时候,会自动调用某个方法,可以在这个方法中对数据进行校验等处理。

看上去是调用属性,但实际上是调用某个方法。把这种特殊的属性,称之为property属性

  • 装饰器方式

property方法中有个四个参数

  • 第1个参数:方法名,调用 对象.属性 时自动触发执行方法 获取

  • 第2个参数:方法名,调用 对象.属性 = XXX 时自动触发执行方法 修改

  • 第3个参数:方法名,调用 del 对象.属性 时自动触发执行方法 删除

  • 第4个参数:字符串,调用 对象.属性.__doc_ ,此参数是该属性的描述信息

  • 类属性方式

property的2种使用的方式,第一种可读性比较高,在大项目中往往使用。

案例:

  • 私有属性添加getter和setter方法

  • 使用property升级getter和setter方法(相对简洁)

  • 使用property取代getter和setter方法(可读性好)

  1. 私有化

  • xx: 公有变量

  • _x: 单前置下划线,私有化属性或方法,from somemodule import *禁止导入,类对象和子类可以访问

  • __xx:双前置下划线,避免与子类中的属性命名冲突,无法在外部直接访问(名字重整所以访问不到)

  • __xx__:双前后下划线,用户名字空间的魔法对象或属性。例如:__init__ , __ 不要自己发明这样的名字

  • xx_:单后置下划线, 用于避免与Python关键词的冲突

四、元类

python中一切皆对象,对象是由类创建出来的,类是由元类创建的

元类:就是一个特殊的类,用来专门创建其他类的类。

元类(type)-->元类(type)-->...元类(type)-->类(类对象)-->实例对象

  • 动态创建类方式1:

  • 使用type创建类

type()除了可查看类型,还可创建类

type(类名, 由父类名称组成的元组(针对无继承的情况,可以为空),包含属性的字典(名称和值))

使用type可以创建一个与class定义类一模一样的类,只不过class定义的类可读性好,而type创建的类更随意,体现了type创建类是的动态特点

  • 自定义元类使用class定义的修改

元类几乎没什么用,整个python,唯独使用的地方只有数据库的ORM模型

五、描述符

1.__getattribute__和__getattr__

  • __getattr__只针对未定义属性的调用,调用未定义属性时改为执行__getattr__方法

  • __getattribute__针对所有的属性运行,只要调用属性就改为执行__getattribute__方法

通过类对象调用属性,不会执行__getattribute__方法

__getattr__与__getattribute__都是对访问属性时的特殊操作,如果同时出现,会调用__getattribute__

2.描述符Descriptor

描述符对象:一个实现了__get__()、__set__()和__delete__()中任意一种方法的新式类所创建的实例对象

描述符:如果有另外一个类,这个类中有一个类属性,这个类属性相对应的是上面类创建的实例对象

程序从上往下执行时,遇到类会先看看里面有什么东西(先执行),看完后元类便会创建出一个类对象

描述符可以做类型检查,比property更方便

  • property的不足:对property来说,最大的缺点就是它们不能重复使用。

为budget和gross这些字段也添加非负检查,可以看到代码增加了不少,但重复的逻辑也出现了不少。

  • 描述符是property的升级版,允许你为重复的property逻辑编写单独的类来处理

3.描述符调用机制

3.1__dict__访问顺序

在Python中所有的对象中的所有属性,以及方法都是用字典的方式进行存储,属性名或者方法名当做key,而指向的东西当做value,可以通过xx.__dict__来获取这个字典

__dict__返回的是一个字典,类和实例都可以调用,键就是类或实例所拥有的属性、方法,可以用这个字典访问属性,但是方法就不能这样直接访问

当我们调用aa.m时的访问顺序

  1. 程序会先查找 aa.__dict__['m'] 是否存在

  1. 不存在再到type(aa).__dict__['m']中查找

  1. 然后找type(aa)的父类

  1. 期间找到的是普通值就输出,如果找到的是一个描述符,则调用__get__方法

3.2 数据描述符、非数据描述符

  • 同时定义了__get__和__set__方法的描述符称为数据描述符(资料描述符)

  • 只定义了__get__的描述符称为非数据描述符(非资料描述符)

二者的区别是:当属性名和描述符名相同时,在访问这个同名属性时,如果是资料描述符就会先访问描述符,如果是非资料描述符就会先访问属性

调用属性时,先看父类中有无描述符,不同情况结果不同。

  • 只读描述器

只可查看不可修改,需要同时定义 __set__ 和 __get__,并在 __set__ 中引发一个 AttributeError 异常。

3.3描述符的细节

  • 调用描述符的原理

  • __get__、__set__中的参数说明

  • 不能将描述符放在实例属性中

  • Descriptor的实例一定是类的属性(公共的),因此使用的时候需要自行区分

4.描述符应用

4.1.实现@classmethod

4.2 惰性计算

  • setattr

  • __name__

  • 惰性计算案例

可在增加一个只读描述符,不允许修改pi的值3.14

六、垃圾回收(了解)

开发时几乎不用,但如果不知道垃圾回收,可能会出现因为内存不够程序闪退。

  1. 垃圾回收一

  • 小整数对象池

Python 对小整数的定义是 [-5, 256] ,把这个范围称为小整数对象池,

Python为了让程序运行效率更快,所以事先创建好了很多经常用的数字,不会被垃圾回收。在一个 Python 的程序中,所有位于这个范围内的整数使用的都是同一个对象.

同理,单个字母也是这样的。但是当定义2个相同的字符串时,引用计数为0,触发垃圾回收

  • 大整数对象池

每一个大整数,均创建一个新的对象。

  • intern机制(字符串驻留)

  1. 垃圾回收二

2.1 Garbage collection(GC垃圾回收)

C语言中,开发者调用某个函数,例如malloc(100)表示申请100个字节,使用free()来进行释放,如果忘记了释放,则产生遗留的内存,这些内存就是垃圾,我们把这种方式叫做 内存泄漏
Python以引用计数为主,以隔代收集为辅的方式保证了内存尽量不会出现垃圾
C语言中是自己管理,Python中是Python解释器帮我们管理,从表面上看Python.更智能
但是,Pytho解释器有时会留下一部分请理不了的内存,此时就是我们研究GC的原因

  • 引用计数

Pythor中为了能够知道当前这个对象有多少个变量指向它,因此会在每个对象中有一个小小的空间,里面存放引用计数。

  • 画说 Ruby 与 Python 垃圾回收

Ruby提前创建了成百上千个对象,并把它们串在链表上,名曰:可用列表。

Python什么时候创建什么时候申请:创建对象的时候,Python会花些时间为我们找到并分配内存。

为变量n1赋新值,Ruby把旧值留在原处。

Python使用引用计数GC算法:每当对象的引用数减为0,Python立即将其释放,把内存还给操作系统

Ruby使用标记-清除,再通过调整内部指针,将其指向一个新链表

Python使用引用计数GC算法:每当对象的引用数减为0,Python立即将其释放,把内存还给操作系统

  • 分代收集解决循环引用问题

Python使用另一种不同的链表来持续追踪活跃的对象。Python会循环遍历零代列表上的每个对象,找出列表中每个互相引用的对象,根据规则减掉其引用计数。

计数值-1 变为0则实现回收

触发的条件:Python中的GC阈值 跟剩余的内存(生成的-释放的)进行比较

随着时间的推移,程序所使用的对象逐渐从零代列表移动到一代列表。而Python对于一代列表中对象的处理遵循同样的方法,一旦被分配计数值与被释放计数值累计到达一定阈值,Python会将剩下的活跃对象移动到二代列表。

从某种意义上说,Python的GC算法类似于Ruby所用的标记回收算法。

  1. 垃圾回收三

在Python中,采用分代收集的方法。把对象分为三代,一开始,对象在创建的时候,放在一代中,如果在一次一代的垃圾检查中,该对象存活下来,就会被放到二代中,同理在一次二代的垃圾检查中,该对象存活下来,就会被放到三代中。

这三条链类似秒分时,触发条件不同。清理0代链触发条件是GC阈值,清理一代链触发条件是清理0代链的次数达到一定值,清理二代链触发条件是清理一代链的次数达到一定值。

能在第三条链子上留着的说明这些对象一直保留,一定是整个代码的核心数据

有三种情况会触发垃圾回收:

  1. 当gc模块的计数器达到阈值的时候,自动回收垃圾

  1. 调用gc.collect(),手动回收垃圾

  1. 程序退出的时候,python解释器来回收垃圾

七、logging日志

  1. 日志级别

  1. 日志输出

  • 将日志输出到控制台

  • 将日志输出到文件

  • 既要把日志输出到控制台, 还要写入日志文件

3.日志格式


(三)正则表达式

一、re模块

在Python中需要通过正则表达式对字符串进行匹配的时候,可以使用一个模块,名字为re

  • re.match() 能够匹配出以"xx"开头的字符串

import re
result = re.match(正则表达式,要匹配的字符串)# 使用match方法进行匹配操作
result.group()# 如果上一步匹配到数据的话,可以使用group方法来提取数据
  • 匹配字符

  • 匹配开头结尾

  • 匹配个数

  • 匹配分组

re.search() #按照需求,在整个字符串中查找,而不一定是从开头
re.findall()#查询所有
re.sub()#按照规则去查询字符串,并且将匹配到的数据进行替换

二、贪婪和非贪婪

Python里数量词默认是贪婪的(在少数语言里也可能是默认非贪婪),总是尝试匹配尽可能多的字符;

非贪婪则相反,总是尝试匹配尽可能少的字符。

在"*","?","+","{m,n}"后面加上?,使贪婪变成非贪婪。


(四)多任务程序

假的多任务:操作系统为了让多个程序,都能够得到执行的机会,采用了一系列的方案来实现,例如:时间片调度

操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。

如果一个程序想同时执行多个部分的代码,那么基本有2种方式实现:线程、进程

一、线程

线程是一个抽象的概念,可以把它想象成程序在执行代码时的那个执行流,创建出一个新线程,即又分出一个执行流箭头

1.当一个程序运行时,默认有一个线程,这个线程我们称之为主线程

2.多任务也就可以理解为让你的代码再运行的过程中额外创建一些线程,让这些线程去执行代码

在Python中如果想使用线程实现多任务,可以使用thread模块 但是它比较底层,即意味着过程较为复杂不方便使用;推荐使用threading模块,它是对thread做了一些包装的,可以更加方便使用

创建出来的线程是不会执行,调用start方法才可让这个线程开始运行

1.一个程序中,可以有多个线程执行同一相同的代码,但是每个线程执行每个线程的功能,互不影响,仅仅是做的事情相同罢了
2.当在创建Thread对象是target执行的函数的代码执行完之后,意味着这个子线程结束
3.虽然主线程没有了代码,但是它依然会等待所有的子线程结束之后它才会真正的结束,原因是:主线程有个特殊的功能对子线程产生的垃圾进行回收处理。当主线程结束后,才意味着整个程序真正结束。

多线程执行的顺序不确定,因为操作系统的调度顺序不确定的(系统运行环境与资源分配可能随时变)

每一次运行,谁先执行打印不确定,每一次都要抢

t1和t2每次谁先开始,不一定

可以在中间加个延时,让t1先执行然后再执行t2,没有延时不一定是t1先开始执行

threading.enumerate() #返回的线程数量
  • 创建线程:函数的方式 创建&传参

target指明去哪里执行代码,args kwargs指明去执行代码的时候所携带的参数

import threadingdef task 1(num,num2):passt1=threading.Thread(target=函数名,args=(11,),kwargs={"num2":22}) #args必须是元组
t1.start() #真正的创建线程

target=是采取的命名参数格式 (形参名=XXX),指向函数的引用
args是传递一个元组,元组中的数据个数与target指定的函数形参个数、顺序一致
kwargs是一个字典 key当做形参中的变量名,value是给这个形参传递的数值,形参=XXX 命名参数

案例-"udp聊天程序”,实现同时收发数据

import socket
import threadingdef send_msg(udp_socket):"""获取键盘数据,并将其发送给对方"""while True:print("1: 发送数据")print("2: 退出程序")op = input("请输入操作序号:")if op == "1":# 1. 输入对方的ip地址dest_ip = input("\n请输入对方的ip地址:")# 2. 输入对方的portdest_port = int(input("\n请输入对方的port:"))while True:# 3. 从键盘输入数据msg = input("\n请输入要发送的数据:")if msg:# 4. 发送数据udp_socket.sendto(msg.encode("utf-8"), (dest_ip, dest_port))else:# 要是没有输入内容则认为是要重新输入ip、portbreakelif op == "2":breakudp_socket.close()def recv_msg(udp_socket):"""接收数据并显示"""while True:try:# 1. 接收数据recv_msg = udp_socket.recvfrom(1024)except:breakelse:# 2. 解码recv_ip = recv_msg[1]recv_msg = recv_msg[0].decode("utf-8")# 3. 显示接收到的数据print(">>>%s:%s" % (str(recv_ip), recv_msg))def main():# 1. 创建套接字udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)# 2. 绑定本地信息udp_socket.bind(("", 7890))# 3. 创建一个新的线程,用来接收数据udp_r = threading.Thread(target=recv_msg, args=(udp_socket,))# 4. 创建一个新的线程,用来发送数据udp_s = threading.Thread(target=send_msg, args=(udp_socket,))# 5. 运行创建的子线程udp_r.start()udp_s.start()if __name__ == "__main__":main()
  • 创建线程的另一种方式:类的方式 创建

若函数间数据相互影响,进程结束则函数结束,易乱套。因此提出了类方式,使数据跟功能成为整体

创建子类,继承线程类threading,通过实例对象完成功能

可以自己定义一个类,但是这个类要继承Thread

1.python的threading.Thread类有一个run方法,用于定义线程的功能函数可以在自己的线程类中覆盖该方法(重写)。
2.线程会自动调用run方法, 类中其他方法应放在run方法内才可实现调用
3.当线程的run()方法结束时表示该线程结束

案例-并发服务器:为了解决不能同时服务的问题,可以考虑使用多线程来完成TCP的服务器

TCPServer线程(老板)只负责链接,来一个客人创建一个新HandleData线程(小二服务员)去服务

import socket
import threadingclass HandleData(threading.Thread):def __init__(self, client_socket):super().__init__()self.client_socket = client_socketdef run(self):# 接收/发送数据while True:recv_content = self.client_socket.recv(1024)if len(recv_content) != 0:print(recv_content)self.client_socket.send(recv_content)else:self.client_socket.close()breakdef __del__(self):self.client_socket.close()class TCPServer(threading.Thread):def __init__(self, port):# 调用父类的初始化方法# threading.Thread.__init__(self)super().__init__()# 创建套接字self.server_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 绑定本地信息self.server_s.bind(("", port))# 将套接字由默认的主动链接模式改为被动模式(监听模块)self.server_s.listen(128)def run(self):# 等待客户端进行链接while True:new_s, client_info = self.server_s.accept()print(client_info)# t = HandleData(new_s)# t.start()HandleData(new_s).start()def __del__(self):# 关闭套接字 self.server_s.close()def main():tcp_server = TCPServer(7788)  # 7788表示TCP要绑定的端口tcp_server.start()if __name__ == '__main__':main()
  • 线程间通信 Queue

方式一:队列

队列(Queue)

先进先出(FIFO)
可以存放任意类型数据

淘宝双十一抢购,当时处理不过来,先把信息存储,然后谁先付的款先卖谁,实现 缓冲(先存储后处理)

堆栈Queue

后进先出(LIFO)
可以存放任意数据类型

last in first out

堆水泥袋,用时先取上面的
即先处理后放的

优先级Queue

存放的是元组类型,第1个元素表示优先级,第2个表示存储的数据
优先级数字越小优先级越高
数据优先级高的优先被取出

用于VIP用户数据优先被取出场景,因为上面两种都要挨个取出

使用put放数据
使用gt取数据(如果当前队列中没有数据,此时会堵塞)

案例-带有聊天记录的UDP聊天程序

import socket
import threading
import queueclass SendMsg(threading.Thread):def __init__(self, udp_socket, queue):super().__init__()self.udp_socket = udp_socketself. queue = queuedef run(self):"""获取键盘数据,并将其发送给对方"""while True:print("1: 发送数据")print("2: 退出程序")op = input("请输入操作序号:")if op == "1":# 1. 输入对方的ip地址dest_ip = input("\n请输入对方的ip地址:")# 2. 输入对方的portdest_port = int(input("\n请输入对方的port:"))while True:# 3. 从键盘输入数据msg = input("\n请输入要发送的数据:")if msg:# 4. 发送数据self.udp_socket.sendto(msg.encode("utf-8"), (dest_ip, dest_port))info = "<<<(%s, %d):%s\n" % (dest_ip, dest_port, msg)self.queue.put(info)else:# 要是没有输入内容则认为是要重新输入ip、portbreakelif op == "2":breakdef __del__(self):self.udp_socket.close()class RecvMsg(threading.Thread):def __init__(self, udp_socket, queue):super().__init__()self.udp_socket = udp_socketself.queue = queuedef run(self):"""接收数据并显示"""while True:try:# 1. 接收数据recv_msg = self.udp_socket.recvfrom(1024)except:breakelse:# 2. 解码recv_ip = recv_msg[1]recv_msg = recv_msg[0].decode("utf-8")# 3. 显示接收到的数据info = ">>>%s:%s\n" % (str(recv_ip), recv_msg)print(info)self.queue.put(info)def __del__(self):self.udp_socket.close()class SaveChat(threading.Thread):def __init__(self, queue):super().__init__()self.queue = queuedef run(self):while True:with open("./chat.txt", "a") as f:chat_info = self.queue.get()print("正在将(%s)写入到聊天记录文件中" % chat_info)f.write(chat_info)def main():# 创建套接字udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)# 绑定本地信息udp_socket.bind(("", 7890))# 创建一个FiFo的队列q = queue.Queue()# 创建线程对象udp_r = RecvMsg(udp_socket, q)udp_s = SendMsg(udp_socket, q)chat_thread = SaveChat(q)# 运行创建的子线程udp_r.start()udp_s.start()chat_thread.start()if __name__ == "__main__":main()

方式二:全局变量

通过全局变量可以解决多个线程之间共享数据的问题,但是,使用不够恰当会乱套

  • 线程对全局变量随意遂改可能造成多线程之间对全局变量的混乱(即线程非安全)

  • 此外,如果多个线程同时对同一个全局变量操作,会出现资源竞争问题,从而数据结果会不正确 解决方法:互斥锁

互斥锁

互斥锁为资源引入一个状态:锁定/非锁定

某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

上了锁后执行任务可以避免被打扰,别人只能等释放锁

# 创建锁(全局)
mutex = threading.Lock()# 锁定
mutex.acquire()
......#中间的内容不被打扰
# 释放
mutex.release()

如果这个锁之前是没有上锁的,那么acquire不会堵塞(堵塞:程序卡在这里,死等某个条件满足)
如果在调用acquire对这个锁上锁之前 它已经被 其他线程上了锁,那么此时acquire会堵塞,直到这个锁被解锁为止(注意:一定是同一把锁才可以)

锁的好处:

  • 确保了某段关键代码同时只能由一个线程从头到尾完整地执行

锁的坏处:

  • 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了

  • 由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁

二、进程

进程:一个程序运行起来后,代码+用到的资源 称之为进程,它是操作系统分配资源的基本单元。

进程的创建及传参方式跟线程几乎一样,只不过线程类换成了进程类

  • 创建进程方式一:创建Process对象

multiprocessing模块是跨平台版本的多进程模块,提供了一个Process类来创建一个进程对象,这个对象可以理解为是一个独立的进程,可以执行另外的事情

#1.导入模块
#2.定义一个函数·表示执行要执行的代码
#3.创建一个进程对象,用target指明函数引用
#4.调用它的stat方法才会其正的创建进程
#主进程继续向下运行

#1,导入multiprocess1ng
#2,定义一个函数(子进程执行的代码)
#3,进程对象=multiprocess1ng.Process(target=函数名)
#4,进程对象,start()#真正的创建进程
#主进程继续向下运行代码

start():启动子进程实例(创建子进程)
is_alive():判断子进程是否还在活着
join([timeout]):是否等待子进程执行结束,或等待多少秒
terminate():不管任务是否完成,立即终止子进程

创建进程方式二:基础Process类,创建自己的对象,实现run方法

#1.导入threading
#2.定义一个类,继承threading.Thread
# 可以重写_init_,这个方法可以用来接收创建对象时要传送的参数
# def_init_(self,xxxxx):
# super(._init())
# 一定要实现run方法

#3,通过自己定义的类,创建出一个对象,这个对象就是线程对象
# 如果要是给这个自定义的类传递了参数,类名(参数)

#4.对象.start()

进程 VS 线程

  • 进程之间不共享全局变量

1.当创建一个子进程时,会复制父进程的很多东西(全局变量等)类似分身(如手机里的微信分身)

2.子进程和主进程是单独的2个进程,不是一个

  • 当一个进程结束的时候,不会对其他的进程产生影响

  • 线程之间共享全局变量

1.所有的线程都在同一个进程中,这个进程是主进程

都是3个箭头同时执行,加工为什么要分线程跟进程?

多线程使用同一份资源,线程数量多了可能会导致资源崩掉;有一个线程崩溃对资源有较大影响

多进程是多份独立的资源,更加稳定,但进程多了耗资源大

线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。

线程不能够独立执行,必须依存在进程中。

进程间通信--队列Queue

进程间是相互独立的,数据不共享,但有时需要数据共享,就需要进程间通信(IPC)

如果在一台电脑上不同进程间通信,就可以用其它的方式实现(知道即可,不需要深入研究,研究操作系统时才需要深入研究它们):
文件(一个进程写入到文件,一个进程从文件中读取)
共享内存
管道等等

  1. 使用Queue()时,若括号中没有指定最大可接收的消息数量,或数量为负值,那么就代表可接受的消息数量没有上限(直到内存的尽头)

  1. Queue的几个方法功能说明:

Queue.qsize():返回当前队列包含的消息数量
Queue.empty():如果队列为空,返回True,反之False
Queue.full():如果队列满了,返回True,反之False
Queue.get([block[, timeout]]):获取队列中的一条消息,然后将其从列队中移除,block默认值为Truei. 如果block使用默认值,且没有设置timeout(单位秒),消息列队如果为空,此时程序将被阻塞(停在读取状态),直到从消息列队读到消息为止,如果设置了timeout,则会等待timeout秒,若还没读取到任何消息,则抛出Queue.Empty异常ii. 如果block值为False,消息列队如果为空,则会立刻抛出Queue.Empty异常
Queue.get_nowait():相当Queue.get(False)
Queue.put(item,[block[, timeout]]):将item消息写入队列,block默认值为Truei. 如果block使用默认值,且没有设置timeout(单位秒),消息列队如果已经没有空间可写入,此时程序将被阻塞(停在写入状态),直到从消息列队腾出空间为止,如果设置了timeout,则会等待timeout秒,若还没空间,则抛出Queue.Full异常ii. 如果block值为False,消息列队如果没有空间可写入,则会立刻抛出Queue.Full异常g. Queue.put_nowait(item):相当Queue.put(item, False)

创建进程方式三:进程池

进程池可以让创建的进程重复利用,而之前的进程函数执行完进程也就结束了,创建一个进程和释放一个进程的所用到的资源比较多,频繁创建和释放反而不会加快代码执行,进程池可节省资源使用

子线程任务结束但线程不结束,从任务队列中取出先缓存的任务继续执行,即这3个线程进行重复工作

初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会用之前的进程来执行新的任务
主进程不会因为进程池满了就等待,而是仍然执行自己的任务,还添加进程则在任务队列中缓冲执行
pool.close()表示不再向进程池添加任务了
pool.join()#主进程等待,直到所有的pool标记的进程池中所有的进程都结束之后,主进程才会继续向下运行。此外还可以回收已经结束的进程资源

进程号

import os
pid1=os.getpid()#获取当前进程的进程号
pid2=os.getppid()#获取父进程号 

每个进程都有1个数字来标记,这个数字称之为进程号
Linux系统中查看PID的命令是ps (ps -aux显示更多信息),kill pid的方式结束一个进程(kill12345 -9表示强制杀掉这个进程)

进程池中的Queue

如果要使用Pool创建进程,就需要使用multiprocessing.Manager()中的Queue(),而不是multiprocessing.Queue()

案例-文件夹copy器(多进程版)

import multiprocessing
import os
import time
import randomdef copy_file(queue, file_name,source_folder_name,  dest_folder_name):"""copy文件到指定的路径"""f_read = open(source_folder_name + "/" + file_name, "rb")f_write = open(dest_folder_name + "/" + file_name, "wb")while True:time.sleep(random.random())content = f_read.read(1024)if content:f_write.write(content)else:breakf_read.close()f_write.close()# 发送已经拷贝完毕的文件名字queue.put(file_name)def main():# 获取要复制的文件夹source_folder_name = input("请输入要复制文件夹名字:")# 整理目标文件夹dest_folder_name = source_folder_name + "[副本]"# 创建目标文件夹try:os.mkdir(dest_folder_name)except:pass  # 如果文件夹已经存在,那么创建会失败# 获取这个文件夹中所有的普通文件名file_names = os.listdir(source_folder_name)# 创建Queuequeue = multiprocessing.Manager().Queue()# 创建进程池pool = multiprocessing.Pool(3)for file_name in file_names:# 向进程池中添加任务pool.apply_async(copy_file, args=(queue, file_name, source_folder_name, dest_folder_name))# 主进程显示进度pool.close()all_file_num = len(file_names)while True:file_name = queue.get()if file_name in file_names:file_names.remove(file_name)copy_rate = (all_file_num-len(file_names))*100/all_file_numprint("\r%.2f...(%s)" % (copy_rate, file_name) + " "*50, end="")if copy_rate >= 100:breakprint()if __name__ == "__main__":main()

三、协程

协程,又称微线程,纤程。英文名Coroutine。

计算密集型任务:使用多进程(大数据计算,实时性高,如比特币,比谁先算出来)
Io密集型任务:使用多线程、多协程(实时性不高,占用资源不多而且还能完成任务)

一个程序,为了完成多任务,可以由多进程来实现,假如说进程数是10,那么此时有10个一起运行
一个进程中可以开线程,假如每个进程程有10个线程,那么一共是10x10===>100个任务一起运行
一个线程中可以开协程,假如每个线程中有10个协程,那么一共是10x10x10->1000个任务一起运行

通过协程能够实现多任务,但是它的这种方案一定是假的多任务,又因为只要运行时切换任务足够快,用户看不出区别,所以表面上这就是多任务

  • yield实现协程,但要说清楚先后执行顺序

  • greenlet实现协程,需要人工切换

sudo pip3 install greenlet

  • gevent实现协程,自动切换(自动切换的条件是遇到延迟) 可以实现高并发

pip3 install gevent

改进:monkey.patch_all() 使 time.sleep()效果等同于gevent.sleep()

joinall{}

GIL(全局解释器锁)

为什么会出现多线程执行时是假的多任务呢?

这其实就是本文最开始提到的,是因为Python的历史原因导致的,用C语言实现的Python解释器有这个问题,从而Python默认的C语言解释器执行Python多任务代码时,如果是进程实现的是真的多任务,而线程实际上假的多任务,这个问题就称为GIL

为什么有了GIL,还需要互斥锁?

答:都是锁,但功能不同。一个线程执行时,GIL防止另外一个线程抢占CPU,即防止多个线程使用cpu的问题,它保证了单线程执行一段任务。互斥锁避免全局变量的抢占访问,保证了一段逻辑完整执行。

进程、线程、协程对比

多进程不见的越多越好,多线程也不是越多越好,那么应该谁来觉得系统到底是多少进程多少线程呢?

答案:压力测试,得出一个动态平衡点

Python全栈编程相关推荐

  1. 案例驱动python编程入门-郑州高薪python全栈工程师

    阶段一.Python 全栈工程师之必知必会 - 前端开发技术 课程一.入门必备 - 新手学 HTML5+CSS3 1.HTML基本标签 2.W3C标准及XHTML1.0基本规范 3.表格.表单及框架 ...

  2. python视频网站项目_价值2400元的python全栈开发系列Flask Python Web 网站编程视频

    2 e/ b4 F1 c' H$ D! X 价值2400元的python全栈开发系列Flask Python Web 网站编程视频-优品课堂' z3 _1 Y7 ]6 j4 z # p# r# g* ...

  3. python web全栈开发_价值2400元的python全栈开发系列Flask Python Web 网站编程视频教程...

    课程目录 01-计算机基础常识.mp4 02-Python语言概览.安装与运行.mp4 03-Python 变量.数据类型及存储.mp4 04-Python 常用数据类型概览.mp4 05-数值与字符 ...

  4. python 全栈开发,Day32(知识回顾,网络编程基础)

    python 全栈开发,Day32(知识回顾,网络编程基础) 一.知识回顾 正则模块 正则表达式 元字符 :. 匹配除了回车以外的所有字符\w 数字字母下划线\d 数字\n \s \t 回车 空格 和 ...

  5. 〖Python全栈白宝书-免费版①〗- Python编程环境搭建-Python3解释器安装

    该篇文章为 Python全栈白宝书-免费版试读部分),觉得还可以的小伙伴欢迎订阅下方推荐的 Python全栈白宝书. 推荐: Python全栈白宝书专栏,免费阶段订阅数量4300+,购买任意白宝书体系 ...

  6. 美国AI博士指出:60天掌握Python全栈需要...

    我见过市面上很多的 Python 讲解教程和书籍,他们大都这样讲 Python 的: 先从 Python 的发展历史开始,介绍 Python 的基本语法规则,Python 的 list, dict, ...

  7. python全栈工程师薪水_不止 20K,Python 工程师薪资再飙升(内附转型指南)

    原标题:不止 20K,Python 工程师薪资再飙升(内附转型指南) Python 诞生之初就被誉为最容易上手的编程语言.进入火热的 AI 人工智能时代后,它也逐渐取代 Java,成为编程界的头牌语言 ...

  8. termux pythonlxml安装_将安卓手机打造成你的python全栈开发利器

    原标题:将安卓手机打造成你的python全栈开发利器 超神利器 相信多数安卓用户都使用过Qpython这款移动端的Python编辑器吧?之前我也研究过一阵子这个工具,但因为一次简单的爬虫让我对它失望之 ...

  9. python全栈开发优势_Python全栈开发多少钱?学Python价格贵吗?

    Python全栈开发培训多少钱?学习Python是大家进入编程世界的理想之选,而且Python也是一门非常受欢迎的编程,可以从事的领域有很多. 从目前市场上的行情来说,一般情况下Python培训的费用 ...

最新文章

  1. Linux循环链表删除节点,删除循环单链表开头元素
  2. html文件里的scr是什么,HTML中关于url、scr、href的区别
  3. CacheManager - 用 C# 编写的 .NET 的开源缓存抽象层
  4. 洛谷-DFS-1019-单词接龙-个人AC题解和公共AC题解笔记
  5. Win11系统如何解除网络限制
  6. 给 UITextField 添加左侧指示图片(类似微信登录框)
  7. I2c驱动i2c_master_send()和i2c_master_recv()用法
  8. 风控中英文术语手册(银行_消费金融信贷业务)_v4
  9. Nagios社区真有意思
  10. 思科(Cisco)路由器常用命令总结
  11. 视频联网云平台EasyCVR集成海康EHome协议系列——Ehome协议调用流程介绍
  12. knockoutjs与ajax,MVVM架构~knockoutjs系列之为Ajax传递Ko数组对象
  13. hget hmget redis api使用
  14. 人生顿悟之博观而约取,厚积而薄发
  15. 真正拖垮你的,其实是沉没成本
  16. 新GRE阅读关于Emily Dickinson 的作品
  17. hx711c语言程序,51单片机HX711传感器电子秤设计(原理图、程序源码、BOM等)
  18. CSDN访猿团CEO:猿团创业孵化,为何要收26800+10%股权
  19. StarlingX分布式云部署(抢鲜)
  20. 与二分来一场美丽的邂逅

热门文章

  1. 解决linux系统网络时常断开的问题
  2. 8千多英语语法练习题ACCESS\EXCEL数据库
  3. 给HTML页面设置背景音乐
  4. MySQLSyntaxErrorException异常处理办法
  5. 做自媒体18个月,倒欠38万,一个自媒体创作者的自述
  6. discuz gbk php在utf8,Discuz!3.4论坛从GBK转换成UTF8的成功经验
  7. 【JZOJ 5405】【NOIP2017提高A组模拟10.10】Permutation
  8. 异方差性以及加权最小二乘优化
  9. C语言函数指针与NULL
  10. 关于js中0==‘‘判断为True