1. 程式人生 > >day29-序列化 json、pickle、shelve

day29-序列化 json、pickle、shelve

1、pickle和JSON序列化

如果我們想用文字檔案儲存一個 Python 物件怎麼操作?
這裡就涉及到序列化的問題,序列化指的是將原本的字典、列表等內容轉換成一個字串的過程。

2、為什麼要使用序列化

比如,我們在python程式碼中計算的一個數據需要給另外一段程式使用,那我們怎麼給?
現在我們能想到的方法就是存在檔案裡,然後另一個python程式再從檔案裡讀出來。
但是我們都知道,對於檔案來說是沒有字典這個概念的,所以我們只能將資料轉換成字典放到檔案中。
你一定會問,將字典轉換成一個字串很簡單,就是str(dic)就可以辦到了,為什麼我們還要學習序列化模組呢?
沒錯序列化的過程就是從dic 變成str(dic)的過程。現在你可以通過str(dic),將一個名為dic的字典轉換成一個字串,
但是你要怎麼把一個字串轉換成字典呢?
聰明的你肯定想到了eval(),如果我們將一個字串型別的字典str_dic傳給eval,就會得到一個返回的字典型別了。
eval()函式十分強大,但是eval是做什麼的?e官方demo解釋為:將字串str當成有效的表示式來求值並返回計算結果。
BUT!強大的函式有代價。安全性是其最大的缺點。
想象一下,如果我們從檔案中讀出的不是一個數據結構,而是一句"刪除檔案"類似的破壞性語句,那麼後果實在不堪設設想。
而使用eval就要擔這個風險。
所以,我們並不推薦用eval方法來進行反序列化操作(將str轉換成python中的資料結構)

3、序列化的目的

1、以某種儲存形式使自定義物件持久化。
2、將物件從一個地方傳遞到另一個地方。
3、使程式更具維護性。

 

Python中最常用兩種方式進行序列化:
JSON格式
pickle模組

4、JSON

JSON(JavaScript Object Notation, JS 物件標記)是一種輕量級的資料交換格式。
JSON 格式在網際網路應用開發中應用非常廣泛,可以作為不同的服務元件之間進行資料傳遞的格式。在網際網路應用提供的各種 API 介面返回值基本都是 JSON 格式。
Python 也提供了 json 模組支援 JSON 序列化。

Json模組提供了四個功能:dumps、dump、loads、load
dumps loads 用於網路傳輸
dump load 用於檔案寫入和讀取

4.1、dumps、loads 用於對字串進行操作
dumps 和 loads 分別執行了序列化和反序列化的操作,並且 JSON 序列化後的內容為字串,所以文字寫入和讀取不需要用二進位制格式。

import json
dic = {'name': 'Tom', 'age': 25, 'sex': ''}
ret = json.dumps(dic, ensure_ascii = True)  #序列化過程,就是變成一個特殊的字串
print(ret, type(ret))
# 結果:{"name": "Tom", "age": 25, "sex": "man"} <class '
str'> # json.dumps裡面ensure_ascii預設值為True,如果有漢字會顯示成"sex": "\u7537",改成False就可以 #注意,json轉換完的字串型別的字典中的字串是由雙引號""表示的 response = json.loads(ret) print(response, type(response)) #反序列化過程,將json的字串型別轉換成字典格式 #結果:{'name': 'Tom', 'age': 25, 'sex': 'man'} <class 'dict'> #注意,要用json的loads功能處理的字串型別的字典中的字串必須由雙引號""表示 ret1 = json.dumps(dic, separators= (':','|'), ensure_ascii=False) # 分隔符由:變成 | print(ret1) #結果:{"name"|"Tom":"age"|25:"sex"|"man"} list_dic = [1,['a','b','c'],3,{'k1':'v1','k2':'v2'}] #也可以處理巢狀的資料型別 str_dic = json.dumps(list_dic) print(type(str_dic), str_dic) # 結果:<class 'str'> [1, ["a", "b", "c"], 3, {"k1": "v1", "k2": "v2"}] list_dic2 = json.loads(str_dic) print(type(list_dic2), list_dic2) # <class 'list'> [1, ['a', 'b', 'c'], 3, {'k1': 'v1', 'k2': 'v2'}]

4.2、dump、load  用於對檔案進行操作

import json
dic = {'k1':'v1','k2':'v2','k3':'v3'}
with open('json_file', 'w',encoding='utf-8') as f1:
    json.dump(dic, f1)
#dump方法接收一個檔案控制代碼,直接將字典轉換成json字串寫入檔案


with open('json_file', 'r', encoding='utf-8') as f1:
    dic2 = json.load(f1)  #load方法接入一個檔案控制代碼,直接將檔案中的json字串轉換成資料結構返回
print(dic2)
# {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}

import json
data = {'username':['李華','二愣子'],'sex':'male','age':16}
json_dict = json.dumps(data, sort_keys=True, indent=2, separators=(',', ':'), ensure_ascii=False)
print(json_dict)
# {
#   "age":16,
#   "sex":"male",
#   "username":[
#     "李華",
#     "二愣子"
#   ]
# }

Skipkeys:預設值是False,如果dict的keys內的資料不是python的基本型別(str,unicode,int,long,float,bool,None),設定為False時,就會報TypeError的錯誤。此時設定成True,則會跳過這類key

ensure_ascii:預設值True,如果dict內含有non-ASCII的字元,則會類似\uXXXX的顯示資料,設定成False後,就能正常顯示
indent:應該是一個非負的整型,如果值為None,則一行顯示資料,否則會換行且按照indent的數量顯示前面的空白,這樣打印出來的json資料也叫pretty-printed json
separators:分隔符,實際上是(item_separator, dict_separator)的一個元組,預設的就是(‘,’,’:’);這表示dictionary內keys之間用“,”隔開,而KEY和value之間用“:”隔開。
encoding:預設是UTF-8,設定json資料的編碼方式。
sort_keys:將資料根據keys的值進行排序。

json_dict = json.dumps(data, sort_keys=True, indent=None, separators=(',', '|'), ensure_ascii=False)
print(json_dict)
結果:{"age"|16,"sex"|"male","username"|["李華","二愣子"]}

json寫入檔案時如何寫入中文?

在dumps時加入ensure_ascii=False
ensure_ascii:,當它為True的時候,所有非ASCII碼字元顯示為\uXXXX序列,只需在dump時將ensure_ascii設定為False即可,此時存入json的中文即可正常顯示。)
在開啟文字時加入encoding='utf-8'

Json 適用於所有語言
在python中適用於所有語言的型別有以下這些,python中的集合不在這裡

+----------------+---------------+
| Python   | JSON    |
+=========+=========+
| dict         | object   |
+----------------+---------------+
| list, tuple   | array   |
+----------------+---------------+
| str        | string |
+----------------+---------------+
| int, float    | number |
+----------------+---------------+
| True       | true |
+----------------+---------------+
| False           | false |
+----------------+---------------+
| None        | null |
+----------------+---------------+

5、pickle模組

我們首先通過一個例項將 Python 的一個字典存入到檔案中並讀取出來恢復成字典物件,這個過程中用的就是 pickle 模組:
pickle 只用於python語言之間的傳輸,包含所有的python支援的資料型別
pickle模組提供了四個功能:dumps、dump(序列化,存)、loads(反序列化,讀)、load(不僅可以序列化字典,列表...可以把python中任意的資料型別序列化)

import pickle
dic = {'k1':'v1','k2':'v2','k3':'v3'}
str_dic = pickle.dumps(dic)
print(str_dic)
# b'\x80\x03}q\x00(X\x02\x00\x00\x00k1q\x01X\x02\x00\x00\x00v1q\x02X\x02\x00\x00\x00k2q\x03X\x02\x00\x00\x00v2q\x04X\x02\x00\x00\x00k3q\x05X\x02\x00\x00\x00v3q\x06u.'

dic2 = pickle.loads(str_dic)
print(dic2)
# {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}

注意寫入和讀取檔案都需要使用b二進位制模式。
最終我們寫入檔案並讀取後仍然可以恢復到原來的字典物件。如果只是想將物件序列化成一個位元組流,那可以使用 pickle.dumps(obj)。

import pickle
import time

struct_time = time.localtime(100)
print(struct_time)
# time.struct_time(tm_year=1970, tm_mon=1, tm_mday=1, tm_hour=8, tm_min=1, tm_sec=40, tm_wday=3, tm_yday=1, tm_isdst=0)

f = open('pickle_file', 'wb')
pickle.dump(struct_time, f)
f.close()

f = open('pickle_file', 'rb')
struct_time2 = pickle.load(f)
print(struct_time2)
# time.struct_time(tm_year=1970, tm_mon=1, tm_mday=1, tm_hour=8, tm_min=1, tm_sec=40, tm_wday=3, tm_yday=1, tm_isdst=0)

json是一種所有的語言都可以識別的資料結構。
如果我們將一個字典或者序列化成了一個json存在檔案裡,那麼java程式碼或者js程式碼也可以拿來用。
但是如果我們用pickle進行序列化,其他語言就不能讀懂這是什麼了~
所以,如果你序列化的內容是列表或者字典,我們非常推薦你使用json模組
但如果出於某種原因你不得不序列化其他的資料型別,而未來你還會用python對這個資料進行反序列化的話,那麼就可以使用pickle

6、shelve模組

shelve也是python提供給我們的序列化工具,比pickle用起來更簡單一些。
shelve只提供給我們一個open方法,是用key來訪問的,使用起來和字典類似。

import shelve
f = shelve.open('shelve_file')
f['key'] = {'int':10, 'float':9.5, 'string':'Sample data'}  #直接對檔案控制代碼操作,就可以存入資料
f.close()

import shelve
f1 = shelve.open('shelve_file')
existing = f1['key']  #取出資料的時候也只需要直接用key獲取即可,但是如果key不存在會報錯
f1.close()
print(existing)

這個模組有個限制,它不支援多個應用同一時間往同一個DB進行寫操作。所以當我們知道我們的應用如果只進行讀操作,我們可以讓shelve通過只讀方式開啟DB

import shelve
f = shelve.open('shelve_file', flag='r')
existing = f['key']
f.close()
print(existing)

由於shelve在預設情況下是不會記錄待持久化物件的任何修改的,所以我們在shelve.open()時候需要修改預設引數,否則物件的修改不會儲存。

import shelve
f1 = shelve.open('shelve_file')
print(f1['key'])
f1['key']['new_value'] = 'this was not here before'
f1.close()

f2 = shelve.open('shelve_file', writeback=True)
print(f2['key'])
f2['key']['new_value'] = 'this was not here before'
f2.close()

 

writeback方式有優點也有缺點。

優點是減少了我們出錯的概率,並且讓物件的持久化對使用者更加的透明瞭
但這種方式並不是所有的情況下都需要,首先,使用writeback以後,shelve在open()的時候會增加額外的記憶體消耗,並且當DB在close()的時候會將快取中的每一個物件都寫入到DB,這也會帶來額外的等待時間。因為shelve沒有辦法知道快取中哪些物件修改了,哪些物件沒有修改,因此所有的物件都會被寫入。