Python學習——序列化與反序列化-json&pickle

淺洛帆發表於2018-04-24

一、概述

玩過稍微大型一點的遊戲的朋友都知道,很多遊戲的存檔功能使得我們可以方便地迅速進入上一次退出的狀態(包括裝備、等級、經驗值等在內的一切執行時資料),那麼在程式開發中也存在這樣的需求:比較簡單的程式,物件的處理都在記憶體中直接實現,程式退出後物件就消失;但對於功能需求稍微拔高一點的程式來講,很多時候往往需要需要把物件持久化儲存起來,以便下次啟動程式時還能直接進入最後一次的狀態。

這個處理過程在程式開發中就是序列化與反序列化。

二、序列化與反序列化的概念

概述中引入了一個遊戲存檔的場景,本質上是遊戲程式把執行時的物件轉換成可以持久儲存的物件,然後儲存(到資料庫)的過程。還是以這個為引子來講講序列化與反序列化的概念(以下概念整合自網路資料,個人認為解釋比較到位了)。

  • 序列化
    我們把程式執行時記憶體中的資料結構或物件轉換成二進位制串位元組序列的過程稱之為序列化,這樣我們就可以對物件實現持久化儲存或網路傳輸。
    請注意以下重點:

    1. 序列化的物件
      是記憶體中的資料結構或物件,也就是我們在程式執行中操縱的一切物件(這是一個物件導向的時代,當然包括很多地方說的變數啦~)
    2. 序列化後的物件
      變為二進位制串位元組序列,這個不作過多解釋,要持久化儲存到硬體或進行網路傳輸,必須是bytes物件。
    3. 序列化的目的
      想想自己玩遊戲時存檔的那種便利性和必要性把,有些物件必須能夠持久化地儲存(一般檔案,資料庫,巴拉巴拉…)或進行網路傳輸(分散式程式),才能滿足功能需求。
  • 反序列化
    反序列化就是序列化的逆向過程,持久化儲存或網路傳輸資料的最終目的也是為了後續使用,必須可以逆向載入到記憶體中二次利用,這就是反序列化。
    python中的序列化與反序列化模組有json和pickle,下面就來看看怎麼玩轉它們。

三、json模組

json模組提供了dumps,loads,dump和load四種方法,下面展開來闡述:

1. dumps序列化和loads反序列化

dumps和loads是成對出現的:
dumps用於將python中的簡單資料型別(典型的是字典和列表,還有字串)進行json格式encode編碼,轉換為符合json格式的字串(返回標準的json格式字串);
loads則剛好相反,用於把符合json格式的字串decode成python中特定的資料型別(注意是簡單的資料型別,下文會詳細解釋)。

>>> import json
>>> list1=['a','b','c']
>>> print(type(list))
<class 'type'>

# dumps序列化,可以理解為encode json過程
>>> print(json.dumps(list1))
["a", "b", "c"]
>>> print(type(json.dumps(list1)))
<class 'str'>   # list dumps處理後變為str型別
>>> dict1={'id':'001','name':'Pumpkin'}
>>> print(type(dict1))
<class 'dict'>
>>> print(type(json.dumps(dict1)))
<class 'str'>   # dict經過dumps處理後也變成str型別

# loads反序列化
>>> print(json.loads(json.dumps(list1)))
['a', 'b', 'c']
>>> print(json.loads(json.dumps(dict1)))
{'id': '001', 'name': 'Pumpkin'}
>>>print(type(json.loads(json.dumps(list1))))
<class 'list'>  # 把經過json dumps處理過的字串loads序列化,可還原為原來的資料型別
>>> print(type(json.loads(json.dumps(dict1))))
<class 'dict'>  # 把經過json dumps處理過的字串loads序列化,可還原為原來的資料型別

以上程式碼僅僅是展示loads方法的效果,實際使用中我們可以把符合python中json格式的自定義字串(比如程式中的輸入)轉換成特定的資料型別,這樣就可以在程式中跑起來:

>>> str1='["a", "b", "c"]'
>>> print(type(json.loads(str1)))
<class 'list'>  # 反序列化為list
>>> print(json.loads(str1))
['a', 'b', 'c']
>>> str2='{"id":"001","name":"Pumpkin"}'
>>> print(json.loads(str2))
{'id': '001', 'name': 'Pumpkin'}
>>> print(json.loads(str2))
{'id': '001', 'name': 'Pumpkin'}
>>> print(type(json.loads(str2)))
<class 'dict'>  # 反序列化為dict

注意:通過loads方法反序列化自定義的字串時,外層的引號必須是單引號,內層的引號是雙引號(可以對照dumps後輸出的引號是雙引號來看),這是python的規範,別問為什麼了。

好了,以上展示了dumps和loads的處理過程,我們的目的不是把python中的執行時物件能進行持久化儲存或網路傳輸嗎?下面展示通過dumps處理後儲存到文字檔案和從文字檔案中loads出歷史儲存的資料效果:

(1) dumps序列化後儲存

import json
dict1 = {'id':'001','name':'Pumpkin'}
with open('dumps.txt', 'w', encoding = 'utf-8') as f:
    f.write(json.dumps(dict1))

看看儲存後的文字檔案dumps.txt的內容:
這裡寫圖片描述

(2) loads反序列化從檔案中載入資料

>>> import json
>>> with open('dumps.txt','r',encoding='utf-8') as f:
...     content = f.read()
>>> print(json.loads(content))
{'id': '001', 'name': 'Pumpkin'}
>>> print(type(json.loads(content)))
<class 'dict'>  #loads反序列化後成功還原為原來的資料型別dict
>>> print(json.loads(content).get('name'))
Pumpkin  #此時可以應用dict的各種大法了
>>> print(json.loads(content)['name'])
Pumpkin

2、 dump序列化和load反序列化

dump和load也是成對出現的,dump可以把python中的特定資料型別(比較多用的還是dict和list)轉換為json格式,並直接寫入寫入一個file-like Object(操作的物件包括未轉換的python物件和file-like Object),load則與此相反,可從檔案物件中反序列化出python的原生物件出來。

>>> import json
>>> dict1={'id': '001', 'name': 'Pumpkin'}
>>> with open('dump.txt','w',encoding='utf-8') as f:
...     json.dump(dict1,f)   # dump序列化,直接操作原生資料型別物件和檔案控制程式碼
...
#load反序列化
>>> with open('dump.txt','r',encoding='utf-8') as f:
...     content = json.load(f)  # 注意這裡先用一個物件把load的內容儲存起來,否則關閉檔案後就不能再訪問了
...
>>> print(content)
{'id': '001', 'name': 'Pumpkin'}
>>> print(type(content))
<class 'dict'>   # 成功反序列化成dict
>>> print(content['name'])
Pumpkin          # 試試dict大法

3、(dumps & loads) VS (dump & load)

對比下dumps & loads和dump & load吧:

(1)dumps & loads

只能解決python中可以被json模組處理的物件和json格式字串之間相互轉換的問題,它們操作的物件分別是可以被json模組處理的物件和json格式字串。

(2)dump和load

可以理解為json檔案處理函式,操作的物件是python中可以被json模組處理的物件和file-like Object,遮蔽或者說省略了json格式字串這一細節。

綜合對比起來,如果要通過檔案儲存或從檔案中載入執行時物件,使用dump和load更方便,程式碼更少;反之如果僅僅需要進行json格式處理,則建議使用dumps和loads。

四、pickle

pickle模組實現了用於對Python物件結構進行序列化和反序列化的二進位制協議,與json模組不同的是pickle模組序列化和反序列化的過程分別叫做 pickling 和 unpickling,且轉換前後是二進位制位元組碼,不再是簡單的可閱讀的字串:

  • pickling: 是將Python物件轉換為位元組流的過程;
  • unpickling: 是將位元組流二進位制檔案或位元組物件轉換回Python物件的過程;

1. dumps序列化和loads反序列化

與json的dumps和loads非常類似,不同的就在於轉換後的格式是二進位制位元組碼

>>> import pickle
>>> dict1={'id':'001','name':'Pumpkin'}
>>> pickle.dumps(dict1)
b'\x80\x03}q\x00(X\x02\x00\x00\x00idq\x01X\x03\x00\x00\x00001q\x02X\x04\x00\x00\
x00nameq\x03X\x07\x00\x00\x00Pumpkinq\x04u.' # 序列化成二進位制位元組碼
>>> print(type(pickle.dumps(dict1)))
<class 'bytes'>
>>> pickle.loads(pickle.dumps(dict1))        # 成功反序列化
{'id': '001', 'name': 'Pumpkin'}
>>> print(type(pickle.loads(pickle.dumps(dict1))))
<class 'dict'>
>>> pickle.loads(pickle.dumps(dict1))['name']
'Pumpkin'

由於pickle序列化後資料型別變為二進位制位元組碼,因此在儲存檔案和讀取檔案時需要分別以wb和rb模式開啟:

>>> import pickle
>>> dict1={'id':'001','name':'Pumpkin'}
>>> with open('picklt.txt','wb') as f:   # 以wb模式開啟檔案後寫入dumps內容
...     f.write(pickle.dumps(dict1))
...

>>> with open('picklt.txt','rb') as f:   # 以rb模式開啟後讀取內容
...     data = pickle.loads(f.read())
...
>>> print(data)
{'id': '001', 'name': 'Pumpkin'}

通過dumps寫入後,由於是二進位制位元組碼,所以開啟picklt.txt時會有亂碼顯示.

2. dump序列化和load反序列化

同理,pickle的dump序列化和load反序列化也和json的dump、load非常類似,還是看相同的例子吧:

>>> import pickle
>>> dict1={'id': '001', 'name': 'Pumpkin'}
>>> with open('pickle.txt','wb') as f:
...     pickle.dump(dict1,f)
...
>>> with open('pickle.txt','rb') as f:
...     content = pickle.load(f)
...
>>> print(content)
{'id': '001', 'name': 'Pumpkin'}
>>> print(content['name'])
Pumpkin

3. 序列化函式

(1)序列化

# !/usr/bin/env python
# -*- coding: utf-8 -*-
import pickle

def sayhi(name):
    print('Hello:', name)

info = {'name':'Pumpkin', 'func':sayhi}  #func對應的值是一個函式

with open('test.txt', 'wb') as f:
    data = pickle.dumps(info)
    f.write(data)

(2)反序列化

# -*- coding: utf-8 -*-

import pickle

def sayhi(name):   #此處需要定義出函式,因為它不能被直接載入到記憶體中
    print('Hello:',name)

with open('test.txt','rb') as f:
    data = pickle.loads(f.read())

print(data.get('name'))
data.get('func')('Tom')

結果輸出:
Pumpkin
Hello: Tom

四、json和pickle對比

  • JSON是一種文字序列化格式(它輸出的是unicode檔案,大多數時候會被編碼為utf-8),而pickle是一個二進位制序列化格式;

  • JOSN處理的是python物件和字串的轉換問題,是我們可以讀懂的資料格式,而pickle是二進位制格式,我們無法讀懂;

  • JSON是與特定的程式語言或系統無關的,且它在Python生態系統之外被廣泛使用,而pickle使用的資料格式是特定於Python的;

  • 預設情況下,JSON只能表示Python內建資料型別,而且是僅限於比較簡單的資料型別,如dict,list和str,對於自定義資料型別需要一些額外的工作來完成;pickle可以直接表示大量的Python資料型別,包括自定資料型別(其中,許多是通過巧妙地使用Python內省功能自動實現的;複雜的情況可以通過實現specific object API來解決)。

參考:http://www.cnblogs.com/linupython/p/8256428.html

相關文章