模块 json&pickle configparser hashlib
1 json&pickle模块
1.1 序列化与反序列化
序列化指将内存中的数据转换为一种特定的格式,可用于存储和传输给平台使用。
特定格式包括json,pickle
反序列化指将特定格式的数据反解成原数据。
eval
dict1 = {'a': 97, 'b': 122}
dict1_str = str(dict1) # 序列化: dict => str
dict2 = eval(dict1_str) # 反序列化: str => dict
1.2 序列化与反序列化的用途
- 用于在硬盘上存储数据;
可以将程序运行的状态保存在硬盘上,例如存档等。 - 将数据传递给其它平台使用。
可用于跨平台/不同语言之间的数据交互。
python的列表 <=> json <=> java的数组
强调:
针对用途1 可以是一种python语言专用的格式 — pickle
针对用途2 应该选用一种在不同语言之间都能识别的通用格式 — json
原因:
json为了兼顾其它语言,并不支持于某种语言所特有的数据类型,例如python的集合,集合在其它语言中可能找不到对应的拥有相似数据处理机制的数据类型,因此json不支持python的集合数据类型;
而pickle只需针对python语言,因此支持所有python的数据类型。
Json | Python |
---|---|
{} | dict |
[] | list |
“string” | str |
1 | int |
1.1 | float |
true/false | boolean: True/False |
null | None |
注意,json中的字符串只支持双引号。
1.3 json模块
json.dumps() 对数据进行序列化
json.loads() 对数据进行反序列化
import json
lst = [1, True, 'abc', {'a': 97, 'A': 65}]
list_json = json.dumps(lst)
print(list_json, type(list_json))
# [1, true, "abc", {"a": 97, "A": 65}] <class 'str'>
res_list = json.loads(list_json)
print(res_list)
# [1, True, 'abc', {'a': 97, 'A': 65}]
操作文件
import json
lst = [1, True, 'abc', {'a': 97, 'A': 65}]
with open(r'./demo1.txt', mode='wt', encoding='utf-8') as f1:
list_json = json.dumps(lst)
f1.write(list_json)
with open(r'./demo2.txt', mode='wt', encoding='utf-8') as f2:
json.dump(lst, f2)
with open(r'./demo1.txt', mode='rt', encoding='utf-8') as f3:
list_json = f3.read()
res_list = json.loads(list_json)
print(res_list)
# [1, True, 'abc', {'a': 97, 'A': 65}]
with open(r'./demo2.txt', mode='rt', encoding='utf-8') as f4:
res_list = json.load(f4)
print(res_list)
# [1, True, 'abc', {'a': 97, 'A': 65}]
1.4 bytes类型
在python2中,str类型就是bytes类型
在python3.6及以后 json的反序列化支持bytes类型的数据
import json
lst = json.loads(b'[1, true, "abc", {"a": 97, "A": 65}]')
print(lst)
with open(r'./demo1.txt', mode='rb') as f3:
list_json = f3.read()
res_list = json.loads(list_json)
print(res_list)
# [1, True, 'abc', {'a': 97, 'A': 65}]
bytes_str = '[1, true, "你好", {"a": 97, "A": 65}]'.encode('utf-8')
print(bytes_str) # b'[1, true, "\xe4\xbd\xa0\xe5\xa5\xbd", {"a": 97, "A": 65}]'
print(type(bytes_str)) # <class 'bytes'>
lst = json.loads(bytes_str)
print(lst) # [1, True, '你好', {'a': 97, 'A': 65}]
1.5 pickle模块
import pickle
set_str = pickle.dumps({1, 2, 3})
print(set_str, type(set_str))
# b'\x80\x04\x95\x0b\x00\x00\x00\x00\x00\x00\x00\x8f\x94(K\x01K\x02K\x03\x90.' <class 'bytes'>
raw_set = pickle.loads(set_str)
print(raw_set) # {1, 2, 3}
因此操作文件时使用b模式打开文件。
python2与python3的pickle兼容性问题
python2不支持大于2的protocol,而python3中默认protocol=4
如果在python3中进行序列化操作后,需要在python2中进行反序列化,
需要在python3的dump操作中指定protocol=2,这样python2中反序列化操作才能正常使用。
# coding: utf-8
import pickle
with open('a.pkl', mode='wb') as f:
pickle.dump('你好啊', f, protocol=2)
with open('a.pkl', mode='rb') as f:
res = pickle.load(f)
print(res)
2 猴子补丁
当使用某个模块时对某个功能不满意,希望自定义这个功能,这个过程就是打补丁(猴子补丁)。
核心:在导入模块后,用自己的代码去替换模块的源代码,而这一操作需要在程序的入口文件中完成。
模块导入的特点:如果对同一模块进行重复导入,后续的导入会直接引入第一次导入的结果。因此应该在首次导入时对模块打补丁。
为了确定首次导入的文件,可以在入口文件start.py中导入模块并打补丁。
例如 ujson模块提供的方法在效率上比json模块相应方法更高。
用ujson模块的dumps()和loads()去替换json模块中的相应方法。
start.py
import json
import ujson
def monkey_patch_json():
json.dumps = ujson.dumps
json.loads = ujson.loads
monkey_patch_json() # 在入口文件调用
分析,导入json模块,创建json名称空间,将json名称空间中的dumps和loads用ujson中的对应方法替换掉。
当后续的程序中导入json模块并调用json.dumps()时,实际上调用的是ujson.dumps()。
注意,不能使用 import ujson as json
因为在程序的其它文件中导入json模块时还是会创建json模块的名称空间,起别名的方法并不会影响名称空间,即如果使用起别名的方法,项目中所有import json的位置都需要替换成 import ujson as json。
3 configparser模块
用来解析配置文件的模块。
ini文件,缩写:Initialization File,即初始化文件
# 注释1
; 注释2
[section1]
k1 = v1
k2: v2
[section2]
k1 = v1
demo.ini
; exp ini file
[Database]
ServerIP=192.168.1.1
ServerPort=8080
ServerCount=10
IsMain=true
[Language]
Language=CHS
import configparser
config = configparser.ConfigParser()
config.read(r'./demo.ini')
# 获取所有sections
print(config.sections()) # ['Database', 'Language']
# 获取options(keys)
print(config.options('Database')) # ['serverip', 'serverport']
# 获取items(key, value)
print(config.items('Database')) # [('serverip', '192.168.1.1'), ('serverport', '8080')]
# 获取指定值
print(config.get('Database', 'ServerPort')) # 8080 str
print(config.getint('Database', 'ServerCount')) # 10 int
4 hashlib模块
4.1 哈希算法
哈希(hash) ,意思是切碎并搅拌,在计算机领域泛指哈希算法,哈希算法是一系列算法的统称,根据接收的数据生成一串数据,即哈希值。
特点:
- 一一对应
只要传入的内容相同,得到的哈希值一定相同。 - 哈希值长度固定
不管传入的数据量大小,生成的哈希值的位数固定,不随输入的数据发生变化。 - 单向算法
无法根据哈希值反推出输入数据。
作用:
- 数据加密(特点1, 3)
例如用户注册时对密码进行加密,将密码生成的哈希值存储于服务端,用户登陆时客户端根据输入的密码生成哈希值,将其发送到服务端与服务端保存的哈希值进行比较确认。这样即使在向客户端传输过程中数据被其他人截取了,他们也很难通过截取到的哈希值反推出原密码。 - 文件完整性校验(特点1, 2)
传输一份文件,用传输后的文件生成哈希值,与源文件的哈希值进行对比,如果一样,则说明在传输过程中数据没有损坏,获得的文件是完整的。
import hashlib
m1 = hashlib.md5()
m1.update('abc'.encode('utf-8'))
m1.update('d'.encode('utf-8'))
m1.update('ef'.encode('utf-8'))
res1 = m1.hexdigest()
print(res1) # e80b5017098950fc58aad83c8c14978e
m2 = hashlib.md5()
m2.update('abcdef'.encode('utf-8'))
res2 = m2.hexdigest()
print(res1 == res2) # True
密码加盐
增加破解成本
import hashlib
pw = 'abc123'
pw_salt = f'aaa{pw}aaa'
m = hashlib.md5()
m.update(pw_salt.encode('utf-8'))
res_pw_salt = m.hexdigest()
pw_input = input('请输入密码:').strip()
pw_input_salt = f'aaa{pw_input}aaa'
m1 = hashlib.md5()
m1.update(pw_input_salt.encode('utf-8'))
res_pw_input_salt = m1.hexdigest()
if res_pw_salt == res_pw_input_salt:
print('密码正确。')
else:
print('密码错误。')
5 subprocess模块
subprocess模块允许你启动一个新的进程,连接输入/输出/错误的管道,
获得子进程的返回码。
import subprocess
obj = subprocess.Popen('dir',
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
print(obj)
res_out = obj.stdout.read() # 返回bytes类型
res_err = obj.stderr.read()
print(res_out.decode('gbk')) # 使用系统的编码类型
print(res_err.decode('gbk'))
6 练习
6.1
把登录与注册的密码都换成密文形式。
注册功能改用json实现。
import hashlib
import json
def read_file():
with open(r'./user_info.txt', mode='rt', encoding='utf-8') as f:
return json.load(f)
def write_file(user_dict):
with open(r'./user_info.txt', mode='wt', encoding='utf-8') as f:
json.dump(user_dict, f)
def register():
user_dict = read_file()
while True:
un_input = input('请输入用户名:').strip()
if un_input in user_dict:
print('用户名已存在,请重新输入。')
else:
break
while True:
pw_input = input('请输入密码:').strip()
re_pw_input = input('请再次输入密码:').strip()
if pw_input != re_pw_input:
print('两次密码输入不一致,请重新输入。')
else:
m = hashlib.md5()
m.update(f'2020{pw_input}2020'.encode('utf-8'))
pw_md5 = m.hexdigest()
break
user_dict[un_input] = [pw_md5, 0]
write_file(user_dict)
print(f'用户{un_input}注册成功,初始金额为0')
def login():
user_dict = read_file()
while True:
un_input = input('请输入用户名:').strip()
if un_input not in user_dict:
print('用户名不存在,请确认后重新输入。')
else:
break
pw_md5 = user_dict[un_input][0]
while True:
pw_input = input('请输入密码:').strip()
m = hashlib.md5()
m.update(f'2020{pw_input}2020'.encode('utf-8'))
pw_input_md5 = m.hexdigest()
if pw_input_md5 != pw_md5:
print('密码错误,请确认后重新输入。')
else:
break
print(f'用户{un_input}登陆成功。')
6.2
文件完整性校验(考虑大文件)
import hashlib
import os
def get_file_md5(filepath):
file_size = os.path.getsize(filepath)
offset_list = [0, file_size // 3, file_size // 3 * 2, file_size - 10]
md5_obj = hashlib.md5()
with open(filepath, 'rb') as f:
for each_offset in offset_list:
f.seek(each_offset)
read_data = f.read(10)
md5_obj.update(read_data)
return md5_obj.hexdigest()
6.3
项目的配置文件采用configparser进行解析
[Database]
DatabaseFolder=db
UserInfo=user_db.txt
[Fiction]
FictionFolder=fictions
FictionClass=fiction_class.txt
[Log]
LogFolder=log
LogFile=log.txt
import configparser
import os
filepath_config = configparser.ConfigParser()
filepath_config.read(r'./filepath.ini')
db_folder = filepath_config.get('Database', 'DatabaseFolder')
user_info_filename = filepath_config.get('Database', 'userinfo')
user_info_filepath = os.path.join(db_folder, user_info_filename)
print(user_info_filepath)