Python pickle 二進制序列化和反序列化 - 數據持久化

模塊 pickle 實現了對一個 Python 對象結構的二進制序列化和反序列化。 "pickling" 是將 Python 對象及其所擁有的層次結構轉化爲一個字節流的過程,而 "unpickling" 是相反的操作,會將(來自一個 binary file 或者 bytes-like object 的)字節流轉化回一個對象層次結構。 pickling(和 unpickling)也被稱爲“序列化”, “編組” 1 或者 “平面化”。而爲了避免混亂,此處採用術語 “封存 (pickling)” 和 “解封 (unpickling)”。

pickle 模塊 並不安全。 你只應該對你信任的數據進行 unpickle 操作。
構建惡意的 pickle 數據來 在 解封時執行任意代碼 是可能的。 絕對不要對不信任來源的數據和可能被篡改過的數據進行解封。
請考慮使用 hmac 來對數據進行簽名,確保數據沒有被篡改。
在你處理不信任數據時,更安全的序列化格式如 json 可能更爲適合。

與 json 模塊的比較

在 pickle 協議和 JSON (JavaScript Object Notation) 之間有着本質上的差異:

  • JSON 是一個文本序列化格式(它輸出 unicode 文本,儘管在大多數時候它會接着以 utf-8 編碼),而 pickle 是一個二進制序列化格式;
  • JSON 是我們可以直觀閱讀的,而 pickle 不是;
  • JSON是可互操作的,在Python系統之外廣泛使用,而pickle則是Python專用的;
  • 默認情況下,JSON 只能表示 Python 內置類型的子集,不能表示自定義的類;但 pickle 可以表示大量的 Python 數據類型(可以合理使用 Python 的對象內省功能自動地表示大多數類型,複雜情況可以通過實現 specific object APIs 來解決)。
  • 不像pickle,對一個不信任的JSON進行反序列化的操作本身不會造成任意代碼執行漏洞。

Pickle的基本用法

序列化(Pickling)

要將Python對象序列化爲二進制數據,可以使用pickle.dump()函數。以下是一個簡單的示例,將一個Python列表保存到文件中:

import pickle

data = [1, 2, 3, 4, 5]

# 打開一個文件以寫入二進制數據
with open('data/data.pkl', 'wb') as file:
    pickle.dump(data, file)

在上述代碼中,使用pickle.dump()函數將data列表序列化爲二進制數據,並將其保存到名爲data.pkl的文件中。參數'wb'表示以二進制寫入模式打開文件。

反序列化(Unpickling)

要從文件中加載並反序列化二進制數據,可以使用pickle.load()函數。以下是加載data.pkl文件並還原Python對象的示例:

import pickle

# 打開文件以讀取二進制數據
with open('data/data.pkl', 'rb') as file:
    loaded_data = pickle.load(file)

print("反序列化 %s" % loaded_data)

在上述代碼中,使用pickle.load()函數從data.pkl文件中加載數據,並將其還原爲Python對象。

Pickle的工作原理

pickle模塊的工作原理涉及到將Python對象轉換爲一種可序列化的中間格式,然後再將該中間格式序列化爲二進制數據。這個中間格式是一個自包含的表示對象的字典,其中包含了對象的數據和其類型信息。

當使用pickle.dump()序列化對象時,pickle 模塊首先創建一個包含對象數據和類型信息的中間字典。然後,它將該字典轉換爲二進制數據。反序列化時,pickle模塊將二進制數據還原爲中間字典,然後再從字典中還原Python對象。

這種方法使pickle模塊非常靈活,因爲它可以序列化幾乎所有Python對象,包括自定義對象,只要它們可以在中間字典中表示。

Pickle的適用場景

pickle模塊在以下情況下非常有用:

  • 數據持久化:你可以使用pickle將Python對象保存到文件中,以便稍後讀取。這對於保存模型、配置文件、數據緩存等非常有用。
  • 數據傳輸:你可以使用pickle將Python對象序列化並通過網絡傳輸,以便不同的Python程序之間共享數據。
  • 對象複製:你可以使用pickle將Python對象進行深拷貝,以便創建對象的獨立副本,而不是引用原始對象。
  • 測試和調試:pickle也用於創建模擬數據,以便進行測試和調試。

Pickle的注意事項

儘管pickle非常方便,但在使用它時需要注意一些事項:

  • 安全性:反序列化數據時要小心,因爲pickle可以執行任意代碼。不要從不受信任的來源加載pickle數據,以免遭受安全風險。
  • 版本兼容性:在不同版本的Python之間,pickle數據的兼容性可能會有問題。因此,確保在不同版本之間測試並驗證pickle數據的兼容性。
  • 自定義對象:一些自定義對象的序列化和反序列化可能會受到限制,因此需要額外的配置。你可能需要實現特定的__reduce__方法來控制對象的序列化行爲。

示例代碼

以下是一個示例代碼,演示如何使用pickle模塊來序列化和反序列化一個自定義Python對象:

import pickle

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return f"Person(name='{self.name}', age={self.age})"


# 創建一個自定義對象
person = Person("Alice", 30)

# 序列化並保存到文件
with open('data/person.pkl', 'wb') as file:
    pickle.dump(person, file)

# 從文件中加載並反序列化
with open('data/person.pkl', 'rb') as file:
    loaded_person = pickle.load(file)

print(loaded_person)  # 輸出: Person(name='Alice', age=30)

在上述代碼中,我們首先定義了一個自定義類Person,然後創建了一個Person對象。我們使用pickle將該對象序列化爲二進制數據,然後再從二進制數據中反序列化還原對象。

執行惡意代碼

執行pickle對象中的惡意代碼是非常危險的,因爲它可能會導致數據丟失或系統崩潰。因此,在使用pickle模塊時應該非常小心,並確保只序列化和反序列化來自可信來源的數據。以下是一個示例,演示瞭如何使用pickle模塊執行任意代碼:

import pickle

# 定義一個惡意函數
malicious_func = """
# import os
# os.system('rm -rf /')

with open('example.txt', 'w') as file:
    file.write('Hello, World!')
"""

# 生成將惡意代碼文件
with open('data/malicious.pkl', 'wb') as file:
    pickle.dump(malicious_func, file)

# 從文件中加載並反序列化 -- 確保只反序列化來自可信來源的數據
with open('data/malicious.pkl', 'rb') as file:
    loaded_person = pickle.load(file)

# 執行pickle對象中的惡意函數
exec(loaded_person)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章