python3--序列化模块,hashlib模块

发布时间:2018-04-23 17:48:00编辑:Run阅读(3619)

    内置方法(回顾)

    __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中的元素

    如果值存在,则覆盖

    blob.png

    set对一个对象序列的去重,如何判断这两个值是否相等

    值a进行hash --> 存值

    值b进行hash --> 判断值是否:相等 --> 说明是一样的

    值b进行hash --> 判断值是否: 不相等  --> 在开辟一个空间,来存放b



    python常用模块!!                         

    什么叫序列化---将原本的字典,列表等内容转换成一个字符串的过程叫做序列化。

    在python代码中计算的一个数据需要给另外一段程序使用,那我们怎么给?现在能想到的方法就是存在文件里,然后另一个python程序再从文件里读出来,很麻烦!

    序列化的目的

    1 以某种存储形式使自定义对象持久化

    2 将对象从一个地方传递到另一个地方

    3 使程序更具维护性

    blob.png


    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()

    直接结果,打开文件看

    blob.png


    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个文件

    blob.png


    读取文件

    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


关键字