内置方法(回顾)
__len__ len(obj)的结果依赖于obj.__len__()的结果,计算对象的长度
__hash__ hash(obj)的结果依赖于obj.__hash__()的结果,计算对象的hash值
__eq__ obj1 == obj2的结果依赖于obj.__eq__()的结果,用来判断值相等
__str__ str(obj) print(obj) '%s'%obj 的结果依赖于__str__,用来做输出,显示
__repr__ repr(obj) '%r'%obj 的结果依赖于__repr__,还可以做str的备胎
__format__ format()的结果依赖于__format__的结果,是对象格式化
__call__ obj()相当于调用__call__,实现了__call__的对象是callable的
__new__ 构造方法,在执行__init__之前执行,负责创建一个对象,在单例模式中有具体的应用
__del__ 析构方法,在对象删除的时候,删除这个对象之前执行,主要用来关闭在对象中打开的系统资源
单例模式:只有一个对象,只开了一个内存空间
python中的单例模式,是依赖__new__来完成的
class A:
def __getitem__(self, item):
print(item)
a = A()
a['bbb'] # 对象['值']触发了__getitem__
#__getitem__ 对象[]的形式对对象进行增删改查
# __setitem__
# __delitem__
# __delattr__ del obj.attr 用来自定义删除一个属性的方法
面试题
写一个类,定义100个对象
拥有三个属性name age sex
如果两个对象的name 和 sex 完全相同,就认为这是同一个对象,忽略age属性
做这100个对象的去重
需用用到的知识点
hash算法 一个值 进行一系列的计算得出一个数字,让每一个不同的值计算出的数字都不相等
class Person:
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
def __hash__(self):
# hash算法本身就存在了 且直接在python中就能调用
# 姓名相同 性别相同的对象的hash值应该相等才行
return hash(self.name+self.sex)
def __eq__(self, other):
if self.name == other.name and self.sex == other.sex:return True
p_lst1 = []
for i in range(98):
p_lst1.append(Person('egon' + str(i),i,'man'))
p_lst1.append(Person('egon50',200,'man'))
p_lst1.append(Person('egon51',300,'man'))
print(len(p_lst1))
# set对一个对象序列的去重,依赖于这个对象的两个方法: hash eq
print(len(set(p_lst1)))
执行结果
98
set对一个对象序列的去重,依赖于这个对象的两个方法: hash eq
key hash 数字 --> 内存地址 --> value
set hash 数字 --> 内存地址 --> set中的元素
如果值存在,则覆盖
set对一个对象序列的去重,如何判断这两个值是否相等
值a进行hash --> 存值
值b进行hash --> 判断值是否:相等 --> 说明是一样的
值b进行hash --> 判断值是否: 不相等 --> 在开辟一个空间,来存放b
python常用模块!!
什么叫序列化---将原本的字典,列表等内容转换成一个字符串的过程叫做序列化。
在python代码中计算的一个数据需要给另外一段程序使用,那我们怎么给?现在能想到的方法就是存在文件里,然后另一个python程序再从文件里读出来,很麻烦!
序列化的目的
1 以某种存储形式使自定义对象持久化
2 将对象从一个地方传递到另一个地方
3 使程序更具维护性
python中的序列化模块
json 所有的编程语言都通用的序列化格式,它支持的数据类型非常有限:数字,字符串,列表,字典
pickle 只能在python语言的程序之间传递数据用的,pickle支持python中所有的数据类型
shelve python3.*之后才有的
序列化的使用场景:
1 能存储在文件中的一定是字符串 或者是 字节
2 能在网络上传输的 只有字节
json
json模块提供了四个功能:dumps,dump,loads,load
序列化:dumps 示例
import json
dic = {'张三': (175, 70, '开车')}
ret = json.dumps(dic) # 序列化:将一个字典转换成一个字符串
print(type(dic), dic) # 打印字典类型,内容
print(type(ret), ret) # 打印序列化后的类型,内容
# 注意,json转换完的字符串类型的字典中的字符串是由""双引号表示
执行结果
反序列化:loads 示例
# 反序列化,变成原来的类型
import json
dic = {'张三': (175, 70, '开车')}
ret = json.dumps(dic) # dumps序列化
res = json.loads(ret) # loads反序列化
print(type(res), res)
执行结果
dump方法接收一个文件句柄,直接将字典转换成json字符串写入文件
示例
import json
dic = {'张三': (175, 70, '开车')}
f = open('userinfo', 'w', encoding='utf-8')
# 先接收要序列化的对象,在接收文件句柄
json.dump(dic, f, ensure_ascii=False) # 加上ensure_ascii=False,显示中文
f.close()
直接结果,打开文件看
load方法接收一个文件句柄,直接将文件中的json字符串转换成数据结构返回
import json
dic = {'张三': (175, 70, '开车')}
f = open('userinfo', 'r', encoding='utf-8')
ret = json.load(f)
print(ret)
f.close()
执行结果
{'张三': [175, 70, '开车']}
json个性化输出
import json
data = {'username':['李华','二愣子'],'sex':'male','age':16}
json_dic2 = json.dumps(data,sort_keys=True,indent=2,separators=(',',':'),ensure_ascii=False)
# sort_keys 对每一个元素,做了排序
# indent 表示空白数,控制缩进
# separatots 分隔符
# ensure_ascii=False 不适用ascii编码显示
print(json_dic2)
执行结果
{
"age":16,
"sex":"male",
"username":[
"李华",
"二愣子"
]
}
如果有多个字典,用json如何写入?
错误示例
import json
dic1 = {'张三': (175, 70, '开车')}
dic2 = {'李四': (170, 60, '旅游')}
dic3 = {'王五': (190, 80, '唱歌')}
f = open('user_info', 'a', encoding='utf-8')
json.dump(dic1, f, ensure_ascii=False) # json.dump写进文件
json.dump(dic2, f, ensure_ascii=False)
json.dump(dic3, f, ensure_ascii=False)
f.close()
# json.load读取文件数据
f1 = open('user_info', 'r', encoding='utf-8')
print(json.load(f1))
f1.close()
执行报错,读取文件时报错
json.decoder.JSONDecodeError: Extra data: line 1 column 24 (char 23)
正确方法
import json
dic1 = {'张三': (175, 70, '开车')}
dic2 = {'李四': (170, 60, '旅游')}
dic3 = {'王五': (190, 80, '唱歌')}
f = open('user_info', 'a', encoding='utf-8')
st1 = json.dumps(dic1) # 先用json.dumps序列化成字符串
f.write(st1 + '\n') # 在write写入
st2 = json.dumps(dic2)
f.write(st2 + '\n')
st3 = json.dumps(dic3)
f.write(st3 + '\n')
f.close()
# 读取文件数据
f1 = open('user_info', 'r', encoding='utf-8')
for i in f1:
print(json.loads(i.strip())) # json.loads反序列化
f1.close()
执行结果
{'张三': [175, 70, '开车']}
{'李四': [170, 60, '旅游']}
{'王五': [190, 80, '唱歌']}
总结:
dumps序列化 loads反序列化 只在内存中操作数据 主要用于网络传输 和多个数据与文件打交道
dump序列化 load反序列化 主要用于一个数据直接存在文件里--直接和文件打交道
json不支持元组,不支持除了str数据类型之外的key
json在写入多次dump的时候,不能对应执行多次load来取出数据,pickle可以
json如果要写入多个元素 可以先将元素dumps序列化, f.write(序列化+'\n')写入文件,读出元素的时候,应该先按行读文件,在使用loads将读出来的字符串转换成对应的数据类型
pickle模块
pickle,用于python特有的类型和python的数据类型间进行转换(只能用于python程序之间的交互)
pickle模块提供了四个功能:dumps,dump(序列化,存),loads(反序列化,读),load(不仅可以序列化字典,列表...可以把python中任意的数据类型序列化)
示例
import pickle
dic = {(1999, 20, '开车'): "张三"}
ret = pickle.dumps(dic) # 序列化结果 不是一个可读的字符串 而是一个bytes类型
print(ret)
执行结果
b'\x80\x03}q\x00M\xcf\x07K\x14X\x06\x00\x00\x00\xe5\xbc\x80\xe8\xbd\xa6q\x01\x87q\x02X\x06\x00\x00\x00\xe5\xbc\xa0\xe4\xb8\x89q\x03s.'
利用pickle.loads反序列化读
import pickle
dic = {(1999, 20, '开车'): "张三"}
ret = pickle.dumps(dic) # 序列化结果 不是一个可读的字符串 而是一个bytes类型
print(pickle.loads(ret)) # 反序列化读
执行结果
{(1999, 20, '开车'): '张三'}
文件操作模式必须带b,因为它是bytes类型,所以不需要指定编码
写入pickle.dump
import pickle
dic = {(1999, 20, '开车'): "张三"}
f = open('test', 'wb') # 因为是bytes类型,所以必须要带b,二进制
pickle.dump(dic, f)
f.close()
执行结果,像乱码一样的东西
�}q M�KX 开车q�qX 张三qs.
读取pickle.load,因为它是bytes类型,所以不需要指定编码
import pickle
dic = {(1999, 20, '开车'): "张三"}
f = open('test', 'rb') # 因为是bytes类型,所以必须要带b,二进制
print(pickle.load(f))
f.close()
执行结果
{(1999, 20, '开车'): '张三'}
pickle多行操作
import pickle
# 关于写多行
# 写入文件
dic1 = {(1999, 21, '开车'): "张三"}
dic2 = {(1998, 22, '唱歌'): "李四"}
dic3 = {(1997, 23, '跳舞'): "王五"}
f = open('test1', 'wb')
pickle.dump(dic1, f) # 序列化写入
pickle.dump(dic2, f) # 序列化写入
pickle.dump(dic3, f) # 序列化写入
f.close()
# 读取文件
f1 = open('test1', 'rb')
while True: # 循环读取
try:
print(pickle.load(f1))
except EOFError:
break
执行结果
{(1999, 21, '开车'): '张三'}
{(1998, 22, '唱歌'): '李四'}
{(1997, 23, '跳舞'): '王五'}
关于序列化自定义类的对象
json错误示例
class A:
pass
a = A()
import json
json.dumps(a)
执行报错
TypeError:
json只能是字符串
pickle示例
class A:
def __init__(self, name, age):
self.name = name
self.age = age
a = A('Sam', 80)
import pickle
ret = pickle.dumps(a) # 序列化一个对象
print(ret)
print(pickle.loads(ret)) # 读取对象(内存地址)
print(a.__dict__)
执行结果
b'\x80\x03c__main__\nA\nq\x00)\x81q\x01}q\x02(X\x03\x00\x00\x00ageq\x03KPX\x04\x00\x00\x00nameq\x04X\x03\x00\x00\x00Samq\x05ub.'
{'name': 'Sam', 'age': 80}
将序列化对象写入文件
class A:
def __init__(self, name, age):
self.name = name
self.age = age
a = A('Sam', 80)
import pickle
ret = pickle.dumps(a) # 序列化一个对象
f = open('classes', 'wb')
pickle.dump(a, f) # 序列化对象a写入文件
f.close()
f1 = open('classes', 'rb')
obj = pickle.load(f1) # 反序列化读取对象赋值给obj
print(obj.__dict__)
执行结果
{'age': 80, 'name': 'Sam'}
pickle错误示例
import pickle
f = open('classes', 'rb')
obj = pickle.load(f)
print(obj.__dict__)
执行报错
AttributeError: Can't get attribute 'A'
将对象反序列化时,必须保证该对象在内存中存在,否则读取报错
既然pickle如此强大,为什么还要学json呢?
这里要说明一下,json是一种所有的语言都可以识别的数据结构,而pickle只能是针对python程序而言
shelve序列化,在python3.X之后才新增的
shelve也是python提供给我们的序列化工具,比pickle用起来更简单一些
shelve只提供给我们一个open方法,是用key来访问的,使用起来和字典类似
示例
import shelve
f = shelve.open('shelve_file')
# 直接对文件句柄操作,就可以存入数据
f['key'] = {'int':10, 'float':9.5, 'string':'hello world'}
f.close()
执行结果,会产生3个文件
读取文件
import shelve
f = shelve.open('shelve_file')
# 直接对文件句柄操作,就可以存入数据
f['key'] = {'int':10, 'float':9.5, 'string':'hello world'}
f.close()
f1 = shelve.open('shelve_file')
s1 = f1['key']
print(s1)
执行结果
{'int': 10, 'string': 'hello world', 'float': 9.5}
这个模块有个限制,它不支持多个应用同一时间往同一个DB进行写操作,所以当我们知道我们的应用如果只进行读操作,我们可以让shelve通过只读方式打开DB
import shelve
f = shelve.open('shelve_file', flag='r') # flag = 'r' 表示只读方式
s1 = f['key']
f.close()
print(s1)
执行结果
{'int': 10, 'string': 'hello world', 'float': 9.5}
shelve中的一个bug
import shelve
f = shelve.open('shelve_file', flag='r') # flag = 'r' 表示只读方式
#f['key']['int'] = 50 # 不能修改已有结构中的值
#f['key']['new'] = 'new' # 不能在已有的结构中添加新的项
f['key'] = 'new' # 只读却覆盖了字典 不是很明显这里是什么意思!
print(f['key'])
f.close()
执行结果
new
由于shelve在默认情况下是不会记录对持久化对象的任何修改的,所以我们在shelve.open()时候需要修改默认参数,否则对象的修改不会保存
新增一个属性
import shelve
f = shelve.open('shelve_file', writeback=True)
s1 = f['key']
f['key']['new_value'] = '人生苦短,我用Python'
print(s1)
f.close()
执行结果
{'int': 10, 'new_value': '人生苦短,我用Python', 'string': 'hello world', 'float': 9.5}
shelve的writeback方式有优点也有缺点,优点是减少了我们出错的概率,并且让对象的持久化对用户更加的透明了;使用writeback以后,shelf在open()的时候会增加额外的内存消耗
序列化的使用:推荐使用json和picker
hashlib模块
算法介绍
Python的hashlib提供了常见的摘要算法,如md5,sha1等等。
什么是摘要算法呢?摘要算法又称哈希算法,散列算法。它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)
摘要算法就是通过摘要函数()对任意长度的数据data计算出固定长度的摘要digest,目的是为了发现原始数据是否被人篡改过。
用法:
1 文件的一致性校验
2 密文验证的时候加密
md5算法 通用的算法
sha算法 安全系数更高,sha算法有很多种,后面的数字越大安全系数越高,得到的数字结果越长,计算的时间越长
md5 加密示例
import hashlib
m = hashlib.md5()
m.update('Sam666'.encode('utf-8'))
print(m.hexdigest())
s = hashlib.md5()
s.update('Tom999'.encode('utf-8'))
print(s.hexdigest())
执行结果
1f7062b5dc99693c421c273838c36f48
bb740188c8025148b53505e12f559ddf
这个是16进制的数,固定长度,结果是唯一的(hex)表示16进制
md5不能反解
暴力破解和撞库
将所有常见的密码(123456,admin,111111之类的) 都进行md5摘要,生成一个md5密码库,对比查询(暴力破解和撞库的实现)
怎么解决呢?
加盐
# 加盐md5 123456
m = hashlib.md5('admin'.encode('utf-8'))
m.update('123456'.encode('utf-8'))
print(m.hexdigest())
# 没加盐md5 123456
m = hashlib.md5()
m.update('123456'.encode('utf-8'))
print(m.hexdigest())
执行结果
a66abb5684c45962d887564f08346e8d
e10adc3949ba59abbe56e057f20f883e
结果很明显不一样,如果加盐的密码不暴露,安全性要比单纯的md5高很多
更高级的方法(动态加盐)
import hashlib
username = input('输入账号:').strip()
password = input('输入密码:').strip()
m = hashlib.md5(username[::2].encode('utf-8')) #根据用户名(字符串切片动态加盐)
m.update(password.encode('utf-8'))
print(m.hexdigest())
执行结果
输入账号:admin
输入密码:123456
a7aea2b468c1e775a411558475b661c7
因为每个用户名都是不同的,这种动态加盐的方式,会更安全,也不会被随便破解
文件的一致性校验
一段字符串直接进行摘要和分成几段摘要的结果是相同的
import hashlib
m = hashlib.md5()
m.update('hello,sam,I know your password is sam666'.encode('utf-8'))
print(m.hexdigest())
s = hashlib.md5()
s.update(b'hello,') # 前面加b是转成bytes类型
s.update(b'sam,')
s.update(b'I know your ')
s.update(b'password is sam666')
print(s.hexdigest())
执行结果
d7a8902ae981eb2014d66a2e5f0a3574
d7a8902ae981eb2014d66a2e5f0a3574
文件内容的一致性校验
import hashlib
def check(filename):
m = hashlib.md5()
with open(filename, 'rb') as f:
content = f.read() # 直接读取所有内容
m.update(content) # 对内容进行摘要
return m.hexdigest()
ret1 = check('file1') # 执行前先创建两个文件,内容一样则md5码一样,有个标点不一样,md5则不同
ret2 = check('file2')
print(ret1)
print(ret2)
执行结果
7c31d05a8123bea369e06d400aee8f2f
7c31d05a8123bea369e06d400aee8f2f
上面那种方法不可取,如果文件很大,全部读取的话,内存不够,系统会卡
第二种方法,因为字符串分段后,格式内容一致,那么md5码也是一样的,所以可以每次读取部分内容,这样操作,结果也不会影响
import hashlib
def check(filename):
m = hashlib.md5()
with open(filename, 'rb') as f:
while True:
content = f.read(4096) # 每次读取4096个字节
if content:
m.update(content) # 对内容进行摘要
else:
break
return m.hexdigest()
ret1 = check('file1')
ret2 = check('file2')
print(ret1)
print(ret2)
执行结果
7c31d05a8123bea369e06d400aee8f2f
7c31d05a8123bea369e06d400aee8f2f