day16 序列化、hashlib、collections和软件开发规范
day16 序列化、hashlib、collections和软件开发规范
今日内容
- 序列化
- json
- pickle
- hashlib
- collections
- 统计
- 双端队列(队列,栈)
- 有序字典
- 默认字典
- 命名元组
- 软件开发规范 -- 将博客园代码规范化,通过模块导入的方式
昨日回顾
-
自定义模块
- 模块的导入
- import 模块名
- 模块名.功能
- from 模块名 import 功能
- 取别名
- from 可能会覆盖原有的别名
- from xx import *
- 模块查找顺序:内存 --> 内置 --> sys.path
- sys.path.append("路径")
__all__ = ['name', 'login']
if __name__ = '__main__':
有两种用途- 模块
- 脚本
- import 和 from
- import 全部导入
- from 指定功能导入
- 推荐使用 from
- 模块的导入
-
time模块
import time time.time() time.sleep() time.localtime() time.strftime() time.strptime() time.mktime() %Y-%m-%d %H:%M:%S
-
datetime模块
from datetime import time datetime.now() datetime(2019, 11, 1, 11, 11, 11)
-
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()
-
sys模块
import sys sys.path sys.argv sys.platform sys.modules sys.exit(2) sys.version
-
random模块
import random random.random() random.randint() random.randrange() random.choice() random.choices() random.sample() random.shuffle()
今日内容详细
序列化
关于序列化,我们只需要掌握两个模块,四种方法。
两个模块:
- json 重点,只能实现字典和列表的序列化
- pickle
四种方法:
- dump 和 load 用于文件写入存储
- dumps 和 loads 用于网络传输(网络编程)
序列化,也就是将一个数据类型转换成另一个数据类型。我们平时使用的list
、dict
等工厂函数就是序列化的一种。
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转换之后,将会变成字符串。
在上面的例子中,我们用到了dumps
和loads
两个方法,其中:
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"]
["宝元", "尚玉杰"]
dumps
和loads
方法实现的是字典和列表在程序中的序列化与反序列化。而我们主要的目的是要在文件中进行相关操作,这就涉及到dump
和load
两个方法:
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'}
上面的代码运行完成后,在当前文件夹中,我们还能找到新创建的文件,里面有我们写入的数据。
上面这种方法只能写入一条数据,如果写入多条数据,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
也是使用dump
和load
实现对文件进行操作。所不同的是,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时能识别出其中的换行标识的。千万不可以修改文件中的任何字符,否则文件将不能被读取。
json
和pickle
的区别
json
序列化之后得到的是字符串,仅支持字典和字符串,应用范围极广,各种编程语言几乎都能支持json
pickle
序列化之后得到的是字节,支持Python中大部分对象,仅被Python支持
hashlib模块
hashlib也称摘要算法或加密算法,其主要功能是对数据进行加密和校验文件的一致性。
hashlib支持的加密算法有:md5、sha1、sha256和sha512,加密复杂度和花费时间依次增加。
加密算法的特点有:
- 内容相同,密文一定相同
- 加密的密文是不可逆的
- 加密过程为:明文 --> 字节 --> 密文
简单的加密操作为:
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 -- 用于存放配置文件,也称静态文件