数学运算
得商除法:
>>> 3 // 2
1
次方:
>>> 10 ** 3
1000
Py解释器的内置函数
print()
type();//用于查看数据对象的类型
字符串str
字符串的定义
双引、单三引、双三引
区别:三引支持跨行;双引不支持,需要在字符串中使用
\n
转义符表示换行
字符串内容里本身就有引号
- 字符串内容里有单引号,那定义字符串时就用双引号
- 字符串内容里有双引号,那定义字符串时就用单引号
- 字符串内容里既有单引又有双引,那定义字符串时就用三引号
字符串切片
hello = '刘总你好啊'
print(hello[2:4])
# 或
print(hello[-3:-1])
特:开头切 / 切到结尾 可省或一个切片索引 eg.hello[ : 2] / hello[2 : ]
获取字符串长度
len()
函数
定义函数关键字:def
def interview():
print("把求职者带到3号会议室")
print("请求职者 完成答卷")
print("让测试经理来面试 求职者")
print("让技术总监面试 求职者")
interview()
函数返回值
当函数需要返回结果时,就需要使用return关键字
def squarep(num1,num2):
return num1**2 + num2**2
ret = squarep(1,2)
print(ret)
注意:解释器执行代码的时候,一旦执行了函数中的 return 语句,就会立即从函数中返回到调用的地方,该函数下面还有代码,也不会再执行了
函数缺省值参数
def overScoreStudents(studentScoreList, score=60):
调用时如果score传60,可写为overScoreStudents(studentScoreList)
如果调用时传入了参数,eg. overScoreStudents(ssList, 70) ;那么解释器就会将 70
传给参数score
注意:定义函数时,一旦有一个参数有缺省值,那它后面所有的参数必须都有缺省值
列表list
用方括号来表示一个列表;每个格可存储 任何类型 的数据对象 ;可以是整数、小数、字符串、函数、也可以存储另一个列表对象
可索引可切片
a = [1, 2, 3.14, 'hello', [7,8,9] ]
a[-1][:2] 取出的是 [7,8]
改变列表内容
列表元素可以填写变量,也可以直接填写表达式,解释器会自动计算结果,放入列表中
切片赋值
list1 = [0, 1, 2, 3, 4, 5]
list1[1:4] = ['a','b','c','e','f','g','h']
> [0, 'a', 'b', 'c', 'e', 'f', 'g', 'h', 4, 5]
合并俩列表
+
判断元素是否在列表中
关键字in ;返回值为true/false
list = [1,2,3,4,'hello']
print('hello' in list)
支持多个变量同时赋值
x,y=[1,2]
print(x,y)
元组tuple
用圆括号来表示一个元组;同样每个格可存储 任何类型 的数据对象 ;可以是整数、小数、字符串、函数、另一个列表对象、另一个元组对象;不同的是 元组的内容是不可改变的
注意:若元组中只有一个元素,必须在它后面加逗号 eg. a= (1,) 否则写成 a= (1) ,a就是数字1了(会把()当成用于提高优先级的东西),而非包含数字1的元组
定义元组时也可去掉() eg. a = 1,
可索引可切片
判断元素是否在元组中
关键字in;返回值为true/false
tuple = (1,2,3,4,'hello')
print('hello' in tuple)
支持多个变量同时赋值
x,y = (1,2)
print(x,y)
判断语句
布尔表达式
条件组合
且 :关键字and ;表达式1 and 表达式2
或 :关键字or ;表达式1 or 表达式2
注意:当 and 和 or 一起用时,会先计算and部分,得到的结果再和or进行计算
非:关键字not;not 表达式1
注意:not、 and 和 or 一起用时, 会先计算 not , 再计算 and 部分, 最后再计算 or
判断语句
▲ :else if 要简写为 elif
情景:当函数执行某个功能前,要做一些条件检查, 如果这些条件任何一个不满足, 后续的操作就不要做了—— 可以结合return语句
对象的方法
字符串对象的方法
count();//返回字符串对象中包含了多少个参数指定的字符串
>>>'我们今天不去上学,我们去踢足球'.count('我们')
2
find();//在字符串中查找参数子字符串,并返回该参数字符串在其中第一个出现的位置索引
>>>str1='我们今天不去上学,我们去踢足球'
>>>pos1=str1.find('我们')
0
第二个参数,可用来指定查找字符串范围(从索引为几的位置开始往后查找)
>>>str1='我们今天不去上学,我们去踢足球'
>>>pos1=str1.find('我们',5)
9
split();//用于字符串的截取
以参数字符串为分隔符,将字符串切割为多个字符串,作为元素存入一个列表,最后返回这个列表
>>> str1= '小张:79 | 小李:88 | 小赵:83'
>>>pos1=str1.split('|')
['小张:79 ',' 小李:88 ',' 小赵:83']
join(); //用于字符串的连接
将列表中的字符串元素 以某字符串为连接符,连接成一个字符串
>>> '|'.join(['小张:79 ',' 小李:88 ',' 小赵:83'])
小张:79 | 小李:88 | 小赵:83
strip() / lstrip() /rstrip() //删字符串空格
- strip();//只删字符串前后空格,不删中间
- lstrip();//只删字符串左侧的空格
- rstrip();//只删字符串右侧空格
replace();//替换
用于替换字符串中所有指定的 子字符串 为另一个字符串
⭐删字符串所有空格该如何实现
>>>' 小 李:88 '.replace(' ','') //将空格替换为空字符串
'小李:88'
startswith() / endswith();//检查字符串是否以参数指定的字符串开头或结尾
返回值:true / false
列表对象的方法
append();//在列表后面添加一个元素
insert();//在指定位置插入一个元素
pop();//取出并删除一个元素,返回值是取出的元素;参数是取出的元素的索引
remove();//删除一个元素(注意只能删一个),参数是要删的元素的值
注意:从第1个元素开始,寻找 和参数对象 相等的元素,如果找到了,就删除;找到后,不会继续往后寻找其它相等的元素。
reverse() ;//列表对象的一个方法,可将列表元素倒过来
index();//返回参数对象在列表中的索引
sort();//对列表进行排序;但返回值是None
格式化字符串
先用占位符占位,将要填的变量传进去
print格式化
格式:在字符串模板中使用占位符先占位,后面再提供一个元组,里面依次存放需要填入到占位符位置的数据
print('税前薪资:%s,缴税:%s,税后薪资:%s' %(salary,tax,aftertax))
%s 是一种格式化符号, Python 解释器 看到%s , 就会调用内置函数 str(),并将对应的格式化对象作为参数传入 , 返回的结果字符串填入对应占位符
注意:如果占位符是1个,后面如果用元组注意加,;当然因为只有一个格式化对象,也可以直接使用,不放入元组中
占位符
除了%s还有%d、%f ;格式化对象为整数或浮点数的情况
- 指定宽度:
1.正常是不足用空格补齐
'税前薪资:%10d 元' % 100000
=>税前薪资: 100000 元
2.(10)不足10个字符补0(0) => '税前薪资:%010d 元' % 100000
=> 税前薪资:0000100000 元
3.小数补0补后面
'税前薪资:%010f 元' % 1000.4522
=>税前薪资:1000.452200 元
4.保留小数点后两位
'税前薪资:%010.2f 元' % 1000.4522
=>税前薪资:0001000.45 元
- 对齐:
左对齐,加-
'税前薪资:%-10s 元' % 100000
=>税前薪资:100000 元
f-string格式化
前提:必须是Python3.6之后的版本
格式:在字符串模板前加f,然后占位符使用{},里面放的是对应的数据对象
print(f '税前薪资是:{salary}元,缴税:{tax}元,税后薪资:{aftertax}元')
- 指定宽度:
在括号里的变量后面加:宽度值
print(f '{salary:10}');//指定宽度为10,不足补空格
指定小数点后保留几位
print(f '{salary:10.2f}');
不足补0
print(f '{salary:010.2f}');
这里采用的是右对齐,补0补到前面;如果是左对齐,补0就补到后面
- 对齐:
左对齐,<
print(f '{salary:<10}');
循环
while循环
for循环
通常用于从一个sequence类型(字符串、列表、元组)中依次取出每一个元素进行操作
studentAges = ['小王:17', '小赵:16', '小李:17', '小孙:16', '小徐:18']
for student in studentAges:
print(student)
循环指定次数:
使用range(次数)函数; 当参数为两个的时候就指明了起止范围,注意是左闭右开;当参数为三个的时候,最后一个指明的是步长
终止循环
break
和return的区别:
return只能用在函数里面;在函数里面return和break的区别:break终止的是循环,当循环后面有语句,它依然会执行;return是直接返回到调用这个函数的地方
只结束当前这轮循环
continue
列表推导式
场景:把一个列表里的每一个元素,经过相同的处理,生成另一个列表
正常情况下我们一般这样写
list1 = [1,2,3,4,5,6]
list2 = []
for num in list1:
list2.append(num*num)
列表推导式简化
list1 = [1,2,3,4,5,6]
list2 = [num**2 for num in list1]
for num in list1:从list1中取出每一个元素
num**2(for前面的部分):对取出的元素进行的处理操作
循环的嵌套
字符编码
字符集
以数字表示字符的映射关系
ASCII
unicode字符集:包括了现今世界上的常用文字符号 和 其对应的数字表示
字符编码
用字节表示 字符对应的数字
unicode最常见的字符编码规范:UTF8、UTF16
UTF16:任何字符对应的数字都用两个字节保存
8位一字节(1111 1111 => \xEE );\x 说明用16进制表示一个字节(2转16,四个一组)
字符串编码
就是将字符串变为字节串
Py3里的字符串对象都是unicode字符串,在对它进行存储和传输时, 通常使用字符串的encode方法,参数指定编码方式
字节串解码
就是将字节串变为字符串
Py的解码都是解码成unicode字符串对象,要解码这个字节串,首先要知道这个字节串用什么字符编码方式编码的
通常使用字节串对象的decode方法进行解码,参数指定编码方式
文件读写
Py内置函数open
open(
file,
mode='r',
buffering=-1,
encoding=None,
errors=None,
newline=None,
closefd=True,
opener=None
)
用于打开文件
open参数
file:指定要打开的文件路径
mode:文件打开的模式
常见打开模式:
mode缺省值是r
r —— 只读
w —— 只写(注意是清空文件重头写;文件不存在的时候会新建文件)
a —— 追加
encoding:指定读写文本文件时,使用的字符编解码方式
后面调write写入(字符串写到文件),open指定编码(编码为字节串)
后面调read读取(从文件中读取内容),open指定解码(解码为字符串)
文件写入
f = open('tmp.txt','w',encoding='utf8')
# write方法会将字符串编码为utf8字节串写入文件
f.write('白月黑羽:祝大家好运气')
f.close()
文件读取
场景:从文本文件中,读取内容到字符串对象,并截取其中的名字部分
f = open('tmp.txt','r',encoding='gbk')
# read 方法会在读取文件中的原始字节串后, 根据上面指定的gbk解码为字符串对象返回
content = f.read()
f.close()
name = content.split(':')[0]
print(name)
注意:read()有参数size ;可以指定读取多少个字符,不传就读全部
逐行读取readlines()
返回一个列表,列表中的每个元素依次对应文本文件中每行内容
f = open('tmp.txt')
linelist = f.readlines()
f.close()
for line in linelist:
print(line)
这里 linelist = ['哈哈哈哈哈哈\n','邓超\n','陈赫\n','鹿晗\n'];而print函数会自动在结尾加\n,这就导致最后有空行
要想去掉空行,可改写为:
f = open('tmp.txt')
content = f.read() # 读取全部文件内容
f.close()
# 将文件内容字符串 按换行符 切割 到列表中,每个元素依次对应一行
linelist = content.splitlines()
for line in linelist:
print(line)
二进制方式读写文件
二进制读方式打开文件
mode参数指定为rb
# mode参数指定为rb 就是用二进制读的方式打开文件
f = open('tmp.txt','rb')
# 这里f.read()底层就不需要解码了,content得到的是字节串对象
content = f.read()
f.close()
# 内容为 b'\xe7\x99\xbd\xe6\x9c\x88\xe9\xbb\x91\xe7\xbe\xbd'
print(content)
要想解码自己调decode函数
print(content.decode('utf8'))
# 该对象的长度是字节串里面的字节个数,就是12,每3个字节对应一个汉字的utf8编码 白月黑羽
# 注意这个len参数是字节串返回的就是字节串的个数,参数是字符串返回的就是字符串个数
print(len(content))
二进制方式写数据到文件
注意:传给write的参数只能是字节串对象
f = open('tmp.txt','wb')
content = '白月黑羽祝大家好运连连'
f.write(content.encode('utf8'))
f.close()
也可以直接字节串写入
content = b'\xe7\x99\xbd\xe6\x9c\x88\xe9\xbb\x91\xe7\xbe\xbd'
f.write(content)
应用: 文件拷贝
必须用二进制方式打开;如果用文本文件方式打开,read()底层就会自动给它解码,又没指定解码方式,就会默认gbk编码方式解码,这个文件又不一定全是汉字,就会报错。
def fileCopy(srcPath,destPath):
srcF = open(srcPath,'rb')
content = srcF.read()
srcF.close()
destF = open(destPath,'wb')
destF.write(content)
destF.close()
fileCopy('1.png','1copy.png')
模块和库
Py中,一个代码文件代码,也就是一个.py文件,我们称它为一个模块
模块间的调用
法一:关键字import
eg. import save
这样save就成了模块aa(aa.py)里的一个变量,这个变量指向的是一个模块(文件)对象,这样就可以通过save.savetofile访问到save模块(save.py)里的函数
法二:关键字from import
可导入其它模块里的标识符(包括变量名和函数名等)
eg. from save import savetofile
导入多个其它模块
可分开导入,也可一起导入(模块间用,隔开)
从一个模块里导入多个标识符(可以是变量名也可以是函数名)
eg. from aa import func1,var1,func2,var2
导入一个模块里所有标识符
eg. from aa import *
起别名 as
当我们从两个模块导入函数,恰好这两个函数同名,这时我们可以给其中一个起个别名,避免冲突
from save import savetofile
from save2 import savetofile as savetofile2
将模块放入包中
Py把放模块文件的目录,称为包
包目录里需要有一个名为__init__.py的初始化文件,有它,Py才认为这是一个Py包,通常这个初始化文件里不需要什么代码,一个空文件就可。 注意:调用时要加上所有包的路径前缀
import stock.food.beef
stock.food.beef.stockleft()
也可以
from stock.food.beef import stockleft
stockleft()
库
模块文件里的函数,实现了通用的功能,经常被其它模块所调用,我们把这些被调用的模块文件称为库
字典dict
存放的是键值对数据
字典的定义
字典对象定义用{},其中的每个元素间用,隔开;每个元素都是一个键值对,键和值间用:隔开
members = {
'account1' : 13 ,
'account2' : 12
}
value可以是任何类型的 对象
字典对象的特点:根据键查找值 很方便
members = {
'account1' : 13 ,
'account2' : 12
}
print(members['account1'])
字典的使用
字典对象中添加/修改元素
key存在就是修改;key不存在就是添加
members['account1'] = 13
key value
删除元素
members = {
'account1' : 13 ,
'account2' : 12
}
val = members.pop('account1')
print(val)
这里pop方法还会返回参数key对应的value对象
检查元素
通过in关键字,检查字典对象中是否有我们要找的元素
eg. 'account1' in members
遍历字典
通过使用字典对象的items方法,items方法返回的是一个类似列表一样的对象,其中每个元素是 键值组成的元组
members = {
'account1' : 13 ,
'account2' : 12 ,
'account3' : 15 ,
}
for account,level in members.items():
# members.items() = [('account1', 13), ('account2', 12), ('account3', 15)]
print (f'account:{account}, level:{level}')
keys() / values()
字典对象的keys方法,返回的是 将字典所有的键存入的一个类似列表的对象
members = {
'account1' : 13 ,
'account2' : 12 ,
'account3' : 15 ,
}
members.keys()
> ['account1', 'account2', 'account3']
字典对象的values方法,返回的是 将字典所有的值存入的一个类似列表的对象
members = {
'account1' : 13 ,
'account2' : 12 ,
'account3' : 15 ,
}
members.values()
> [13, 12, 15]
清空字典
字典对象的clear方法
members.clear() 区别于 members = {}
members.clear()是把原来对象的内容清空了;members = {} 只是指向了新的对象,原来对象并没有改变
两字典合并
调用字典对象的update方法
members = {
'account1' : 13 ,
'account2' : 12 ,
'account3' : 15 ,
}
another = {
'account4' : 13 ,
'account5' : 12 ,
}
members.update(another)
print(members)
获取字典元素的个数
使用Py内置函数len
members = {
'account1' : 13 ,
'account2' : 12 ,
'account3' : 15 ,
}
print(len(members)) # 结果为3,表示字典中有3个元素
自定义类
定义类关键字class;后加类名
类属性(静态属性)
若想获得类属性值采用:类名.属性名
静态方法
前加@staticmethod装饰器,指明下面的函数是类里的静态方法
调用:类名.静态方法
类的实例
一个个具体的对象 称为该类型的实例
要想产生一个类的实例对象,只需要 类名后面加();像这样 car1 = BenzCar()
实例对象具有类的一切属性和方法
实例属性
通常在 类的初始化方法(类在实例化对象时,解释器会执行的方法;相当于构造方法) __init__里定义
car1 = BenzCar() ;
解释器在执行这段代码时,会先在内存中创建一个该类的实例对象,然后会查看这个类有没有__init__初始化方法,有的话就调用,调用时就将实例对象传给__init__的第一个参数self
class BenzCar:
brand = '奔驰'
country = '德国'
@staticmethod
def pressHorn():
print('嘟嘟~~~~~~')
def __init__(self,color,engineSN):
self.color = color # 颜色
self.engineSN = engineSN # 发动机编号
car1 = BenzCar('白色','24503425527866')
print (car1.color)
注意:
1.BenzCar.engineSN 不能通过类名访问实例属性(人类的身份证号码???)
2.
def __init__(self,color,engineSN): self.color = color # 颜色 self.engineSN = engineSN # 发动机编号
属于实例方法
3.静态方法不能访问实例属性
4.实例属性名和静态属性名重复怎么区分
通过类实例访问的该属性,访问的是实例属性;通过类名访问的该属性,访问的是类属性
class Car: brand = '奔驰' name = 'Car' def __init__(self): # 可以通过实例访问到类属性 print(self.brand) # 定义实例属性和类属性重名 self.name = 'benz car' c1 = Car() print(f'通过实例名访问name:{c1.name}') print(f'通过类名 访问name:{Car.name}')
实例方法
class BenzCar:
brand = '奔驰'
country = '德国'
@staticmethod
def pressHorn():
print('嘟嘟~~~~~~')
def __init__(self,color,engineSN):
self.color = color # 颜色
self.engineSN = engineSN # 发动机编号
def changeColor(self,newColor):
self.color = newColor
car1 = BenzCar('白色','24503425527866')
car1.changeColor('黑色')
print (car1.color)
调用 changeColor
方法的时候,只需要传入参数 newColor
对应新的颜色即可;不需要我们传入self参数,self 参数是实例对象本身,解释器会自动帮我们传入
类间的关系
继承
我们将被继承类称为父类/基类;继承类称为子类/派生类
继承格式
class 子类(父类):
...
子类会自动拥有父类的一切属性和方法;
子类在继承父类的一切特性的基础上,还可以有自己的属性和方法;
子类可以重新定义父类的属性和方法 ;
调用父类的初始化方法
子类的初始化方法里一定要调用父类的初始化方法,否则解释器自己不会执行父类的初始化方法
BenzCar.__init__(self,color,engineSN)
class Benz2018(BenzCar):
price = 880000
model = 'Benz2018'
def __init__(self,color,engineSN,weight):
# 先调用父类的初始化方法
BenzCar.__init__(self,color,engineSN)
self.weight = weight # 车的重量
self.oilweight = 0 # 油的重量
# 加油
def fillOil(self, oilAdded):
self.oilweight += oilAdded
self.weight += oilAdded
还可使用super()
super().__init__(color, engineSN)
这个写法,方法参数不需要加self参数
异常
异常对象
Py标准库中很多异常类都继承自标准库里面的 Exception 类,代表各种不同类型的错误。
捕获异常
在编码时,就预料到了某些代码运行时可能出现某些异常,就可以使用 try...except...
这样的方法来捕获和处理异常。
while True:
try:
miles = input('请输入英里数:')
km = int(miles) * 1.609344
print(f'等于{km}公里')
except ValueError:
print('你输入了非数字字符')
解释器在执行try下面的缩进代码时,如果出现异常,就会停止执行后续代码;检查这个错误类型和except后声明的类型匹不匹配的上,匹配的上,就说明早就预料到了,执行except下的代码,没匹配上就终止程序。
匹配所有异常
try:
100/0
except Exception as e:
# 把抛出的异常对象 赋值 给变量e
print('未知异常:', e)
因为所有异常都是Exception的子类,所以Exception能匹配所有类型的异常
也可以这样写
try:
100/0
except:
print('未知异常:')
如果我们想知道异常产生在第几行,以及异常信息,可以导入traceback模块
import traceback
try:
100/0
except :
print(traceback.format_exc())
Traceback (most recent call last):
File "xxxx/xxx.py", line 4, in <module>
100/0
ZeroDivisionError: division by zero
自定义异常
抛出自定义异常关键字:raise
# 异常对象,代表电话号码有非法字符
class InvalidCharError(Exception):
pass
# 异常对象,代表电话号码非中国号码
class NotChinaTelError(Exception):
pass
def register():
tel = input('请注册您的电话号码:')
# 如果有非数字字符
if not tel.isdigit():
raise InvalidCharError()
# 如果不是以86开头,则不是中国号码
if not tel.startswith('86'):
raise NotChinaTelError()
return tel
try:
ret = register()
# 还是需要有一个变量接收的,因为register()返回值tel
except InvalidCharError:
print('电话号码中有错误的字符')
except NotChinaTelError:
print('非中国手机号码')
多线程
进程和线程
正在运行着的程序就是一个进程;每个进程中至少包含一个线程
之前的Py程序虽然没有创建线程,但当Py解释器程序运行起来成为一个进程,OS就自动的创建一个线程,通常称为主线程,在这个主线程里面执行代码指令
线程是操作系统创建的,每个线程对应一个代码执行的数据结构,保存了代码执行过程中的重要的状态信息;没有线程,操作系统没法管理和维护 代码运行的状态信息;所以没有创建线程之前,操作系统是不会执行我们的代码的。
如何创建线程
Threading模块
创建并启动一个单线程
print('主线程执行代码')
# 从 threading 库中导入Thread类
from threading import Thread
from time import sleep
# 定义一个函数,作为新线程执行的入口函数
def threadFunc(arg1,arg2):
print('子线程 开始')
print(f'线程函数参数是:{arg1}, {arg2}')
sleep(5)
print('子线程 结束')
# 创建 Thread 类的实例对象
thread = Thread(
# target 参数 指定 新线程要执行的函数
# 注意,这里指定的函数对象只能写一个名字,不能后面加括号,
# 如果加括号就是直接在当前线程调用执行,而不是在新线程中执行了
target=threadFunc,
# 如果 新线程函数需要参数,在 args里面填入参数
# 注意参数是元组, 如果只有一个参数,后面要有逗号,像这样 args=('参数1',)
args=('参数1', '参数2')
)
# 执行start 方法,就会创建新线程,
# 并且新线程会去执行入口函数里面的代码。
# 这时候 这个进程 有两个线程了。
thread.start()
print('主线程结束')
执行结果
创建并启动一个多线程
循环创建多个线程,并循环启动线程执行
import threading
from datetime import datetime
def thread_func(): # 线程函数
print('我是一个线程函数', datetime.now())
def many_thread():
threads = []
for _ in range(10): # 循环创建10个线程
t = threading.Thread(target=thread_func)
threads.append(t)
for t in threads: # 循环启动10个线程
t.start()
if __name__ == '__main__':
many_thread()
_:在Py中称为丢弃变量;当你不用这个变量或者不想给这个变量起名字的时候,就可以定义这个变量的名字为_
等待线程结束
使用Thread对象的 join
方法
哪个线程对象调用的,就等待哪个线程结束
print('主线程执行代码')
# 从 threading 库中导入Thread类
from threading import Thread
from time import sleep
# 定义一个函数,作为新线程执行的入口函数
def threadFunc(arg1,arg2):
print('子线程 开始')
print(f'线程函数参数是:{arg1}, {arg2}')
sleep(5)
print('子线程 结束')
# 创建 Thread 类的实例对象
thread = Thread(
# target 参数 指定 新线程要执行的函数
target=threadFunc,
# 如果 新线程函数需要参数,在 args里面填入参数
args=('参数1', '参数2')
)
# 执行start 方法,就会创建新线程,
# 并且新线程会去执行入口函数里面的代码。
# 这时候 这个进程 有两个线程了。
thread.start()
# 主线程的代码执行 子线程对象的join方法,就会等待子线程结束,才继续执行下面的代码
thread.join()
print('主线程结束')
多线程共享数据
注意一点:多线程的执行顺序 "线程间的执行是无序的" ;当前执行哪个线程是由CPU决定的(线程是CPU调度资源的基本单位),所以当前CPU调度哪个线程,哪个线程就执行,未被调度的线程是无法执行的
下面我们就 多线程同时访问 bank对象(共享资源)进行分析
首先先来看单线程(串行执行)
from time import sleep
bank = {
'byhy' : 0
}
# 定义一个函数,作为新线程执行的入口函数
def deposit(theadidx,amount):
balance = bank['byhy']
# 执行一些任务,耗费了0.1秒
sleep(0.1)
bank['byhy'] = balance + amount
for idx in range(10):
deposit (idx,1)
print(f'最后我们的账号余额为 {bank["byhy"]}')
> 最后我们的账号余额为 10
这里注意bank['byhy']这个是全局变量嗷
再来看多线程(并发执行)
from threading import Thread
from time import sleep
bank = {
'byhy' : 0
}
# 定义一个函数,作为新线程执行的入口函数
def deposit(theadidx,amount):
balance = bank['byhy']
# 执行一些任务,耗费了0.1秒
sleep(0.1)
bank['byhy'] = balance + amount
print(f'子线程 {theadidx} 结束')
theadlist = []
for idx in range(10):
thread = Thread(target = deposit,
args = (idx,1)
)
thread.start()
# 把线程对象都存储到 threadlist中
theadlist.append(thread)
for thread in theadlist:
thread.join()
print('主线程结束')
print(f'最后我们的账号余额为 {bank["byhy"]}')
1.为什么会出现这种状况
首先 sleep(0.1) 的存在使得所有子线程的balance都为0
做个假设:子9执行到sleep(0.1),子9进入阻塞状态;当等待事件发生完成,线程就会由阻塞状态转为就绪状态;当CPU空闲时,它就会从就绪队列中选一个线程运行,至于选哪个就看CPU调度算法选哪个了
2.线程间的执行是无序的
thread = Thread(...) 线程对象被创建,进入新建状态
thread.start()这里才启动线程,进入就绪状态,等着被CPU调度
注意一个问题:去掉sleep你看到子线程打印出来的是顺序执行的,但其实不是。谁先执行看的是谁先被CPU调度。
但这不是研究的重点,我们这里着重看的是共享资源bank['byhy']—并发执行最后值为1,加了锁之后值为10
3.“有可能同时操作bank对象,可能出现一个线程覆盖另一个线程的结果问题”这什么意思
(没有sleep情况)因为多线程并发执行,存在子2刚执行完balance赋值语句,立马切到子5执行,还没等到bank['byhy']在子2中改变就把它赋给了当前子5线程的balance,也就是这里说的存在的覆盖问题。
解决多线程同时访问共享数据 —— 给共享资源加个锁
from threading import Thread,Lock
from time import sleep
bank = {
'byhy' : 0
}
bankLock = Lock()
# 定义一个函数,作为新线程执行的入口函数
def deposit(theadidx,amount):
# 操作共享数据前,申请获取锁
bankLock.acquire()
balance = bank['byhy']
# 执行一些任务,耗费了0.1秒
sleep(0.1)
bank['byhy'] = balance + amount
print(f'子线程 {theadidx} 结束')
# 操作完共享数据后,申请释放锁
bankLock.release()
theadlist = []
for idx in range(10):
thread = Thread(target = deposit,
args = (idx,1)
)
thread.start()
# 把线程对象都存储到 threadlist中
theadlist.append(thread)
for thread in theadlist:
thread.join()
print('主线程结束')
print(f'最后我们的账号余额为 {bank["byhy"]}')
做个假设:子1在访问共享数据时加锁了,那当它执行到sleep( 0.1)的时候正常应该进入阻塞态,放弃CPU使用权;当子2想访问共享数据时访问不了,要等待子1锁的释放,子1等待事件完成,阻塞态转成就绪态,上处理机运行继续执行下面代码,然后释放锁,子2 再去获取锁....
装饰器
就是给原来函数加些功能;参数为被装饰的函数
import time
# 定义一个装饰器函数
def sayLocal(func):
def wrapper():
curTime = func()
return f'当地时间: {curTime}'
return wrapper
def getXXXTime():
return time.strftime('%Y_%m_%d %H:%M:%S',time.localtime())
# 装饰 getXXXTime
getXXXTime = sayLocal(getXXXTime)
print (getXXXTime())
可简写为
import time
# 定义一个装饰器函数
def sayLocal(func):
def wrapper():
curTime = func()
return f'当地时间: {curTime}'
return wrapper
@sayLocal
def getXXXTime():
return time.strftime('%Y_%m_%d %H:%M:%S',time.localtime())
print (getXXXTime())
JSON
序列化和反序列化
为什么要序列化
我们通过任何协议传输信息时传的都是字节串,这时发送方需要把这个对象转换为字节序列,才能在网络上传送,接收方则需要把字节序列再恢复为对象。
对于JSON格式的字符串,不能直接使用;但对于Py数据对象,我们可以直接通过对象的key去访问它对应的value值,操作方便
定义
序列化:把 程序的各种类型数据对象 变成 表示该数据对象的字节串(可以存储或传输的格式) 的过程
反序列化:把 字节串转化为 程序中的数据对象 的过程
json 序列化/反序列化模块
主要应用于传输数据 , 序列化成字符串
区别于pickle序列化/反序列化模块 —— 主要应用于存储数据 , 序列化成二进制字节流
JSON库的内置函数
json.dumps()
作用:将Py中的数据对象序列化为json格式的字符串
一些主要参数:
obj:要转换成json的对象
ensure_ascii:输出是否为ASCII
import json
historyTransactions = [
{
'time' : '20170101070311', # 交易时间
'amount' : '3088', # 交易金额
'productid' : '45454455555', # 货号
'productname' : 'iphone7' # 货名
},
{
'time' : '20170101050311', # 交易时间
'amount' : '18', # 交易金额
'productid' : '453455772955', # 货号
'productname' : '奥妙洗衣液' # 货名
}
]
jsonstr = json.dumps(historyTransactions)
print(jsonstr)
ensure_ascii默认值为true;这里为什么奥妙洗衣液 会被转换成\u5965\u5999\u6d17\u8863\u6db2
确保输出的是ASCII,但对于非ASCII字符则会以\uxxxx的形式显示;要想中文显示,将ensure_ascii值设为False即可,表示禁用ASCII编码,使用unicode编码
json.loads()
作用:将json格式的字符串反序列化为Py中的数据对象
import json
jsonstr = '[{"time": "20170101070311", "amount": "3088", "productid": "45454455555", "productname": "iphone7"}, {"time": "20170101050311", "amount": "18", "productid": "453455772955", "productname": "\u5965\u5999\u6d17\u8863\u6db2"}]'
translist = json.loads(jsonstr)
print(translist)