day16 序列化、hashlib、collections和软件开发规范

day16 序列化、hashlib、collections和软件开发规范

今日内容

  1. 序列化
    1. json
    2. pickle
  2. hashlib
  3. collections
    1. 统计
    2. 双端队列(队列,栈)
    3. 有序字典
    4. 默认字典
    5. 命名元组
  4. 软件开发规范 -- 将博客园代码规范化,通过模块导入的方式

昨日回顾

  1. 自定义模块

    • 模块的导入
      • import 模块名
      • 模块名.功能
      • from 模块名 import 功能
      • 取别名
      • from 可能会覆盖原有的别名
      • from xx import *
    • 模块查找顺序:内存 --> 内置 --> sys.path
    • sys.path.append("路径")
    • __all__ = ['name', 'login']
    • if __name__ = '__main__':有两种用途
      • 模块
      • 脚本
    • import 和 from
      • import 全部导入
      • from 指定功能导入
      • 推荐使用 from
  2. time模块

    import time
    time.time()
    time.sleep()
    time.localtime()
    time.strftime()
    time.strptime()
    time.mktime()
    %Y-%m-%d %H:%M:%S
    
  3. datetime模块

    from datetime import time
    datetime.now()
    datetime(2019, 11, 1, 11, 11, 11)
    
  4. os模块

    import os
    os.getcwd()
    os.mkdir()
    os.rmdir()
    os.makedirs()
    os.removedirs()
    os.remove()
    os.rename()
    os.path.abspath()
    os.path.basename()
    os.path.dirname()
    os.path.join()
    os.path.isfile()
    os.path.isdir()
    os.path.getsize()
    
  5. sys模块

    import sys
    sys.path
    sys.argv
    sys.platform
    sys.modules
    sys.exit(2)
    sys.version
    
  6. random模块

    import random
    random.random()
    random.randint()
    random.randrange()
    random.choice()
    random.choices()
    random.sample()
    random.shuffle()
    

今日内容详细

序列化

关于序列化,我们只需要掌握两个模块,四种方法。

两个模块:

  1. json 重点,只能实现字典和列表的序列化
  2. pickle

四种方法:

  • dump 和 load 用于文件写入存储
  • dumps 和 loads 用于网络传输(网络编程)

序列化,也就是将一个数据类型转换成另一个数据类型。我们平时使用的listdict等工厂函数就是序列化的一种。

json模块

我们从前说过,对文件的操作都是对字符串进行操作。可是如果使用工厂函数对列表等数据和字符串相互转换会出现很大问题:

lst = [1, 2, 3, 4, 5]
a = str(lst)
print(repr(a))
print(list(a))

输出的结果为:
'[1, 2, 3, 4, 5]'
['[', '1', ',', ' ', '2', ',', ' ', '3', ',', ' ', '4', ',', ' ', '5', ']']

当将列表转换为字符串时,是将列表整体,包括列表元素和括号、逗号甚至空格作为整体转化成字符串。而当我们使用list函数将字符串转换为列表时,又会迭代每一个元素,我们从前的列表就变得面目全非。

我们当然能够使用eval函数将字符串形式的列表转会列表,但我们说过,eval函数在编程过程中是禁止使用的。

这时我们就可以使用json实现列表和字典的序列化:

import json
lst = [1, 2, 3, 4, 5]
a = json.dumps(lst)
print(a, type(a))
b = json.loads(a)
print(b, type(b))

dic = {1: 12, 2: 23}
c = json.dumps(dic)
print(c, type(c))
d = json.loads(c)
print(d, type(d))

输出的结果为:
[1, 2, 3, 4, 5] <class 'str'>
[1, 2, 3, 4, 5] <class 'list'>
{"1": 12, "2": 23} <class 'str'>
{'1': 12, '2': 23} <class 'dict'>

需要注意的是,对于字典数据,如果它的键是数字,经过json转换之后,将会变成字符串。

在上面的例子中,我们用到了dumpsloads两个方法,其中:

  • dumps是序列化
  • loads是反序列化

如果需要序列化的数据中存在中文,就需要将dumps方法的ensure_ascii参数设置为False,否则的话显示的中文将会是一串看不懂的字符:

import json
lst = ["宝元","尚玉杰"]
a = json.dumps(lst)
b = json.dumps(lst, ensure_ascii=False)
print(a, b, sep='\n')

输出的结果为:
["\u5b9d\u5143", "\u5c1a\u7389\u6770"]
["宝元", "尚玉杰"]

dumpsloads方法实现的是字典和列表在程序中的序列化与反序列化。而我们主要的目的是要在文件中进行相关操作,这就涉及到dumpload两个方法:

import json
dic = {'alex': 'alex1234'}
with open('user_info', 'a', encoding='utf-8') as f:
    json.dump(dic, f)
    f.flush()
with open('user_info', 'r', encoding='utf-8') as f1:
    d = json.load(f)
print(d)

输出的结果为:{'alex': 'alex1234'}

上面的代码运行完成后,在当前文件夹中,我们还能找到新创建的文件,里面有我们写入的数据。

1569586110427

上面这种方法只能写入一条数据,如果写入多条数据,json就无法识别而报错。

如果需要写入多条数据,我们就要在每次写入之后加入\n换行;当下次调用时,通过循环来获取每一条内容:

import json
dic = {"alex":"alex1234"}
with open('user_info', 'a', encoding='utf-8') as f:
    for i in range(3):
        json.dump(dic, f)
        f.write('\n')
        f.flush()
with open('user_info', 'r', encoding='utf-8') as f1:
    for j in f1:
        print(json.loads(j), type(json.loads(j)))    # 这里我们是对字符串操作,而不是文件,所以需要使用loads而不是load
        
输出的结果为:
{'alex': 'alex1234'} <class 'dict'>
{'alex': 'alex1234'} <class 'dict'>
{'alex': 'alex1234'} <class 'dict'>

pickle模块

pickle可以将Python中大多数的对象进行序列化(不支持lambda)。与json不同的是,pickle是以字节的形式存储数据,而不是字符串。pickle的用法和json几乎完全一致:

import pickle
def func():
    print(111)
a = pickle.dumps(func)
print(a, type(a))
b = pickle.loads(a)
print(b, type(b))
b()

输出的结果为:
b'\x80\x03c__main__\nfunc\nq\x00.' <class 'bytes'>
<function func at 0x0000027B4D4D1EA0> <class 'function'>
111

json一样,pickle也是使用dumpload实现对文件进行操作。所不同的是,pickle不需要额外的换行操作就可以进行对文件进行多次写如何读取:

import pickle
lst = [1, 2, 3, 4, 5]
for i in range(3):
    lst.append(i)
    with open('test', 'ab') as f:    # pickle写入的是字节,所以需要使用带b的方法
        pickle.dump(lst, f)
        f.flush()


with open('test', 'rb') as f1:
    for i in range(3):
        a = pickle.load(f1)
        print(a)

输出的结果为:
[1, 2, 3, 4, 5, 0]
[1, 2, 3, 4, 5, 0, 1]
[1, 2, 3, 4, 5, 0, 1, 2]

虽然生成的文件仅显示一行,但是不必担心,这只是编码不同造成的,pickle时能识别出其中的换行标识的。千万不可以修改文件中的任何字符,否则文件将不能被读取。

1569587473567

jsonpickle的区别

  • json序列化之后得到的是字符串,仅支持字典和字符串,应用范围极广,各种编程语言几乎都能支持json
  • pickle序列化之后得到的是字节,支持Python中大部分对象,仅被Python支持

hashlib模块

hashlib也称摘要算法或加密算法,其主要功能是对数据进行加密和校验文件的一致性。

hashlib支持的加密算法有:md5、sha1、sha256和sha512,加密复杂度和花费时间依次增加。

加密算法的特点有:

  1. 内容相同,密文一定相同
  2. 加密的密文是不可逆的
  3. 加密过程为:明文 --> 字节 --> 密文

简单的加密操作为:

import hashlib
s = 'abc'
md5 = hashlib.md5()    # 选择加密方式,初始化一个加密
md5.update(s.encode('utf-8'))    # 将要加密的内容,添加到m中
print(md5.hexdigest())

输出的结果为:900150983cd24fb0d6963f7d28e17f72

也可以通过添加一些字符的方式,增加破解难度,称作加盐:

import hashlib
user = input('username:')
pwd = input('pwd:')
md5 = hashlib.md5('oldboy'.encode('utf-8'))    # 加盐
md5.update(pwd.encode('utf-8'))
print(md5.hexdigest())

除了像上面那样设定固定的盐,我们还可以使用用户名来实现动态加盐:

import hashlib
user = input('username:')
pwd = input('pwd:')
md5 = hashlib.md5(user.encode('utf-8'))
md5.update(pwd.encode('utf-8'))
print(md5.hexdigest())

除了md5之外,也可以使用其他方式进行加密,操作是完全相同的:

import hashlib
sha1 = hashlib.sha1()
sha1.update('alex'.encode('utf-8'))
print(sha1.hexdigest())

同样的内容使用同样的方法加密,即便使用不同的方式编码,最终加密出来的结果也是相同的:

import hashlib
sha256 = hashlib.sha256()
sha256.update('wusir'.encode('utf-8'))
print(sha256.hexdigest())

sha256 = hashlib.sha256()
sha256.update('wusir'.encode('gbk'))
print(sha256.hexdigest())

输出的结果为:
49be7bb8561373ffbccbac435bbdfd6ba8d2f973ec7154b3ae7adf266b929ca9
49be7bb8561373ffbccbac435bbdfd6ba8d2f973ec7154b3ae7adf266b929ca9

虽然加密结果一致,但还是建议尽量使用utf-8进行编码。

对于一个长字符串,同时输入和分段输入,得到的加密结果也是一样的:

import hashlib
md5obj = hashlib.md5()
md5obj.update('alex'.encode('utf-8'))
md5obj.update('wusir'.encode('utf-8'))
print(md5obj.hexdigest())

md5obj = hashlib.md5()
md5obj.update('alexwusir'.encode('utf-8'))
print(md5obj.hexdigest())

输出的结果为:
a5e646f24aa7314d8ba3dce048358e90
a5e646f24aa7314d8ba3dce048358e90

因此,对于大文件来说,我们可以逐行加密来节省内存空间。

总结起来,加密的基本结构为:

import hashlib
md5obj = hashlib.md5(盐)
md5obj.update(加密的内容.encode('utf-8'))
print(md5obj.hexdigest())

collections模块

统计

现在有一个列表,我们需要统计每个元素出现的个数,可以通过循环来实现:

lst = [11,2,2,123,1,1,123,12,12,32,12,31,23,123,21,3]
dic = {}
for i in lst:
    dic[i] = lst.count(i)
print(dic)

输出的结果为:
{11: 1, 2: 2, 123: 3, 1: 2, 12: 3, 32: 1, 31: 1, 23: 1, 21: 1, 3: 1}

如果使用collections模块的统计功能,将会更便捷地获得我们想要的结果:

from collections import Counter
lst = [11,2,2,123,1,1,123,12,12,32,12,31,23,123,21,3]
print(Counter(lst))    # 获取的是Counter字典,可以通过dict方法转换为普通字典

输出的结果为:Counter({123: 3, 12: 3, 2: 2, 1: 2, 11: 1, 32: 1, 31: 1, 23: 1, 21: 1, 3: 1})

有序字典

Python3中的字典已经是有序的了,但是对于Python2来说,还是无序的。有序字典在Python2中还是十分有用的:

from collections import OrderedDict
a = OrderedDict({'key1': 1, 'key2': 2})
print(a)
print(a['key1'])

输出的结果为:
OrderedDict([('key1', 1), ('key2', 2)])
1

可以看出,有序字典以一个类似于列表的形式存储。

默认字典

使用默认字典可以快速对字典进行操作:

from collections import defaultdict
dic = defaultdict(list)
dic['key1'].append(10)    # 默认创建键值对'key1':[],可以使用append方法增加元素
dic['key2'] = 123    # 将默认值替换
dic['key3']    # 默认创建键值对'key3':[]
print(dic)

输出的结果为:
defaultdict(<class 'list'>, {'key1': [10], 'key2': 123, 'key3': []})

我们从前又一道作业题,把列表中小于66的元素放到字典键'key1'对应的值的列表中,大于66的元素放到字典键'key2'对应的值的列表中,从前我们是这样做到的:

lst = [11,22,33,44,55,77,88,99]
dic = {'key1': [], 'key2': []}
for i in lst:
    if i > 66:
        dic['key2'].append(i)
    else:
        dic['key1'].append(i)
print(dic)

更巧妙一点的写法为:

lst = [11, 22, 33, 44, 55, 77, 88, 99]
dic = {}
for i in lst:
    dic.setdefault('key2', []).append(i) if i > 66 else dic.setdefault('key1', []).append(i)
print(dic)

使用默认字典,则可以这样写:

from collections import defaultdict
lst = [11, 22, 33, 44, 55, 77, 88, 99]
dic = defaultdict(list)
for i in lst:
    if i > 66:
        dic['key2'].append(i)
    else:
        dic['key1'].append(i)
print(dic)

双端队列

队列是一种先进先出的数据存储方式,栈则是先进后出的数据存储方式。

Python中的列表是一种栈:

lst = []
lst.append(1)
lst.append(2)
lst.append(3)
lst.append(4)
lst.append(5)
print(lst)
lst.pop()
print(lst)
lst.pop()
print(lst)
lst.pop()
print(lst)
lst.pop()
print(lst)
lst.pop()
print(lst)

也可以通过在pop指定删除第一位的方法模拟成为队列:

lst = []
lst.append(1)
lst.append(2)
lst.append(3)
lst.append(4)
lst.append(5)
print(lst)
lst.pop(0)
print(lst)
lst.pop(0)
print(lst)
lst.pop(0)
print(lst)
lst.pop(0)
print(lst)
lst.pop(0)
print(lst)

双端队列则是可以实现在数据两端灵活删除数据的数据结构:

from collections import deque
lst = deque([11, 22, 33, 44])
print(lst)
lst.append(55)    # 在右侧(后方)插入数据
print(lst)
lst.appendleft(66)    # 从左侧(前方)插入数据
print(lst)
lst.pop()    # 删除后输入的数据(栈)
print(lst)
lst.popleft()    # 删除先输入的数据(队列)
print(lst)
lst.insert(2, 77)    # 指定位置插入
print(lst)

输出的结果为:
deque([11, 22, 33, 44])
deque([11, 22, 33, 44, 55])
deque([66, 11, 22, 33, 44, 55])
deque([66, 11, 22, 33, 44])
deque([11, 22, 33, 44])
deque([11, 22, 77, 33, 44])

编程中主要的数据存储结构有:

列表,队列,双端队列,单向链表,双向链表

Python中的垃圾回收机制成为gc

以引用计数为主,标记清除和分带回收为辅

命名元组

命名元组用来使用关键字快速找到相应的数据:

from collections import namedtuple
dg = namedtuple('dg', ['jd', 'wd', 'gd'])
nt = dg(116, 40, 8848)
print(nt)
print(nt.jd)

输出的结果为:
dg(jd=116, wd=40, gd=8848)
116

我们从前学过的time模块的结构化时间就是一种命名元组。

软件开发规范

软件开发规范主要采取的是分文件管理的办法,主要把Python项目分为多个文件,放在指定文件夹中,分类管理。主要的目录结构为:

bin -- 用于存放启动文件
lib -- 用于存放公共组件
core -- 用于存放主逻辑程序,也就是核心代码
db -- 用于存放相关数据
log -- 用于存放日志文件
conf -- 用于存放配置文件,也称静态文件
posted @ 2019-09-28 18:14  shuoliuchn  阅读(130)  评论(0编辑  收藏  举报