pickle --- Python 對(duì)象序列化?

源代碼: Lib/pickle.py


模塊 pickle 實(shí)現(xiàn)了對(duì)一個(gè) Python 對(duì)象結(jié)構(gòu)的二進(jìn)制序列化和反序列化。 "pickling" 是將 Python 對(duì)象及其所擁有的層次結(jié)構(gòu)轉(zhuǎn)化為一個(gè)字節(jié)流的過(guò)程,而 "unpickling" 是相反的操作,會(huì)將(來(lái)自一個(gè) binary file 或者 bytes-like object 的)字節(jié)流轉(zhuǎn)化回一個(gè)對(duì)象層次結(jié)構(gòu)。 pickling(和 unpickling)也被稱(chēng)為“序列化”, “編組” 1 或者 “平面化”。而為了避免混亂,此處采用術(shù)語(yǔ) “封存 (pickling)” 和 “解封 (unpickling)”。

警告

pickle 模塊**并不安全**。你只應(yīng)該對(duì)你信任的數(shù)據(jù)進(jìn)行unpickle操作。

構(gòu)建惡意的 pickle 數(shù)據(jù)來(lái)**在解封時(shí)執(zhí)行任意代碼**是可能的。絕對(duì)不要對(duì)不信任來(lái)源的數(shù)據(jù)和可能被篡改過(guò)的數(shù)據(jù)進(jìn)行解封。

請(qǐng)考慮使用 hmac 來(lái)對(duì)數(shù)據(jù)進(jìn)行簽名,確保數(shù)據(jù)沒(méi)有被篡改。

在你處理不信任數(shù)據(jù)時(shí),更安全的序列化格式如 json 可能更為適合。參見(jiàn) 與 json 模塊的比較 。

與其他 Python 模塊間的關(guān)系?

marshal 間的關(guān)系?

Python 有一個(gè)更原始的序列化模塊稱(chēng)為 marshal,但一般地 pickle 應(yīng)該是序列化 Python 對(duì)象時(shí)的首選。marshal 存在主要是為了支持 Python 的 .pyc 文件.

pickle 模塊與 marshal 在如下幾方面顯著地不同:

  • pickle 模塊會(huì)跟蹤已被序列化的對(duì)象,所以該對(duì)象之后再次被引用時(shí)不會(huì)再次被序列化。marshal 不會(huì)這么做。

    這隱含了遞歸對(duì)象和共享對(duì)象。遞歸對(duì)象指包含對(duì)自己的引用的對(duì)象。這種對(duì)象并不會(huì)被 marshal 接受,并且實(shí)際上嘗試 marshal 遞歸對(duì)象會(huì)讓你的 Python 解釋器崩潰。對(duì)象共享發(fā)生在對(duì)象層級(jí)中存在多處引用同一對(duì)象時(shí)。pickle 只會(huì)存儲(chǔ)這些對(duì)象一次,并確保其他的引用指向同一個(gè)主副本。共享對(duì)象將保持共享,這可能對(duì)可變對(duì)象非常重要。

  • marshal 不能被用于序列化用戶(hù)定義類(lèi)及其實(shí)例。pickle 能夠透明地存儲(chǔ)并保存類(lèi)實(shí)例,然而此時(shí)類(lèi)定義必須能夠從與被存儲(chǔ)時(shí)相同的模塊被引入。

  • 同樣用于序列化的 marshal 格式不保證數(shù)據(jù)能移植到不同的 Python 版本中。因?yàn)樗闹饕蝿?wù)是支持 .pyc 文件,必要時(shí)會(huì)以破壞向后兼容的方式更改這種序列化格式,為此 Python 的實(shí)現(xiàn)者保留了更改格式的權(quán)利。pickle 序列化格式可以在不同版本的 Python 中實(shí)現(xiàn)向后兼容,前提是選擇了合適的 pickle 協(xié)議。如果你的數(shù)據(jù)要在 Python 2 與 Python 3 之間跨越傳遞,封存和解封的代碼在 2 和 3 之間也是不同的。

json 模塊的比較?

Pickle 協(xié)議和 JSON (JavaScript Object Notation) 間有著本質(zhì)的不同:

  • JSON 是一個(gè)文本序列化格式(它輸出 unicode 文本,盡管在大多數(shù)時(shí)候它會(huì)接著以 utf-8 編碼),而 pickle 是一個(gè)二進(jìn)制序列化格式;

  • JSON 是我們可以直觀閱讀的,而 pickle 不是;

  • JSON是可互操作的,在Python系統(tǒng)之外廣泛使用,而pickle則是Python專(zhuān)用的;

  • 默認(rèn)情況下,JSON 只能表示 Python 內(nèi)置類(lèi)型的子集,不能表示自定義的類(lèi);但 pickle 可以表示大量的 Python 數(shù)據(jù)類(lèi)型(可以合理使用 Python 的對(duì)象內(nèi)省功能自動(dòng)地表示大多數(shù)類(lèi)型,復(fù)雜情況可以通過(guò)實(shí)現(xiàn) specific object APIs 來(lái)解決)。

  • 不像pickle,對(duì)一個(gè)不信任的JSON進(jìn)行反序列化的操作本身不會(huì)造成任意代碼執(zhí)行漏洞。

參見(jiàn)

json 模塊:一個(gè)允許JSON序列化和反序列化的標(biāo)準(zhǔn)庫(kù)模塊

數(shù)據(jù)流格式?

pickle 所使用的數(shù)據(jù)格式僅可用于 Python。這樣做的好處是沒(méi)有外部標(biāo)準(zhǔn)給該格式強(qiáng)加限制,比如 JSON 或 XDR(不能表示共享指針)標(biāo)準(zhǔn);但這也意味著非 Python 程序可能無(wú)法重新讀取 pickle 封存的 Python 對(duì)象。

默認(rèn)情況下,pickle 格式使用相對(duì)緊湊的二進(jìn)制來(lái)存儲(chǔ)。如果需要讓文件更小,可以高效地 壓縮 由 pickle 封存的數(shù)據(jù)。

pickletools 模塊包含了相應(yīng)的工具用于分析 pickle 生成的數(shù)據(jù)流。pickletools 源碼中包含了對(duì) pickle 協(xié)議使用的操作碼的大量注釋。

當(dāng)前共有 6 種不同的協(xié)議可用于封存操作。 使用的協(xié)議版本越高,讀取所生成 pickle 對(duì)象所需的 Python 版本就要越新。

  • v0 版協(xié)議是原始的“人類(lèi)可讀”協(xié)議,并且向后兼容早期版本的 Python。

  • v1 版協(xié)議是較早的二進(jìn)制格式,它也與早期版本的 Python 兼容。

  • Protocol version 2 was introduced in Python 2.3. It provides much more efficient pickling of new-style classes. Refer to PEP 307 for information about improvements brought by protocol 2.

  • v3 版協(xié)議是在 Python 3.0 中引入的。 它顯式地支持 bytes 字節(jié)對(duì)象,不能使用 Python 2.x 解封。這是 Python 3.0-3.7 的默認(rèn)協(xié)議。

  • v4 版協(xié)議添加于 Python 3.4。它支持存儲(chǔ)非常大的對(duì)象,能存儲(chǔ)更多種類(lèi)的對(duì)象,還包括一些針對(duì)數(shù)據(jù)格式的優(yōu)化。它是Python 3.8使用的默認(rèn)協(xié)議。有關(guān)第 4 版協(xié)議帶來(lái)改進(jìn)的信息,請(qǐng)參閱 PEP 3154。

  • 第 5 版協(xié)議是在 Python 3.8 中加入的。 它增加了對(duì)帶外數(shù)據(jù)的支持,并可加速帶內(nèi)數(shù)據(jù)處理。 請(qǐng)參閱 PEP 574 了解第 5 版協(xié)議所帶來(lái)的改進(jìn)的詳情。

備注

序列化是一種比持久化更底層的概念,雖然 pickle 讀取和寫(xiě)入的是文件對(duì)象,但它不處理持久對(duì)象的命名問(wèn)題,也不處理對(duì)持久對(duì)象的并發(fā)訪問(wèn)(甚至更復(fù)雜)的問(wèn)題。pickle 模塊可以將復(fù)雜對(duì)象轉(zhuǎn)換為字節(jié)流,也可以將字節(jié)流轉(zhuǎn)換為具有相同內(nèi)部結(jié)構(gòu)的對(duì)象。處理這些字節(jié)流最常見(jiàn)的做法是將它們寫(xiě)入文件,但它們也可以通過(guò)網(wǎng)絡(luò)發(fā)送或存儲(chǔ)在數(shù)據(jù)庫(kù)中。shelve 模塊提供了一個(gè)簡(jiǎn)單的接口,用于在 DBM 類(lèi)型的數(shù)據(jù)庫(kù)文件上封存和解封對(duì)象。

模塊接口?

要序列化某個(gè)包含層次結(jié)構(gòu)的對(duì)象,只需調(diào)用 dumps() 函數(shù)即可。同樣,要反序列化數(shù)據(jù)流,可以調(diào)用 loads() 函數(shù)。但是,如果要對(duì)序列化和反序列化加以更多的控制,可以分別創(chuàng)建 PicklerUnpickler 對(duì)象。

pickle 模塊包含了以下常量:

pickle.HIGHEST_PROTOCOL?

整數(shù),可用的最高 協(xié)議版本。此值可以作為 協(xié)議 值傳遞給 dump()dumps() 函數(shù),以及 Pickler 的構(gòu)造函數(shù)。

pickle.DEFAULT_PROTOCOL?

整數(shù),用于 pickle 數(shù)據(jù)的默認(rèn) 協(xié)議版本。它可能小于 HIGHEST_PROTOCOL。當(dāng)前默認(rèn)協(xié)議是 v4,它在 Python 3.4 中首次引入,與之前的版本不兼容。

在 3.0 版更改: 默認(rèn)協(xié)議版本是 3。

在 3.8 版更改: 默認(rèn)協(xié)議版本是 4。

pickle 模塊提供了以下方法,讓封存過(guò)程更加方便:

pickle.dump(obj, file, protocol=None, *, fix_imports=True, buffer_callback=None)?

將對(duì)象 obj 封存以后的對(duì)象寫(xiě)入已打開(kāi)的 file object file。它等同于 Pickler(file, protocol).dump(obj)。

參數(shù) file、protocol、fix_importsbuffer_callback 的含義與它們?cè)?Pickler 的構(gòu)造函數(shù)中的含義相同。

在 3.8 版更改: 加入了 buffer_callback 參數(shù)。

pickle.dumps(obj, protocol=None, *, fix_imports=True, buffer_callback=None)?

obj 封存以后的對(duì)象作為 bytes 類(lèi)型直接返回,而不是將其寫(xiě)入到文件。

參數(shù) protocol、fix_importsbuffer_callback 的含義與它們?cè)?Pickler 的構(gòu)造函數(shù)中的含義相同。

在 3.8 版更改: 加入了 buffer_callback 參數(shù)。

pickle.load(file, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None)?

從已打開(kāi)的 file object 文件 中讀取封存后的對(duì)象,重建其中特定對(duì)象的層次結(jié)構(gòu)并返回。它相當(dāng)于 Unpickler(file).load()。

Pickle 協(xié)議版本是自動(dòng)檢測(cè)出來(lái)的,所以不需要參數(shù)來(lái)指定協(xié)議。封存對(duì)象以外的其他字節(jié)將被忽略。

參數(shù) file、fix_imports、encoding、errorsstrictbuffers 的含義與它們?cè)?Unpickler 的構(gòu)造函數(shù)中的含義相同。

在 3.8 版更改: 加入了 buffers 參數(shù)。

pickle.loads(data, /, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None)?

重建并返回一個(gè)對(duì)象的封存表示形式 data 的對(duì)象層級(jí)結(jié)構(gòu)。 data 必須為 bytes-like object

Pickle 協(xié)議版本是自動(dòng)檢測(cè)出來(lái)的,所以不需要參數(shù)來(lái)指定協(xié)議。封存對(duì)象以外的其他字節(jié)將被忽略。

Arguments fix_imports, encoding, errors, strict and buffers have the same meaning as in the Unpickler constructor.

在 3.8 版更改: 加入了 buffers 參數(shù)。

pickle 模塊定義了以下 3 個(gè)異常:

exception pickle.PickleError?

其他 pickle 異常的基類(lèi)。它是 Exception 的一個(gè)子類(lèi)。

exception pickle.PicklingError?

當(dāng) Pickler 遇到無(wú)法解封的對(duì)象時(shí)拋出此錯(cuò)誤。它是 PickleError 的子類(lèi)。

參考 可以被封存/解封的對(duì)象 來(lái)了解哪些對(duì)象可以被封存。

exception pickle.UnpicklingError?

當(dāng)解封出錯(cuò)時(shí)拋出此異常,例如數(shù)據(jù)損壞或?qū)ο蟛话踩?。它?PickleError 的子類(lèi)。

注意,解封時(shí)可能還會(huì)拋出其他異常,包括(但不限于) AttributeError、EOFError、ImportError 和 IndexError。

pickle 模塊包含了 3 個(gè)類(lèi),Pickler、UnpicklerPickleBuffer

class pickle.Pickler(file, protocol=None, *, fix_imports=True, buffer_callback=None)?

它接受一個(gè)二進(jìn)制文件用于寫(xiě)入 pickle 數(shù)據(jù)流。

可選參數(shù) protocol 是一個(gè)整數(shù),告知 pickler 使用指定的協(xié)議,可選擇的協(xié)議范圍從 0 到 HIGHEST_PROTOCOL。如果沒(méi)有指定,這一參數(shù)默認(rèn)值為 DEFAULT_PROTOCOL。指定一個(gè)負(fù)數(shù)就相當(dāng)于指定 HIGHEST_PROTOCOL。

參數(shù) file 必須有一個(gè) write() 方法,該 write() 方法要能接收字節(jié)作為其唯一參數(shù)。因此,它可以是一個(gè)打開(kāi)的磁盤(pán)文件(用于寫(xiě)入二進(jìn)制內(nèi)容),也可以是一個(gè) io.BytesIO 實(shí)例,也可以是滿(mǎn)足這一接口的其他任何自定義對(duì)象。

如果 fix_imports 為 True 且 protocol 小于 3,pickle 將嘗試將 Python 3 中的新名稱(chēng)映射到 Python 2 中的舊模塊名稱(chēng),因此 Python 2 也可以讀取封存的數(shù)據(jù)流。

如果 buffer_callback 為 None(默認(rèn)情況),緩沖區(qū)視圖(buffer view)將會(huì)作為 pickle 流的一部分被序列化到 file 中。

如果 buffer_callback 不為 None,那它可以用緩沖區(qū)視圖調(diào)用任意次。如果某次調(diào)用返回了 False 值(例如 None),則給定的緩沖區(qū)是 帶外的,否則緩沖區(qū)是帶內(nèi)的(例如保存在了 pickle 流里面)。

如果 buffer_callback 不是 None 且 protocol 是 None 或小于 5,就會(huì)出錯(cuò)。

在 3.8 版更改: 加入了 buffer_callback 參數(shù)。

dump(obj)?

obj 封存后的內(nèi)容寫(xiě)入已打開(kāi)的文件對(duì)象,該文件對(duì)象已經(jīng)在構(gòu)造函數(shù)中指定。

persistent_id(obj)?

默認(rèn)無(wú)動(dòng)作,子類(lèi)繼承重載時(shí)使用。

如果 persistent_id() 返回 None,obj 會(huì)被照常 pickle。如果返回其他值,Pickler 會(huì)將這個(gè)函數(shù)的返回值作為 obj 的持久化 ID(Pickler 本應(yīng)得到序列化數(shù)據(jù)流并將其寫(xiě)入文件,若此函數(shù)有返回值,則得到此函數(shù)的返回值并寫(xiě)入文件)。這個(gè)持久化 ID 的解釋?xiě)?yīng)當(dāng)定義在 Unpickler.persistent_load() 中(該方法定義還原對(duì)象的過(guò)程,并返回得到的對(duì)象)。注意,persistent_id() 的返回值本身不能擁有持久化 ID。

參閱 持久化外部對(duì)象 獲取詳情和使用示例。

dispatch_table?

Pickler 對(duì)象的 dispatch 表是 copyreg.pickle() 中用到的 reduction 函數(shù) 的注冊(cè)。dispatch 表本身是一個(gè) class 到其 reduction 函數(shù)的映射鍵值對(duì)。一個(gè) reduction 函數(shù)只接受一個(gè)參數(shù),就是其關(guān)聯(lián)的 class,函數(shù)行為應(yīng)當(dāng)遵守 __reduce__() 接口規(guī)范。

Pickler 對(duì)象默認(rèn)并沒(méi)有 dispatch_table 屬性,該對(duì)象默認(rèn)使用 copyreg 模塊中定義的全局 dispatch 表。如果要為特定 Pickler 對(duì)象自定義序列化過(guò)程,可以將 dispatch_table 屬性設(shè)置為類(lèi)字典對(duì)象(dict-like object)。另外,如果 Pickler 的子類(lèi)設(shè)置了 dispatch_table 屬性,則該子類(lèi)的實(shí)例會(huì)使用這個(gè)表作為默認(rèn)的 dispatch 表。

參閱 Dispatch 表 獲取使用示例。

3.3 新版功能.

reducer_override(obj)?

可以在 Pickler 的子類(lèi)中定義的特殊 reducer。此方法的優(yōu)先級(jí)高于 dispatch_table 中的任何 reducer。它應(yīng)該與 __reduce__() 方法遵循相同的接口,它也可以返回 NotImplemented,這將使用 dispatch_table 里注冊(cè)的 reducer 來(lái)封存 obj

參閱 類(lèi)型,函數(shù)和其他對(duì)象的自定義歸約 獲取詳細(xì)的示例。

3.8 新版功能.

fast?

已棄用。設(shè)為 True 則啟用快速模式??焖倌J浇昧恕皞渫洝?(memo) 的使用,即不生成多余的 PUT 操作碼來(lái)加快封存過(guò)程。不應(yīng)將其與自指 (self-referential) 對(duì)象一起使用,否則將導(dǎo)致 Pickler 無(wú)限遞歸。

如果需要進(jìn)一步提高 pickle 的壓縮率,請(qǐng)使用 pickletools.optimize()

class pickle.Unpickler(file, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None)?

它接受一個(gè)二進(jìn)制文件用于讀取 pickle 數(shù)據(jù)流。

Pickle 協(xié)議版本是自動(dòng)檢測(cè)出來(lái)的,所以不需要參數(shù)來(lái)指定協(xié)議。

參數(shù) file 必須有三個(gè)方法,read() 方法接受一個(gè)整數(shù)參數(shù),readinto() 方法接受一個(gè)緩沖區(qū)作為參數(shù),readline() 方法不需要參數(shù),這與 io.BufferedIOBase 里定義的接口是相同的。因此 file 可以是一個(gè)磁盤(pán)上用于二進(jìn)制讀取的文件,也可以是一個(gè) io.BytesIO 實(shí)例,也可以是滿(mǎn)足這一接口的其他任何自定義對(duì)象。

可選的參數(shù)是 fix_imports, encodingerrors,用于控制由Python 2 生成的 pickle 流的兼容性。如果 fix_imports 為 True,則 pickle 將嘗試將舊的 Python 2 名稱(chēng)映射到 Python 3 中對(duì)應(yīng)的新名稱(chēng)。encodingerrors 參數(shù)告訴 pickle 如何解碼 Python 2 存儲(chǔ)的 8 位字符串實(shí)例;這兩個(gè)參數(shù)默認(rèn)分別為 'ASCII' 和 'strict'。encoding 參數(shù)可置為 'bytes' 來(lái)將這些 8 位字符串實(shí)例讀取為字節(jié)對(duì)象。讀取 NumPy array 和 Python 2 存儲(chǔ)的 datetimedatetime 實(shí)例時(shí),請(qǐng)使用 encoding='latin1'。

如果 buffers 為 None(默認(rèn)值),則反序列化所需的所有數(shù)據(jù)都必須包含在 pickle 流中。這意味著在實(shí)例化 Pickler 時(shí)(或調(diào)用 dump()dumps() 時(shí)),參數(shù) buffer_callback 為 None。

如果 buffers 不為 None,則每次 pickle 流引用 帶外 緩沖區(qū)視圖時(shí),消耗的對(duì)象都應(yīng)該是可迭代的啟用緩沖區(qū)的對(duì)象。這樣的緩沖區(qū)應(yīng)該按順序地提供給 Pickler 對(duì)象的 buffer_callback 方法。

在 3.8 版更改: 加入了 buffers 參數(shù)。

load()?

從構(gòu)造函數(shù)中指定的文件對(duì)象里讀取封存好的對(duì)象,重建其中特定對(duì)象的層次結(jié)構(gòu)并返回。封存對(duì)象以外的其他字節(jié)將被忽略。

persistent_load(pid)?

默認(rèn)拋出 UnpicklingError 異常。

如果定義了此方法,persistent_load() 應(yīng)當(dāng)返回持久化 ID pid 所指定的對(duì)象。 如果遇到無(wú)效的持久化 ID,則應(yīng)當(dāng)引發(fā) UnpicklingError

參閱 持久化外部對(duì)象 獲取詳情和使用示例。

find_class(module, name)?

如有必要,導(dǎo)入 module 模塊并返回其中名叫 name 的對(duì)象,其中 modulename 參數(shù)都是 str 對(duì)象。注意,不要被這個(gè)函數(shù)的名字迷惑, find_class() 同樣可以用來(lái)導(dǎo)入函數(shù)。

子類(lèi)可以重載此方法,來(lái)控制加載對(duì)象的類(lèi)型和加載對(duì)象的方式,從而盡可能降低安全風(fēng)險(xiǎn)。參閱 限制全局變量 獲取更詳細(xì)的信息。

引發(fā)一個(gè) 審計(jì)事件 pickle.find_class 附帶參數(shù) module、name。

class pickle.PickleBuffer(buffer)?

緩沖區(qū)的包裝器 (wrapper),緩沖區(qū)中包含著可封存的數(shù)據(jù)。buffer 必須是一個(gè) buffer-providing 對(duì)象,比如 bytes-like object 或多維數(shù)組。

PickleBuffer 本身就可以生成緩沖區(qū)對(duì)象,因此可以將其傳遞給需要緩沖區(qū)生成器的其他 API,比如 memoryview。

PickleBuffer 對(duì)象只能用 pickle 版本 5 及以上協(xié)議進(jìn)行序列化。它們符合 帶外序列化 的條件。

3.8 新版功能.

raw()?

返回該緩沖區(qū)底層內(nèi)存區(qū)域的 memoryview。 返回的對(duì)象是一維的、C 連續(xù)布局的 memoryview,格式為 B (無(wú)符號(hào)字節(jié))。 如果緩沖區(qū)既不是 C 連續(xù)布局也不是 Fortran 連續(xù)布局的,則拋出 BufferError 異常。

release()?

釋放由 PickleBuffer 占用的底層緩沖區(qū)。

可以被封存/解封的對(duì)象?

下列類(lèi)型可以被封存:

  • None, True, and False;

  • integers, floating-point numbers, complex numbers;

  • strings, bytes, bytearrays;

  • tuples, lists, sets, and dictionaries containing only picklable objects;

  • functions (built-in and user-defined) accessible from the top level of a module (using def, not lambda);

  • classes accessible from the top level of a module;

  • instances of such classes whose the result of calling __getstate__() is picklable (see section 封存類(lèi)實(shí)例 for details).

嘗試封存不能被封存的對(duì)象會(huì)拋出 PicklingError 異常,異常發(fā)生時(shí),可能有部分字節(jié)已經(jīng)被寫(xiě)入指定文件中。嘗試封存遞歸層級(jí)很深的對(duì)象時(shí),可能會(huì)超出最大遞歸層級(jí)限制,此時(shí)會(huì)拋出 RecursionError 異常,可以通過(guò) sys.setrecursionlimit() 調(diào)整遞歸層級(jí),不過(guò)請(qǐng)謹(jǐn)慎使用這個(gè)函數(shù),因?yàn)榭赡軙?huì)導(dǎo)致解釋器崩潰。

Note that functions (built-in and user-defined) are pickled by fully qualified name, not by value. 2 This means that only the function name is pickled, along with the name of the containing module and classes. Neither the function's code, nor any of its function attributes are pickled. Thus the defining module must be importable in the unpickling environment, and the module must contain the named object, otherwise an exception will be raised. 3

Similarly, classes are pickled by fully qualified name, so the same restrictions in the unpickling environment apply. Note that none of the class's code or data is pickled, so in the following example the class attribute attr is not restored in the unpickling environment:

class Foo:
    attr = 'A class attribute'

picklestring = pickle.dumps(Foo)

These restrictions are why picklable functions and classes must be defined at the top level of a module.

類(lèi)似的,在封存類(lèi)的實(shí)例時(shí),其類(lèi)體和類(lèi)數(shù)據(jù)不會(huì)跟著實(shí)例一起被封存,只有實(shí)例數(shù)據(jù)會(huì)被封存。這樣設(shè)計(jì)是有目的的,在將來(lái)修復(fù)類(lèi)中的錯(cuò)誤、給類(lèi)增加方法之后,仍然可以載入原來(lái)版本類(lèi)實(shí)例的封存數(shù)據(jù)來(lái)還原該實(shí)例。如果你準(zhǔn)備長(zhǎng)期使用一個(gè)對(duì)象,可能會(huì)同時(shí)存在較多版本的類(lèi)體,可以為對(duì)象添加版本號(hào),這樣就可以通過(guò)類(lèi)的 __setstate__() 方法將老版本轉(zhuǎn)換成新版本。

封存類(lèi)實(shí)例?

在本節(jié)中,我們描述了可用于定義、自定義和控制如何封存和解封類(lèi)實(shí)例的通用流程。

通常,使一個(gè)實(shí)例可被封存不需要附加任何代碼。Pickle 默認(rèn)會(huì)通過(guò) Python 的內(nèi)省機(jī)制獲得實(shí)例的類(lèi)及屬性。而當(dāng)實(shí)例解封時(shí),它的 __init__() 方法通常 不會(huì) 被調(diào)用。其默認(rèn)動(dòng)作是:先創(chuàng)建一個(gè)未初始化的實(shí)例,然后還原其屬性,下面的代碼展示了這種行為的實(shí)現(xiàn)機(jī)制:

def save(obj):
    return (obj.__class__, obj.__dict__)

def restore(cls, attributes):
    obj = cls.__new__(cls)
    obj.__dict__.update(attributes)
    return obj

類(lèi)可以改變默認(rèn)行為,只需定義以下一種或幾種特殊方法:

object.__getnewargs_ex__()?

對(duì)于使用第 2 版或更高版協(xié)議的 pickle,實(shí)現(xiàn)了 __getnewargs_ex__() 方法的類(lèi)可以控制在解封時(shí)傳給 __new__() 方法的參數(shù)。本方法必須返回一對(duì) (args, kwargs) 用于構(gòu)建對(duì)象,其中 args 是表示位置參數(shù)的 tuple,而 kwargs 是表示命名參數(shù)的 dict。它們會(huì)在解封時(shí)傳遞給 __new__() 方法。

如果類(lèi)的 __new__() 方法只接受關(guān)鍵字參數(shù),則應(yīng)當(dāng)實(shí)現(xiàn)這個(gè)方法。否則,為了兼容性,更推薦實(shí)現(xiàn) __getnewargs__() 方法。

在 3.6 版更改: __getnewargs_ex__() 現(xiàn)在可用于第 2 和第 3 版協(xié)議。

object.__getnewargs__()?

這個(gè)方法與上一個(gè) __getnewargs_ex__() 方法類(lèi)似,但僅支持位置參數(shù)。它要求返回一個(gè) tuple 類(lèi)型的 args,用于解封時(shí)傳遞給 __new__() 方法。

如果定義了 __getnewargs_ex__(),那么 __getnewargs__() 就不會(huì)被調(diào)用。

在 3.6 版更改: 在 Python 3.6 前,第 2、3 版協(xié)議會(huì)調(diào)用 __getnewargs__(),更高版本協(xié)議會(huì)調(diào)用 __getnewargs_ex__()。

object.__getstate__()?

Classes can further influence how their instances are pickled by overriding the method __getstate__(). It is called and the returned object is pickled as the contents for the instance, instead of a default state. There are several cases:

  • For a class that has no instance __dict__ and no __slots__, the default state is None.

  • For a class that has an instance __dict__ and no __slots__, the default state is self.__dict__.

  • For a class that has an instance __dict__ and __slots__, the default state is a tuple consisting of two dictionaries: self.__dict__, and a dictionary mapping slot names to slot values. Only slots that have a value are included in the latter.

  • For a class that has __slots__ and no instance __dict__, the default state is a tuple whose first item is None and whose second item is a dictionary mapping slot names to slot values described in the previous bullet.

在 3.11 版更改: Added the default implementation of the __getstate__() method in the object class.

object.__setstate__(state)?

當(dāng)解封時(shí),如果類(lèi)定義了 __setstate__(),就會(huì)在已解封狀態(tài)下調(diào)用它。此時(shí)不要求實(shí)例的 state 對(duì)象必須是 dict。沒(méi)有定義此方法的話,先前封存的 state 對(duì)象必須是 dict,且該 dict 內(nèi)容會(huì)在解封時(shí)賦給新實(shí)例的 __dict__。

備注

如果 __getstate__() 返回 False,那么在解封時(shí)就不會(huì)調(diào)用 __setstate__() 方法。

參考 處理有狀態(tài)的對(duì)象 一段獲取如何使用 __getstate__()__setstate__() 方法的更多信息。

備注

在解封時(shí),實(shí)例的某些方法例如 __getattr__(), __getattribute__()__setattr__() 可能會(huì)被調(diào)用。 由于這些方法可能要求某些內(nèi)部不變量為真值,因此該類(lèi)型應(yīng)當(dāng)實(shí)現(xiàn) __new__() 以建立這樣的不變量,因?yàn)楫?dāng)解封一個(gè)實(shí)例時(shí) __init__() 并不會(huì)被調(diào)用。

可以看出,其實(shí) pickle 并不直接調(diào)用上面的幾個(gè)函數(shù)。事實(shí)上,這幾個(gè)函數(shù)是復(fù)制協(xié)議的一部分,它們實(shí)現(xiàn)了 __reduce__() 這一特殊接口。復(fù)制協(xié)議提供了統(tǒng)一的接口,用于在封存或復(fù)制對(duì)象的過(guò)程中取得所需數(shù)據(jù)。4

盡管這個(gè)協(xié)議功能很強(qiáng),但是直接在類(lèi)中實(shí)現(xiàn) __reduce__() 接口容易產(chǎn)生錯(cuò)誤。因此,設(shè)計(jì)類(lèi)時(shí)應(yīng)當(dāng)盡可能的使用高級(jí)接口(比如 __getnewargs_ex__()、__getstate__()__setstate__())。后面仍然可以看到直接實(shí)現(xiàn) __reduce__() 接口的狀況,可能別無(wú)他法,可能為了獲得更好的性能,或者兩者皆有之。

object.__reduce__()?

該接口當(dāng)前定義如下。__reduce__() 方法不帶任何參數(shù),并且應(yīng)返回字符串或最好返回一個(gè)元組(返回的對(duì)象通常稱(chēng)為“reduce 值”)。

如果返回字符串,該字符串會(huì)被當(dāng)做一個(gè)全局變量的名稱(chēng)。它應(yīng)該是對(duì)象相對(duì)于其模塊的本地名稱(chēng),pickle 模塊會(huì)搜索模塊命名空間來(lái)確定對(duì)象所屬的模塊。這種行為常在單例模式使用。

如果返回的是元組,則應(yīng)當(dāng)包含 2 到 6 個(gè)元素,可選元素可以省略或設(shè)置為 None。每個(gè)元素代表的意義如下:

  • 一個(gè)可調(diào)用對(duì)象,該對(duì)象會(huì)在創(chuàng)建對(duì)象的最初版本時(shí)調(diào)用。

  • 可調(diào)用對(duì)象的參數(shù),是一個(gè)元組。如果可調(diào)用對(duì)象不接受參數(shù),必須提供一個(gè)空元組。

  • 可選元素,用于表示對(duì)象的狀態(tài),將被傳給前述的 __setstate__() 方法。 如果對(duì)象沒(méi)有此方法,則這個(gè)元素必須是字典類(lèi)型,并會(huì)被添加至 __dict__ 屬性中。

  • 可選元素,一個(gè)返回連續(xù)項(xiàng)的迭代器(而不是序列)。這些項(xiàng)會(huì)被 obj.append(item) 逐個(gè)加入對(duì)象,或被 obj.extend(list_of_items) 批量加入對(duì)象。這個(gè)元素主要用于 list 的子類(lèi),也可以用于那些正確實(shí)現(xiàn)了 append()extend() 方法的類(lèi)。(具體是使用 append() 還是 extend() 取決于 pickle 協(xié)議版本以及待插入元素的項(xiàng)數(shù),所以這兩個(gè)方法必須同時(shí)被類(lèi)支持。)

  • 可選元素,一個(gè)返回連續(xù)鍵值對(duì)的迭代器(而不是序列)。這些鍵值對(duì)將會(huì)以 obj[key] = value 的方式存儲(chǔ)于對(duì)象中。該元素主要用于 dict 子類(lèi),也可以用于那些實(shí)現(xiàn)了 __setitem__() 的類(lèi)。

  • 可選元素,一個(gè)帶有 (obj, state) 簽名的可調(diào)用對(duì)象。該可調(diào)用對(duì)象允許用戶(hù)以編程方式控制特定對(duì)象的狀態(tài)更新行為,而不是使用 obj 的靜態(tài) __setstate__() 方法。如果此處不是 None,則此可調(diào)用對(duì)象的優(yōu)先級(jí)高于 obj__setstate__()。

    3.8 新版功能: 新增了元組的第 6 項(xiàng),可選元素 (obj, state)。

object.__reduce_ex__(protocol)?

作為替代選項(xiàng),也可以實(shí)現(xiàn) __reduce_ex__() 方法。 此方法的唯一不同之處在于它應(yīng)接受一個(gè)整型參數(shù)用于指定協(xié)議版本。 如果定義了這個(gè)函數(shù),則會(huì)覆蓋 __reduce__() 的行為。 此外,__reduce__() 方法會(huì)自動(dòng)成為擴(kuò)展版方法的同義詞。 這個(gè)函數(shù)主要用于為以前的 Python 版本提供向后兼容的 reduce 值。

持久化外部對(duì)象?

為了獲取對(duì)象持久化的利益, pickle 模塊支持引用已封存數(shù)據(jù)流之外的對(duì)象。 這樣的對(duì)象是通過(guò)一個(gè)持久化 ID 來(lái)引用的,它應(yīng)當(dāng)是一個(gè)由字母數(shù)字類(lèi)字符組成的字符串 (對(duì)于第 0 版協(xié)議) 5 或是一個(gè)任意對(duì)象 (用于任意新版協(xié)議)。

pickle 模塊不提供對(duì)持久化 ID 的解析工作,它將解析工作分配給用戶(hù)定義的方法,分別是 pickler 中的 persistent_id() 方法和 unpickler 中的 persistent_load() 方法。

要通過(guò)持久化 ID 將外部對(duì)象封存,必須在 pickler 中實(shí)現(xiàn) persistent_id() 方法,該方法接受需要被封存的對(duì)象作為參數(shù),返回一個(gè) None 或返回該對(duì)象的持久化 ID。如果返回 None,該對(duì)象會(huì)被按照默認(rèn)方式封存為數(shù)據(jù)流。如果返回字符串形式的持久化 ID,則會(huì)封存這個(gè)字符串并加上一個(gè)標(biāo)記,這樣 unpickler 才能將其識(shí)別為持久化 ID。

要解封外部對(duì)象,Unpickler 必須實(shí)現(xiàn) persistent_load() 方法,接受一個(gè)持久化 ID 對(duì)象作為參數(shù)并返回一個(gè)引用的對(duì)象。

下面是一個(gè)全面的例子,展示了如何使用持久化 ID 來(lái)封存外部對(duì)象。

# Simple example presenting how persistent ID can be used to pickle
# external objects by reference.

import pickle
import sqlite3
from collections import namedtuple

# Simple class representing a record in our database.
MemoRecord = namedtuple("MemoRecord", "key, task")

class DBPickler(pickle.Pickler):

    def persistent_id(self, obj):
        # Instead of pickling MemoRecord as a regular class instance, we emit a
        # persistent ID.
        if isinstance(obj, MemoRecord):
            # Here, our persistent ID is simply a tuple, containing a tag and a
            # key, which refers to a specific record in the database.
            return ("MemoRecord", obj.key)
        else:
            # If obj does not have a persistent ID, return None. This means obj
            # needs to be pickled as usual.
            return None


class DBUnpickler(pickle.Unpickler):

    def __init__(self, file, connection):
        super().__init__(file)
        self.connection = connection

    def persistent_load(self, pid):
        # This method is invoked whenever a persistent ID is encountered.
        # Here, pid is the tuple returned by DBPickler.
        cursor = self.connection.cursor()
        type_tag, key_id = pid
        if type_tag == "MemoRecord":
            # Fetch the referenced record from the database and return it.
            cursor.execute("SELECT * FROM memos WHERE key=?", (str(key_id),))
            key, task = cursor.fetchone()
            return MemoRecord(key, task)
        else:
            # Always raises an error if you cannot return the correct object.
            # Otherwise, the unpickler will think None is the object referenced
            # by the persistent ID.
            raise pickle.UnpicklingError("unsupported persistent object")


def main():
    import io
    import pprint

    # Initialize and populate our database.
    conn = sqlite3.connect(":memory:")
    cursor = conn.cursor()
    cursor.execute("CREATE TABLE memos(key INTEGER PRIMARY KEY, task TEXT)")
    tasks = (
        'give food to fish',
        'prepare group meeting',
        'fight with a zebra',
        )
    for task in tasks:
        cursor.execute("INSERT INTO memos VALUES(NULL, ?)", (task,))

    # Fetch the records to be pickled.
    cursor.execute("SELECT * FROM memos")
    memos = [MemoRecord(key, task) for key, task in cursor]
    # Save the records using our custom DBPickler.
    file = io.BytesIO()
    DBPickler(file).dump(memos)

    print("Pickled records:")
    pprint.pprint(memos)

    # Update a record, just for good measure.
    cursor.execute("UPDATE memos SET task='learn italian' WHERE key=1")

    # Load the records from the pickle data stream.
    file.seek(0)
    memos = DBUnpickler(file, conn).load()

    print("Unpickled records:")
    pprint.pprint(memos)


if __name__ == '__main__':
    main()

Dispatch 表?

如果想對(duì)某些類(lèi)進(jìn)行自定義封存,而又不想在類(lèi)中增加用于封存的代碼,就可以創(chuàng)建帶有特殊 dispatch 表的 pickler。

copyreg 模塊的 copyreg.dispatch_table 中定義了全局 dispatch 表。因此,可以使用 copyreg.dispatch_table 修改后的副本作為自有 dispatch 表。

例如

f = io.BytesIO()
p = pickle.Pickler(f)
p.dispatch_table = copyreg.dispatch_table.copy()
p.dispatch_table[SomeClass] = reduce_SomeClass

創(chuàng)建了一個(gè)帶有自有 dispatch 表的 pickle.Pickler 實(shí)例,它可以對(duì) SomeClass 類(lèi)進(jìn)行特殊處理。另外,下列代碼

class MyPickler(pickle.Pickler):
    dispatch_table = copyreg.dispatch_table.copy()
    dispatch_table[SomeClass] = reduce_SomeClass
f = io.BytesIO()
p = MyPickler(f)

does the same but all instances of MyPickler will by default share the private dispatch table. On the other hand, the code

copyreg.pickle(SomeClass, reduce_SomeClass)
f = io.BytesIO()
p = pickle.Pickler(f)

modifies the global dispatch table shared by all users of the copyreg module.

處理有狀態(tài)的對(duì)象?

下面的示例展示了如何修改類(lèi)在封存時(shí)的行為。其中 TextReader 類(lèi)打開(kāi)了一個(gè)文本文件,每次調(diào)用其 readline() 方法則返回行號(hào)和該行的字符。 在封存這個(gè) TextReader 的實(shí)例時(shí),除了 文件對(duì)象,其他屬性都會(huì)被保存。 當(dāng)解封實(shí)例時(shí),需要重新打開(kāi)文件,然后從上次的位置開(kāi)始繼續(xù)讀取。實(shí)現(xiàn)這些功能需要實(shí)現(xiàn) __setstate__()__getstate__() 方法。

class TextReader:
    """Print and number lines in a text file."""

    def __init__(self, filename):
        self.filename = filename
        self.file = open(filename)
        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):
        # Copy the object's state from self.__dict__ which contains
        # all our instance attributes. Always use the dict.copy()
        # method to avoid modifying the original state.
        state = self.__dict__.copy()
        # Remove the unpicklable entries.
        del state['file']
        return state

    def __setstate__(self, state):
        # Restore instance attributes (i.e., filename and lineno).
        self.__dict__.update(state)
        # Restore the previously opened file's state. To do so, we need to
        # reopen it and read from it until the line count is restored.
        file = open(self.filename)
        for _ in range(self.lineno):
            file.readline()
        # Finally, save the file.
        self.file = file

使用方法如下所示:

>>>
>>> reader = TextReader("hello.txt")
>>> reader.readline()
'1: Hello world!'
>>> reader.readline()
'2: I am line number two.'
>>> new_reader = pickle.loads(pickle.dumps(reader))
>>> new_reader.readline()
'3: Goodbye!'

類(lèi)型,函數(shù)和其他對(duì)象的自定義歸約?

3.8 新版功能.

有時(shí),dispatch_table 可能不夠靈活。 特別是當(dāng)我們想要基于對(duì)象類(lèi)型以外的其他規(guī)則來(lái)對(duì)封存進(jìn)行定制,或是當(dāng)我們想要對(duì)函數(shù)和類(lèi)的封存進(jìn)行定制的時(shí)候。

對(duì)于那些情況,可能要基于 Pickler 類(lèi)進(jìn)行子類(lèi)化并實(shí)現(xiàn) reducer_override() 方法。 此方法可返回任意的歸約元組 (參見(jiàn) __reduce__())。 它也可以選擇返回 NotImplemented 來(lái)回退到傳統(tǒng)行為。

如果同時(shí)定義了 dispatch_tablereducer_override(),則 reducer_override() 方法具有優(yōu)先權(quán)。

備注

出于性能理由,可能不會(huì)為以下對(duì)象調(diào)用 reducer_override(): None, True, False, 以及 int, float, bytes, str, dict, set, frozenset, listtuple 的具體實(shí)例。

以下是一個(gè)簡(jiǎn)單的例子,其中我們?cè)试S封存并重新構(gòu)建一個(gè)給定的類(lèi):

import io
import pickle

class MyClass:
    my_attribute = 1

class MyPickler(pickle.Pickler):
    def reducer_override(self, obj):
        """Custom reducer for MyClass."""
        if getattr(obj, "__name__", None) == "MyClass":
            return type, (obj.__name__, obj.__bases__,
                          {'my_attribute': obj.my_attribute})
        else:
            # For any other object, fallback to usual reduction
            return NotImplemented

f = io.BytesIO()
p = MyPickler(f)
p.dump(MyClass)

del MyClass

unpickled_class = pickle.loads(f.getvalue())

assert isinstance(unpickled_class, type)
assert unpickled_class.__name__ == "MyClass"
assert unpickled_class.my_attribute == 1

外部緩沖區(qū)?

3.8 新版功能.

在某些場(chǎng)景中,pickle 模塊會(huì)被用來(lái)傳輸海量的數(shù)據(jù)。 因此,最小化內(nèi)存復(fù)制次數(shù)以保證性能和節(jié)省資源是很重要的。 但是 pickle 模塊的正常運(yùn)作會(huì)將圖類(lèi)對(duì)象結(jié)構(gòu)轉(zhuǎn)換為字節(jié)序列流,因此在本質(zhì)上就要從封存流中來(lái)回復(fù)制數(shù)據(jù)。

如果 provider (待傳輸對(duì)象類(lèi)型的實(shí)現(xiàn)) 和 consumer (通信系統(tǒng)的實(shí)現(xiàn)) 都支持 pickle 第 5 版或更高版本所提供的外部傳輸功能,則此約束可以被撤銷(xiāo)。

提供方 API?

大的待封存數(shù)據(jù)對(duì)象必須實(shí)現(xiàn)協(xié)議 5 及以上版本專(zhuān)屬的 __reduce_ex__() 方法,該方法將為任意大的數(shù)據(jù)返回一個(gè) PickleBuffer 實(shí)例(而不是 bytes 對(duì)象等)。

PickleBuffer 對(duì)象會(huì) 表明 底層緩沖區(qū)可被用于外部數(shù)據(jù)傳輸。 那些對(duì)象仍將保持與 pickle 模塊的正常用法兼容。 但是,使用方也可以選擇告知 pickle 它們將自行處理那些緩沖區(qū)。

使用方 API?

當(dāng)序列化一個(gè)對(duì)象圖時(shí),通信系統(tǒng)可以啟用對(duì)所生成 PickleBuffer 對(duì)象的定制處理。

發(fā)送端需要傳遞 buffer_callback 參數(shù)到 Pickler (或是到 dump()dumps() 函數(shù)),該回調(diào)函數(shù)將在封存對(duì)象圖時(shí)附帶每個(gè)所生成的 PickleBuffer 被調(diào)用。 由 buffer_callback 所累積的緩沖區(qū)的數(shù)據(jù)將不會(huì)被拷貝到 pickle 流,而是僅插入一個(gè)簡(jiǎn)單的標(biāo)記。

接收端需要傳遞 buffers 參數(shù)到 Unpickler (或是到 load()loads() 函數(shù)),其值是一個(gè)由緩沖區(qū)組成的可迭代對(duì)象,它會(huì)被傳遞給 buffer_callback。 該可迭代對(duì)象應(yīng)當(dāng)按其被傳遞給 buffer_callback 時(shí)的順序產(chǎn)生緩沖區(qū)。 這些緩沖區(qū)將提供對(duì)象重構(gòu)造器所期望的數(shù)據(jù),對(duì)這些數(shù)據(jù)的封存產(chǎn)生了原本的 PickleBuffer 對(duì)象。

在發(fā)送端和接受端之間,通信系統(tǒng)可以自由地實(shí)現(xiàn)它自己用于外部緩沖區(qū)的傳輸機(jī)制。 潛在的優(yōu)化包括使用共享內(nèi)存或基于特定數(shù)據(jù)類(lèi)型的壓縮等。

示例?

下面是一個(gè)小例子,在其中我們實(shí)現(xiàn)了一個(gè) bytearray 的子類(lèi),能夠用于外部緩沖區(qū)封存:

class ZeroCopyByteArray(bytearray):

    def __reduce_ex__(self, protocol):
        if protocol >= 5:
            return type(self)._reconstruct, (PickleBuffer(self),), None
        else:
            # PickleBuffer is forbidden with pickle protocols <= 4.
            return type(self)._reconstruct, (bytearray(self),)

    @classmethod
    def _reconstruct(cls, obj):
        with memoryview(obj) as m:
            # Get a handle over the original buffer object
            obj = m.obj
            if type(obj) is cls:
                # Original buffer object is a ZeroCopyByteArray, return it
                # as-is.
                return obj
            else:
                return cls(obj)

重構(gòu)造器 (_reconstruct 類(lèi)方法) 會(huì)在緩沖區(qū)的提供對(duì)象具有正確類(lèi)型時(shí)返回該對(duì)象。 在此小示例中這是模擬零拷貝行為的便捷方式。

在使用方,我們可以按通常方式封存那些對(duì)象,它們?cè)诜葱蛄谢瘯r(shí)將提供原始對(duì)象的一個(gè)副本:

b = ZeroCopyByteArray(b"abc")
data = pickle.dumps(b, protocol=5)
new_b = pickle.loads(data)
print(b == new_b)  # True
print(b is new_b)  # False: a copy was made

但是如果我們傳入 buffer_callback 然后在反序列化時(shí)給回累積的緩沖區(qū),我們就能夠取回原始對(duì)象:

b = ZeroCopyByteArray(b"abc")
buffers = []
data = pickle.dumps(b, protocol=5, buffer_callback=buffers.append)
new_b = pickle.loads(data, buffers=buffers)
print(b == new_b)  # True
print(b is new_b)  # True: no copy was made

這個(gè)例子受限于 bytearray 會(huì)自行分配內(nèi)存這一事實(shí):你無(wú)法基于另一個(gè)對(duì)象的內(nèi)存創(chuàng)建 bytearray 的實(shí)例。 但是,第三方數(shù)據(jù)類(lèi)型例如 NumPy 數(shù)組則沒(méi)有這種限制,允許在單獨(dú)進(jìn)程或系統(tǒng)間傳輸時(shí)使用零拷貝的封存(或是盡可能少地拷貝) 。

參見(jiàn)

PEP 574 -- 帶有外部數(shù)據(jù)緩沖區(qū)的 pickle 協(xié)議 5

限制全局變量?

默認(rèn)情況下,解封將會(huì)導(dǎo)入在 pickle 數(shù)據(jù)中找到的任何類(lèi)或函數(shù)。 對(duì)于許多應(yīng)用來(lái)說(shuō),此行為是不可接受的,因?yàn)樗鼤?huì)允許解封器導(dǎo)入并發(fā)起調(diào)用任意代碼。 只須考慮當(dāng)這個(gè)手工構(gòu)建的 pickle 數(shù)據(jù)流被加載時(shí)會(huì)做什么:

>>>
>>> import pickle
>>> pickle.loads(b"cos\nsystem\n(S'echo hello world'\ntR.")
hello world
0

在這個(gè)例子里,解封器導(dǎo)入 os.system() 函數(shù)然后應(yīng)用字符串參數(shù) "echo hello world"。 雖然這個(gè)例子不具攻擊性,但是不難想象別人能夠通過(guò)此方式對(duì)你的系統(tǒng)造成損害。

出于這樣的理由,你可能會(huì)希望通過(guò)定制 Unpickler.find_class() 來(lái)控制要解封的對(duì)象。 與其名稱(chēng)所提示的不同,Unpickler.find_class() 會(huì)在執(zhí)行對(duì)任何全局對(duì)象(例如一個(gè)類(lèi)或一個(gè)函數(shù))的請(qǐng)求時(shí)被調(diào)用。 因此可以完全禁止全局對(duì)象或是將它們限制在一個(gè)安全的子集中。

下面的例子是一個(gè)解封器,它只允許某一些安全的來(lái)自 builtins 模塊的類(lèi)被加載:

import builtins
import io
import pickle

safe_builtins = {
    'range',
    'complex',
    'set',
    'frozenset',
    'slice',
}

class RestrictedUnpickler(pickle.Unpickler):

    def find_class(self, module, name):
        # Only allow safe classes from builtins.
        if module == "builtins" and name in safe_builtins:
            return getattr(builtins, name)
        # Forbid everything else.
        raise pickle.UnpicklingError("global '%s.%s' is forbidden" %
                                     (module, name))

def restricted_loads(s):
    """Helper function analogous to pickle.loads()."""
    return RestrictedUnpickler(io.BytesIO(s)).load()

A sample usage of our unpickler working as intended:

>>>
>>> restricted_loads(pickle.dumps([1, 2, range(15)]))
[1, 2, range(0, 15)]
>>> restricted_loads(b"cos\nsystem\n(S'echo hello world'\ntR.")
Traceback (most recent call last):
  ...
pickle.UnpicklingError: global 'os.system' is forbidden
>>> restricted_loads(b'cbuiltins\neval\n'
...                  b'(S\'getattr(__import__("os"), "system")'
...                  b'("echo hello world")\'\ntR.')
Traceback (most recent call last):
  ...
pickle.UnpicklingError: global 'builtins.eval' is forbidden

正如我們這個(gè)例子所顯示的,對(duì)于允許解封的對(duì)象你必須要保持謹(jǐn)慎。 因此如果要保證安全,你可以考慮其他選擇例如 xmlrpc.client 中的編組 API 或是第三方解決方案。

性能?

較新版本的 pickle 協(xié)議(第 2 版或更高)具有針對(duì)某些常見(jiàn)特性和內(nèi)置類(lèi)型的高效二進(jìn)制編碼格式。 此外,pickle 模塊還擁有一個(gè)以 C 編寫(xiě)的透明優(yōu)化器。

例子?

對(duì)于最簡(jiǎn)單的代碼,請(qǐng)使用 dump()load() 函數(shù)。

import pickle

# An arbitrary collection of objects supported by pickle.
data = {
    'a': [1, 2.0, 3+4j],
    'b': ("character string", b"byte string"),
    'c': {None, True, False}
}

with open('data.pickle', 'wb') as f:
    # Pickle the 'data' dictionary using the highest protocol available.
    pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)

以下示例讀取之前封存的數(shù)據(jù)。

import pickle

with open('data.pickle', 'rb') as f:
    # The protocol version used is detected automatically, so we do not
    # have to specify it.
    data = pickle.load(f)

參見(jiàn)

模塊 copyreg

為擴(kuò)展類(lèi)型提供 pickle 接口所需的構(gòu)造函數(shù)。

模塊 pickletools

用于處理和分析已封存數(shù)據(jù)的工具。

模塊 shelve

帶索引的數(shù)據(jù)庫(kù),用于存放對(duì)象,使用了 pickle 模塊。

模塊 copy

淺層 (shallow) 和深層 (deep) 復(fù)制對(duì)象操作

模塊 marshal

高效地序列化內(nèi)置類(lèi)型的數(shù)據(jù)。

備注

1

不要把它與 marshal 模塊混淆。

2

這就是為什么 lambda 函數(shù)不可以被封存:所有的匿名函數(shù)都有同一個(gè)名字:<lambda>。

3

拋出的異常有可能是 ImportErrorAttributeError,也可能是其他異常。

4

copy 模塊使用這一協(xié)議實(shí)現(xiàn)淺層 (shallow) 和深層 (deep) 復(fù)制操作。

5

The limitation on alphanumeric characters is due to the fact that persistent IDs in protocol 0 are delimited by the newline character. Therefore if any kind of newline characters occurs in persistent IDs, the resulting pickled data will become unreadable.