這是關於序列化和反序列化 Python 物件的教程的第二部分。在第一部分中,您學習了基礎知識,然後深入研究了 Pickle 和 JSON 的細節。
在這一部分中,您將探索 YAML(確保擁有第一部分中的運行範例),討論效能和安全注意事項,了解其他序列化格式,最後了解如何選擇正確的方案。 p>
YAML 是我最喜歡的格式。它是一種人性化的資料序列化格式。與 Pickle 和 JSON 不同,它不是 Python 標準庫的一部分,因此您需要安裝它:
pip 安裝 yaml
yaml模組只有load()
和dump()
函數。預設情況下,它們使用像loads()
和dumps()
這樣的字串,但可以採用第二個參數,它是一個開放流,然後可以轉儲/加載到/來自文件。
import yaml print yaml.dump(simple) boolean: true int_list: [1, 2, 3] none: null number: 3.44 text: string
請注意 YAML 與 Pickle 甚至 JSON 相比的可讀性。現在是 YAML 最酷的部分:它理解 Python 物件!無需自訂編碼器和解碼器。以下是使用 YAML 的複雜序列化/反序列化:
> serialized = yaml.dump(complex) > print serialized a: !!python/object:__main__.A simple: boolean: true int_list: [1, 2, 3] none: null number: 3.44 text: string when: 2016-03-07 00:00:00 > deserialized = yaml.load(serialized) > deserialized == complex True
如您所見,YAML 有自己的符號來標記 Python 物件。輸出仍然非常容易閱讀。日期時間物件不需要任何特殊標記,因為 YAML 本質上支援日期時間物件。
在開始考慮效能之前,您需要考慮效能是否是一個問題。如果您相對不頻繁地序列化/反序列化少量資料(例如在程式開始時讀取設定檔),那麼效能並不是真正的問題,您可以繼續前進。
但是,假設您分析了系統並發現序列化和/或反序列化導致效能問題,則需要解決以下問題。
效能有兩個面向:序列化/反序列化的速度有多快,以及序列化表示有多大?
為了測試各種序列化格式的效能,我將建立一個較大的資料結構,並使用 Pickle、YAML 和 JSON 進行序列化/反序列化。 big_data
清單包含 5,000 個複雜物件。
big_data = [dict(a=simple, when=datetime.now().replace(microsecond=0)) for i in range(5000)]
我將在這裡使用 IPython,因為它有方便的 %timeit
魔術函數來測量執行時間。
import cPickle as pickle In [190]: %timeit serialized = pickle.dumps(big_data) 10 loops, best of 3: 51 ms per loop In [191]: %timeit deserialized = pickle.loads(serialized) 10 loops, best of 3: 24.2 ms per loop In [192]: deserialized == big_data Out[192]: True In [193]: len(serialized) Out[193]: 747328
預設pickle需要83.1毫秒進行序列化,29.2毫秒進行反序列化,序列化大小為747,328位元組。
讓我們嘗試使用最高協定。
In [195]: %timeit serialized = pickle.dumps(big_data, protocol=pickle.HIGHEST_PROTOCOL) 10 loops, best of 3: 21.2 ms per loop In [196]: %timeit deserialized = pickle.loads(serialized) 10 loops, best of 3: 25.2 ms per loop In [197]: len(serialized) Out[197]: 394350
有趣的結果。序列化時間縮短至僅 21.2 毫秒,但反序列化時間略為增加,達 25.2 毫秒。序列化大小顯著縮小至 394,350 位元組 (52%)。
In [253] %timeit serialized = json.dumps(big_data, cls=CustomEncoder) 10 loops, best of 3: 34.7 ms per loop In [253] %timeit deserialized = json.loads(serialized, object_hook=decode_object) 10 loops, best of 3: 148 ms per loop In [255]: len(serialized) Out[255]: 730000
好的。編碼方面的表現似乎比 Pickle 差一點,但解碼的表現卻差很多很多:慢了 6 倍。這是怎麼回事?這是 object_hook
函數的一個工件,需要為每個字典執行以檢查是否需要將其轉換為物件。不使用物件掛鉤運行速度要快得多。
%timeit deserialized = json.loads(serialized) 10 loops, best of 3: 36.2 ms per loop
這裡的教訓是,在序列化和反序列化為 JSON 時,請仔細考慮任何自訂編碼,因為它們可能會對整體效能產生重大影響。
In [293]: %timeit serialized = yaml.dump(big_data) 1 loops, best of 3: 1.22 s per loop In[294]: %timeit deserialized = yaml.load(serialized) 1 loops, best of 3: 2.03 s per loop In [295]: len(serialized) Out[295]: 200091
好的。 YAML 真的非常非常慢。但是,請注意一些有趣的事情:序列化大小僅為 200,091 位元組。比 Pickle 和 JSON 都好得多。讓我們快速了解內部:
In [300]: print serialized[:211] - a: &id001 boolean: true int_list: [1, 2, 3] none: null number: 3.44 text: string when: 2016-03-13 00:11:44 - a: *id001 when: 2016-03-13 00:11:44 - a: *id001 when: 2016-03-13 00:11:44
YAML 在這裡非常聰明。它確定所有 5,000 個字典共享相同的“a”鍵值,因此它僅存儲一次並使用 *id001
為所有物件引用它。
安全性通常是一個關鍵問題。 Pickle和YAML由於建構Python對象,很容易受到程式碼執行攻擊。格式巧妙的檔案可以包含將由 Pickle 或 YAML 執行的任意程式碼。無需驚慌。這是設計使然,並記錄在 Pickle 的文檔中:
警告:pickle 模組並非旨在防止錯誤或惡意建構的資料。切勿取消從不受信任或未經身份驗證的來源收到的資料。
以及 YAML 文件中的內容:
警告:使用從不受信任的來源收到的任何資料呼叫 yaml.load 是不安全的! yaml.load 與 pickle.load 一樣強大,因此可以呼叫任何 Python 函數。
您只需要了解,不應使用 Pickle 或 YAML 載入從不受信任的來源收到的序列化資料。 JSON 沒問題,但是如果您有自訂編碼器/解碼器,那麼您也可能會暴露。
yaml 模組提供了 yaml.safe_load()
函數,該函數僅加載簡單的對象,但隨後您會失去很多 YAML 的功能,並且可能選擇只使用 JSON。
還有許多其他可用的序列化格式。以下是其中的一些。
Protobuf(即協定緩衝區)是 Google 的資料交換格式。它是用 C 實現的,但具有 Python 綁定。它具有複雜的架構並有效地打包資料。非常強大,但不太容易使用。
MessagePack 是另一種流行的序列化格式。它也是二進制且高效的,但與 Protobuf 不同的是它不需要模式。它有一個類似於 JSON 的類型系統,但更豐富一些。鍵可以是任何類型,不僅支援字串和非 UTF8 字串。
CBOR 代表簡潔二進位物件表示。同樣,它支援 JSON 資料模型。 CBOR 不像 Protobuf 或 MessagePack 那麼出名,但它很有趣,原因有兩個:
這是一個大問題。這麼多選擇,你如何選擇?讓我們考慮一下應該考慮的各種因素:
我會讓您變得非常簡單,並介紹幾種常見場景以及我為每種場景推薦的格式:
此處使用 pickle (cPickle) 和 HIGHEST_PROTOCOL
。它快速、高效,無需任何特殊程式碼即可儲存和載入大多數 Python 物件。它也可以用作本地持久緩存。
絕對是 YAML。對於人類需要閱讀或編輯的任何內容來說,沒有什麼比它的簡單性更好的了。它已被 Ansible 和許多其他專案成功使用。在某些情況下,您可能會喜歡使用直接的 Python 模組作為設定檔。這可能是正確的選擇,但它不是序列化,它實際上是程式的一部分,而不是單獨的設定檔。
JSON 顯然是這裡的贏家。如今,Web API 最常由原生使用 JSON 的 JavaScript Web 應用程式使用。某些Web API 可能會傳回其他格式(例如,用於密集表格結果集的csv),但我認為您可以以最小的開銷將csv 資料打包為JSON(無需將每一行作為具有所有列名稱的物件重複)。
使用二進位協定之一:Protobuf(如果需要架構)、MessagePack 或 CBOR。執行您自己的測試來驗證每個選項的效能和代表能力。
Python物件的序列化和反序列化是分散式系統的一個重要面向。您無法直接透過網路傳送 Python 物件。您經常需要與其他語言實作的其他系統進行互通,有時您只想將程式的狀態儲存在持久性儲存中。
Python 在其標準庫中附帶了多種序列化方案,還有更多序列化方案可作為第三方模組使用。了解所有選項以及每個選項的優缺點將使您能夠選擇最適合您情況的方法。
以上是Python 物件序列化與反序列化:第 2 部分的詳細內容。更多資訊請關注PHP中文網其他相關文章!