新聞中心
pickle —- python 對象序列化
源代碼: Lib/pickle.py

秦皇島ssl適用于網(wǎng)站、小程序/APP、API接口等需要進(jìn)行數(shù)據(jù)傳輸應(yīng)用場景,ssl證書未來市場廣闊!成為成都創(chuàng)新互聯(lián)公司的ssl證書銷售渠道,可以享受市場價格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:18982081108(備注:SSL證書合作)期待與您的合作!
模塊 pickle 實現(xiàn)了對一個 Python 對象結(jié)構(gòu)的二進(jìn)制序列化和反序列化。 “pickling” 是將 Python 對象及其所擁有的層次結(jié)構(gòu)轉(zhuǎn)化為一個字節(jié)流的過程,而 “unpickling” 是相反的操作,會將(來自一個 binary file 或者 bytes-like object 的)字節(jié)流轉(zhuǎn)化回一個對象層次結(jié)構(gòu)。 pickling(和 unpickling)也被稱為“序列化”, “編組” 1 或者 “平面化”。而為了避免混亂,此處采用術(shù)語 “封存 (pickling)” 和 “解封 (unpickling)”。
警告
pickle 模塊 并不安全。 你只應(yīng)該對你信任的數(shù)據(jù)進(jìn)行 unpickle 操作。
構(gòu)建惡意的 pickle 數(shù)據(jù)來 在解封時執(zhí)行任意代碼 是可能的。 絕對不要對不信任來源的數(shù)據(jù)和可能被篡改過的數(shù)據(jù)進(jìn)行解封。
請考慮使用 hmac 來對數(shù)據(jù)進(jìn)行簽名,確保數(shù)據(jù)沒有被篡改。
在你處理不信任數(shù)據(jù)時,更安全的序列化格式如 json 可能更為適合。參見 與 json 模塊的比較 。
與其他 Python 模塊間的關(guān)系
與 marshal 間的關(guān)系
Python 有一個更原始的序列化模塊稱為 marshal,但一般地 pickle 應(yīng)該是序列化 Python 對象時的首選。marshal 存在主要是為了支持 Python 的 .pyc 文件.
pickle 模塊與 marshal 在如下幾方面顯著地不同:
-
pickle 模塊會跟蹤已被序列化的對象,所以該對象之后再次被引用時不會再次被序列化。marshal 不會這么做。
這隱含了遞歸對象和共享對象。遞歸對象指包含對自己的引用的對象。這種對象并不會被 marshal 接受,并且實際上嘗試 marshal 遞歸對象會讓你的 Python 解釋器崩潰。對象共享發(fā)生在對象層級中存在多處引用同一對象時。pickle 只會存儲這些對象一次,并確保其他的引用指向同一個主副本。共享對象將保持共享,這可能對可變對象非常重要。
-
marshal 不能被用于序列化用戶定義類及其實例。pickle 能夠透明地存儲并保存類實例,然而此時類定義必須能夠從與被存儲時相同的模塊被引入。
-
同樣用于序列化的 marshal 格式不保證數(shù)據(jù)能移植到不同的 Python 版本中。因為它的主要任務(wù)是支持
.pyc文件,必要時會以破壞向后兼容的方式更改這種序列化格式,為此 Python 的實現(xiàn)者保留了更改格式的權(quán)利。pickle 序列化格式可以在不同版本的 Python 中實現(xiàn)向后兼容,前提是選擇了合適的 pickle 協(xié)議。如果你的數(shù)據(jù)要在 Python 2 與 Python 3 之間跨越傳遞,封存和解封的代碼在 2 和 3 之間也是不同的。
與 json 模塊的比較
There are fundamental differences between the pickle protocols and JSON (JavaScript Object Notation):
-
JSON 是一個文本序列化格式(它輸出 unicode 文本,盡管在大多數(shù)時候它會接著以
utf-8編碼),而 pickle 是一個二進(jìn)制序列化格式; -
JSON 是我們可以直觀閱讀的,而 pickle 不是;
-
JSON是可互操作的,在Python系統(tǒng)之外廣泛使用,而pickle則是Python專用的;
-
默認(rèn)情況下,JSON 只能表示 Python 內(nèi)置類型的子集,不能表示自定義的類;但 pickle 可以表示大量的 Python 數(shù)據(jù)類型(可以合理使用 Python 的對象內(nèi)省功能自動地表示大多數(shù)類型,復(fù)雜情況可以通過實現(xiàn) specific object APIs 來解決)。
-
不像pickle,對一個不信任的JSON進(jìn)行反序列化的操作本身不會造成任意代碼執(zhí)行漏洞。
參見
json 模塊:一個允許JSON序列化和反序列化的標(biāo)準(zhǔn)庫模塊
數(shù)據(jù)流格式
pickle 所使用的數(shù)據(jù)格式僅可用于 Python。這樣做的好處是沒有外部標(biāo)準(zhǔn)給該格式強(qiáng)加限制,比如 JSON 或 XDR(不能表示共享指針)標(biāo)準(zhǔn);但這也意味著非 Python 程序可能無法重新讀取 pickle 封存的 Python 對象。
默認(rèn)情況下,pickle 格式使用相對緊湊的二進(jìn)制來存儲。如果需要讓文件更小,可以高效地 壓縮 由 pickle 封存的數(shù)據(jù)。
pickletools 模塊包含了相應(yīng)的工具用于分析 pickle 生成的數(shù)據(jù)流。pickletools 源碼中包含了對 pickle 協(xié)議使用的操作碼的大量注釋。
當(dāng)前共有 6 種不同的協(xié)議可用于封存操作。 使用的協(xié)議版本越高,讀取所生成 pickle 對象所需的 Python 版本就要越新。
-
v0 版協(xié)議是原始的“人類可讀”協(xié)議,并且向后兼容早期版本的 Python。
-
v1 版協(xié)議是較早的二進(jìn)制格式,它也與早期版本的 Python 兼容。
-
第 2 版協(xié)議是在 Python 2.3 中引入的。 它為 新式類 提供了更高效的封存機(jī)制。 請參考 PEP 307 了解第 2 版協(xié)議帶來的改進(jìn)的相關(guān)信息。
-
v3 版協(xié)議是在 Python 3.0 中引入的。 它顯式地支持 bytes 字節(jié)對象,不能使用 Python 2.x 解封。這是 Python 3.0-3.7 的默認(rèn)協(xié)議。
-
v4 版協(xié)議添加于 Python 3.4。它支持存儲非常大的對象,能存儲更多種類的對象,還包括一些針對數(shù)據(jù)格式的優(yōu)化。它是Python 3.8使用的默認(rèn)協(xié)議。有關(guān)第 4 版協(xié)議帶來改進(jìn)的信息,請參閱 PEP 3154。
-
第 5 版協(xié)議是在 Python 3.8 中加入的。 它增加了對帶外數(shù)據(jù)的支持,并可加速帶內(nèi)數(shù)據(jù)處理。 請參閱 PEP 574 了解第 5 版協(xié)議所帶來的改進(jìn)的詳情。
備注
序列化是一種比持久化更底層的概念,雖然 pickle 讀取和寫入的是文件對象,但它不處理持久對象的命名問題,也不處理對持久對象的并發(fā)訪問(甚至更復(fù)雜)的問題。pickle 模塊可以將復(fù)雜對象轉(zhuǎn)換為字節(jié)流,也可以將字節(jié)流轉(zhuǎn)換為具有相同內(nèi)部結(jié)構(gòu)的對象。處理這些字節(jié)流最常見的做法是將它們寫入文件,但它們也可以通過網(wǎng)絡(luò)發(fā)送或存儲在數(shù)據(jù)庫中。shelve 模塊提供了一個簡單的接口,用于在 DBM 類型的數(shù)據(jù)庫文件上封存和解封對象。
模塊接口
要序列化某個包含層次結(jié)構(gòu)的對象,只需調(diào)用 dumps() 函數(shù)即可。同樣,要反序列化數(shù)據(jù)流,可以調(diào)用 loads() 函數(shù)。但是,如果要對序列化和反序列化加以更多的控制,可以分別創(chuàng)建 Pickler 或 Unpickler 對象。
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 模塊提供了以下方法,讓封存過程更加方便:
pickle.dump(obj, file, protocol=None, **, fix_imports=True, buffer_callback=None*)
將對象 obj 封存以后的對象寫入已打開的 file object file。它等同于 Pickler(file, protocol).dump(obj)。
參數(shù) file、protocol、fix_imports 和 buffer_callback 的含義與它們在 Pickler 的構(gòu)造函數(shù)中的含義相同。
在 3.8 版更改: 加入了 buffer_callback 參數(shù)。
pickle.dumps(obj, protocol=None, **, fix_imports=True, buffer_callback=None*)
將 obj 封存以后的對象作為 bytes 類型直接返回,而不是將其寫入到文件。
參數(shù) protocol、fix_imports 和 buffer_callback 的含義與它們在 Pickler 的構(gòu)造函數(shù)中的含義相同。
在 3.8 版更改: 加入了 buffer_callback 參數(shù)。
pickle.load(file, **, fix_imports=True, encoding=’ASCII’, errors=’strict’, buffers=None*)
從已打開的 file object 文件 中讀取封存后的對象,重建其中特定對象的層次結(jié)構(gòu)并返回。它相當(dāng)于 Unpickler(file).load()。
Pickle 協(xié)議版本是自動檢測出來的,所以不需要參數(shù)來指定協(xié)議。封存對象以外的其他字節(jié)將被忽略。
參數(shù) file、fix_imports、encoding、errors、strict 和 buffers 的含義與它們在 Unpickler 的構(gòu)造函數(shù)中的含義相同。
在 3.8 版更改: 加入了 buffers 參數(shù)。
pickle.loads(data, /, **, fix_imports=True, encoding=’ASCII’, errors=’strict’, buffers=None*)
重建并返回一個對象的封存表示形式 data 的對象層級結(jié)構(gòu)。 data 必須為 bytes-like object。
Pickle 協(xié)議版本是自動檢測出來的,所以不需要參數(shù)來指定協(xié)議。封存對象以外的其他字節(jié)將被忽略。
參數(shù) fix_imports, encoding, errors, strict 和 buffers 的含義與在 Unpickler 構(gòu)造器中的含義相同。
在 3.8 版更改: 加入了 buffers 參數(shù)。
pickle 模塊定義了以下 3 個異常:
exception pickle.PickleError
其他 pickle 異常的基類。它是 Exception 的一個子類。
exception pickle.PicklingError
當(dāng) Pickler 遇到無法解封的對象時拋出此錯誤。它是 PickleError 的子類。
參考 可以被封存/解封的對象 來了解哪些對象可以被封存。
exception pickle.UnpicklingError
當(dāng)解封出錯時拋出此異常,例如數(shù)據(jù)損壞或?qū)ο蟛话踩?。它?PickleError 的子類。
注意,解封時可能還會拋出其他異常,包括(但不限于) AttributeError、EOFError、ImportError 和 IndexError。
pickle 模塊包含了 3 個類,Pickler、Unpickler 和 PickleBuffer:
class pickle.Pickler(file, protocol=None, **, fix_imports=True, buffer_callback=None*)
它接受一個二進(jìn)制文件用于寫入 pickle 數(shù)據(jù)流。
可選參數(shù) protocol 是一個整數(shù),告知 pickler 使用指定的協(xié)議,可選擇的協(xié)議范圍從 0 到 HIGHEST_PROTOCOL。如果沒有指定,這一參數(shù)默認(rèn)值為 DEFAULT_PROTOCOL。指定一個負(fù)數(shù)就相當(dāng)于指定 HIGHEST_PROTOCOL。
參數(shù) file 必須有一個 write() 方法,該 write() 方法要能接收字節(jié)作為其唯一參數(shù)。因此,它可以是一個打開的磁盤文件(用于寫入二進(jìn)制內(nèi)容),也可以是一個 io.BytesIO 實例,也可以是滿足這一接口的其他任何自定義對象。
如果 fix_imports 為 True 且 protocol 小于 3,pickle 將嘗試將 Python 3 中的新名稱映射到 Python 2 中的舊模塊名稱,因此 Python 2 也可以讀取封存的數(shù)據(jù)流。
如果 buffer_callback 為 None(默認(rèn)情況),緩沖區(qū)視圖(buffer view)將會作為 pickle 流的一部分被序列化到 file 中。
如果 buffer_callback 不為 None,那它可以用緩沖區(qū)視圖調(diào)用任意次。如果某次調(diào)用返回了 False 值(例如 None),則給定的緩沖區(qū)是 帶外的,否則緩沖區(qū)是帶內(nèi)的(例如保存在了 pickle 流里面)。
如果 buffer_callback 不是 None 且 protocol 是 None 或小于 5,就會出錯。
在 3.8 版更改: 加入了 buffer_callback 參數(shù)。
-
dump(obj)
將 obj 封存后的內(nèi)容寫入已打開的文件對象,該文件對象已經(jīng)在構(gòu)造函數(shù)中指定。
-
persistent_id(obj)
默認(rèn)無動作,子類繼承重載時使用。
如果 persistent_id() 返回
None,obj 會被照常 pickle。如果返回其他值,Pickler 會將這個函數(shù)的返回值作為 obj 的持久化 ID(Pickler 本應(yīng)得到序列化數(shù)據(jù)流并將其寫入文件,若此函數(shù)有返回值,則得到此函數(shù)的返回值并寫入文件)。這個持久化 ID 的解釋應(yīng)當(dāng)定義在 Unpickler.persistent_load() 中(該方法定義還原對象的過程,并返回得到的對象)。注意,persistent_id() 的返回值本身不能擁有持久化 ID。參閱 持久化外部對象 獲取詳情和使用示例。
-
dispatch_table
Pickler 對象的 dispatch 表是 copyreg.pickle() 中用到的 reduction 函數(shù) 的注冊。dispatch 表本身是一個 class 到其 reduction 函數(shù)的映射鍵值對。一個 reduction 函數(shù)只接受一個參數(shù),就是其關(guān)聯(lián)的 class,函數(shù)行為應(yīng)當(dāng)遵守
__reduce__()接口規(guī)范。Pickler 對象默認(rèn)并沒有 dispatch_table 屬性,該對象默認(rèn)使用 copyreg 模塊中定義的全局 dispatch 表。如果要為特定 Pickler 對象自定義序列化過程,可以將 dispatch_table 屬性設(shè)置為類字典對象(dict-like object)。另外,如果 Pickler 的子類設(shè)置了 dispatch_table 屬性,則該子類的實例會使用這個表作為默認(rèn)的 dispatch 表。
參閱 Dispatch 表 獲取使用示例。
3.3 新版功能.
-
reducer_override(obj)
可以在 Pickler 的子類中定義的特殊 reducer。此方法的優(yōu)先級高于 dispatch_table 中的任何 reducer。它應(yīng)該與
__reduce__()方法遵循相同的接口,它也可以返回NotImplemented,這將使用 dispatch_table 里注冊的 reducer 來封存obj。參閱 類型,函數(shù)和其他對象的自定義歸約 獲取詳細(xì)的示例。
3.8 新版功能.
-
fast
已棄用。設(shè)為 True 則啟用快速模式??焖倌J浇昧恕皞渫洝?(memo) 的使用,即不生成多余的 PUT 操作碼來加快封存過程。不應(yīng)將其與自指 (self-referential) 對象一起使用,否則將導(dǎo)致 Pickler 無限遞歸。
如果需要進(jìn)一步提高 pickle 的壓縮率,請使用 pickletools.optimize()。
class pickle.Unpickler(file, **, fix_imports=True, encoding=’ASCII’, errors=’strict’, buffers=None*)
它接受一個二進(jìn)制文件用于讀取 pickle 數(shù)據(jù)流。
Pickle 協(xié)議版本是自動檢測出來的,所以不需要參數(shù)來指定協(xié)議。
參數(shù) file 必須有三個方法,read() 方法接受一個整數(shù)參數(shù),readinto() 方法接受一個緩沖區(qū)作為參數(shù),readline() 方法不需要參數(shù),這與 io.BufferedIOBase 里定義的接口是相同的。因此 file 可以是一個磁盤上用于二進(jìn)制讀取的文件,也可以是一個 io.BytesIO 實例,也可以是滿足這一接口的其他任何自定義對象。
可選的參數(shù)是 fix_imports, encoding 和 errors,用于控制由Python 2 生成的 pickle 流的兼容性。如果 fix_imports 為 True,則 pickle 將嘗試將舊的 Python 2 名稱映射到 Python 3 中對應(yīng)的新名稱。encoding 和 errors 參數(shù)告訴 pickle 如何解碼 Python 2 存儲的 8 位字符串實例;這兩個參數(shù)默認(rèn)分別為 ‘ASCII’ 和 ‘strict’。encoding 參數(shù)可置為 ‘bytes’ 來將這些 8 位字符串實例讀取為字節(jié)對象。讀取 NumPy array 和 Python 2 存儲的 datetime、date 和 time 實例時,請使用 encoding='latin1'。
如果 buffers 為 None(默認(rèn)值),則反序列化所需的所有數(shù)據(jù)都必須包含在 pickle 流中。這意味著在實例化 Pickler 時(或調(diào)用 dump() 或 dumps() 時),參數(shù) buffer_callback 為 None。
如果 buffers 不為 None,則每次 pickle 流引用 帶外 緩沖區(qū)視圖時,消耗的對象都應(yīng)該是可迭代的啟用緩沖區(qū)的對象。這樣的緩沖區(qū)應(yīng)該按順序地提供給 Pickler 對象的 buffer_callback 方法。
在 3.8 版更改: 加入了 buffers 參數(shù)。
-
load()
從構(gòu)造函數(shù)中指定的文件對象里讀取封存好的對象,重建其中特定對象的層次結(jié)構(gòu)并返回。封存對象以外的其他字節(jié)將被忽略。
-
persistent_load(pid)
默認(rèn)拋出 UnpicklingError 異常。
如果定義了此方法,persistent_load() 應(yīng)當(dāng)返回持久化 ID pid 所指定的對象。 如果遇到無效的持久化 ID,則應(yīng)當(dāng)引發(fā) UnpicklingError。
參閱 持久化外部對象 獲取詳情和使用示例。
-
find_class(module, name)
如有必要,導(dǎo)入 module 模塊并返回其中名叫 name 的對象,其中 module 和 name 參數(shù)都是 str 對象。注意,不要被這個函數(shù)的名字迷惑, find_class() 同樣可以用來導(dǎo)入函數(shù)。
子類可以重載此方法,來控制加載對象的類型和加載對象的方式,從而盡可能降低安全風(fēng)險。參閱 限制全局變量 獲取更詳細(xì)的信息。
引發(fā)一個 審計事件
pickle.find_class附帶參數(shù)module、name。
class pickle.PickleBuffer(buffer)
緩沖區(qū)的包裝器 (wrapper),緩沖區(qū)中包含著可封存的數(shù)據(jù)。buffer 必須是一個 buffer-providing 對象,比如 bytes-like object 或多維數(shù)組。
PickleBuffer 本身就可以生成緩沖區(qū)對象,因此可以將其傳遞給需要緩沖區(qū)生成器的其他 API,比如 memoryview。
PickleBuffer 對象只能用 pickle 版本 5 及以上協(xié)議進(jìn)行序列化。它們符合 帶外序列化 的條件。
3.8 新版功能.
-
raw()
返回該緩沖區(qū)底層內(nèi)存區(qū)域的 memoryview。 返回的對象是一維的、C 連續(xù)布局的 memoryview,格式為
B(無符號字節(jié))。 如果緩沖區(qū)既不是 C 連續(xù)布局也不是 Fortran 連續(xù)布局的,則拋出 BufferError 異常。 -
release()
釋放由 PickleBuffer 占用的底層緩沖區(qū)。
可以被封存/解封的對象
下列類型可以被封存:
-
None,True和False; -
整數(shù)、浮點數(shù)、復(fù)數(shù);
-
字符串、字節(jié)串、字節(jié)數(shù)組;
-
只包含可封存對象的元組、列表、集合和字典;
-
可在模塊最高層級上訪問的(內(nèi)置與用戶自定義的)函數(shù)(使用 def,而不是使用 lambda 定義);
-
可在模塊最高層級上訪問的類;
-
instances of such classes whose the result of calling
__getstate__()is picklable (see section 封存類實例 for details).
嘗試封存不能被封存的對象會拋出 PicklingError 異常,異常發(fā)生時,可能有部分字節(jié)已經(jīng)被寫入指定文件中。嘗試封存遞歸層級很深的對象時,可能會超出最大遞歸層級限制,此時會拋出 RecursionError 異常,可以通過 sys.setrecursionlimit() 調(diào)整遞歸層級,不過請謹(jǐn)慎使用這個函數(shù),因為可能會導(dǎo)致解釋器崩潰。
請注意(內(nèi)置與用戶自定義的)函數(shù)是按完整 qualified name,而不是按值來封存的。 2 這意味著只會封存函數(shù)名稱,以及包含它的模塊和類名稱。 函數(shù)的代碼,以及函數(shù)的屬性都不會被封存。 因而定義它的模塊在解封環(huán)境中必須可以被導(dǎo)入,并且模塊必須包含所命名的對象,否則將會引發(fā)異常。 3
類似地,類也是按完整限定名稱來封存的,因此在解封環(huán)境中也會應(yīng)用相同的限制。 請注意類的代碼或數(shù)據(jù)都不會被封存,因此在下面的示例中類屬性 attr 不會在解封環(huán)境中被恢復(fù):
class Foo:attr = 'A class attribute'picklestring = pickle.dumps(Foo)
這些限制決定了為什么可封存的函數(shù)和類必須在一個模塊的最高層級上定義。
類似的,在封存類的實例時,其類體和類數(shù)據(jù)不會跟著實例一起被封存,只有實例數(shù)據(jù)會被封存。這樣設(shè)計是有目的的,在將來修復(fù)類中的錯誤、給類增加方法之后,仍然可以載入原來版本類實例的封存數(shù)據(jù)來還原該實例。如果你準(zhǔn)備長期使用一個對象,可能會同時存在較多版本的類體,可以為對象添加版本號,這樣就可以通過類的 __setstate__() 方法將老版本轉(zhuǎn)換成新版本。
封存類實例
在本節(jié)中,我們描述了可用于定義、自定義和控制如何封存和解封類實例的通用流程。
通常,使一個實例可被封存不需要附加任何代碼。Pickle 默認(rèn)會通過 Python 的內(nèi)省機(jī)制獲得實例的類及屬性。而當(dāng)實例解封時,它的 __init__() 方法通常 不會 被調(diào)用。其默認(rèn)動作是:先創(chuàng)建一個未初始化的實例,然后還原其屬性,下面的代碼展示了這種行為的實現(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
類可以改變默認(rèn)行為,只需定義以下一種或幾種特殊方法:
object.__getnewargs_ex__()
對于使用第 2 版或更高版協(xié)議的 pickle,實現(xiàn)了 __getnewargs_ex__() 方法的類可以控制在解封時傳給 __new__() 方法的參數(shù)。本方法必須返回一對 (args, kwargs) 用于構(gòu)建對象,其中 args 是表示位置參數(shù)的 tuple,而 kwargs 是表示命名參數(shù)的 dict。它們會在解封時傳遞給 __new__() 方法。
如果類的 __new__() 方法只接受關(guān)鍵字參數(shù),則應(yīng)當(dāng)實現(xiàn)這個方法。否則,為了兼容性,更推薦實現(xiàn) __getnewargs__() 方法。
在 3.6 版更改: __getnewargs_ex__() 現(xiàn)在可用于第 2 和第 3 版協(xié)議。
object.__getnewargs__()
這個方法與上一個 __getnewargs_ex__() 方法類似,但僅支持位置參數(shù)。它要求返回一個 tuple 類型的 args,用于解封時傳遞給 __new__() 方法。
如果定義了 __getnewargs_ex__(),那么 __getnewargs__() 就不會被調(diào)用。
在 3.6 版更改: 在 Python 3.6 前,第 2、3 版協(xié)議會調(diào)用 __getnewargs__(),更高版本協(xié)議會調(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
Noneand 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)解封時,如果類定義了 __setstate__(),就會在已解封狀態(tài)下調(diào)用它。此時不要求實例的 state 對象必須是 dict。沒有定義此方法的話,先前封存的 state 對象必須是 dict,且該 dict 內(nèi)容會在解封時賦給新實例的 __dict__。
備注
如果 __getstate__() 返回 False,那么在解封時就不會調(diào)用 __setstate__() 方法。
參考 處理有狀態(tài)的對象 一段獲取如何使用 __getstate__() 和 __setstate__() 方法的更多信息。
備注
在解封時,實例的某些方法例如 __getattr__(), __getattribute__() 或 __setattr__() 可能會被調(diào)用。 由于這些方法可能要求某些內(nèi)部不變量為真值,因此該類型應(yīng)當(dāng)實現(xiàn) __new__() 以建立這樣的不變量,因為當(dāng)解封一個實例時 __init__() 并不會被調(diào)用。
可以看出,其實 pickle 并不直接調(diào)用上面的幾個函數(shù)。事實上,這幾個函數(shù)是復(fù)制協(xié)議的一部分,它們實現(xiàn)了 __reduce__() 這一特殊接口。復(fù)制協(xié)議提供了統(tǒng)一的接口,用于在封存或復(fù)制對象的過程中取得所需數(shù)據(jù)。4
盡管這個協(xié)議功能很強(qiáng),但是直接在類中實現(xiàn) __reduce__() 接口容易產(chǎn)生錯誤。因此,設(shè)計類時應(yīng)當(dāng)盡可能的使用高級接口(比如 __getnewargs_ex__()、__getstate__() 和 __setstate__())。后面仍然可以看到直接實現(xiàn) __reduce__() 接口的狀況,可能別無他法,可能為了獲得更好的性能,或者兩者皆有之。
object.__reduce__()
該接口當(dāng)前定義如下。__reduce__() 方法不帶任何參數(shù),并且應(yīng)返回字符串或最好返回一個元組(返回的對象通常稱為“reduce 值”)。
如果返回字符串,該字符串會被當(dāng)做一個全局變量的名稱。它應(yīng)該是對象相對于其模塊的本地名稱,pickle 模塊會搜索模塊命名空間來確定對象所屬的模塊。這種行為常在單例模式使用。
如果返回的是元組,則應(yīng)當(dāng)包含 2 到 6 個元素,可選元素可以省略或設(shè)置為 None。每個元素代表的意義如下:
-
一個可調(diào)用對象,該對象會在創(chuàng)建對象的最初版本時調(diào)用。
-
可調(diào)用對象的參數(shù),是一個元組。如果可調(diào)用對象不接受參數(shù),必須提供一個空元組。
-
可選元素,用于表示對象的狀態(tài),將被傳給前述的 __setstate__() 方法。 如果對象沒有此方法,則這個元素必須是字典類型,并會被添加至 __dict__ 屬性中。
-
可選元素,一個返回連續(xù)項的迭代器(而不是序列)。這些項會被
obj.append(item)逐個加入對象,或被obj.extend(list_of_items)批量加入對象。這個元素主要用于 list 的子類,也可以用于那些正確實現(xiàn)了append()和extend()方法的類。(具體是使用append()還是extend()取決于 pickle 協(xié)議版本以及待插入元素的項數(shù),所以這兩個方法必須同時被類支持。) -
可選元素,一個返回連續(xù)鍵值對的迭代器(而不是序列)。這些鍵值對將會以
obj[key] = value的方式存儲于對象中。該元素主要用于 dict 子類,也可以用于那些實現(xiàn)了 __setitem__() 的類。 -
可選元素,一個帶有
(obj, state)簽名的可調(diào)用對象。該可調(diào)用對象允許用戶以編程方式控制特定對象的狀態(tài)更新行為,而不是使用obj的靜態(tài) __setstate__() 方法。如果此處不是None,則此可調(diào)用對象的優(yōu)先級高于obj的 __setstate__()。3.8 新版功能: 新增了元組的第 6 項,可選元素
(obj, state)。
object.__reduce_ex__(protocol)
作為替代選項,也可以實現(xiàn) __reduce_ex__() 方法。 此方法的唯一不同之處在于它應(yīng)接受一個整型參數(shù)用于指定協(xié)議版本。 如果定義了這個函數(shù),則會覆蓋 __reduce__() 的行為。 此外,__reduce__() 方法會自動成為擴(kuò)展版方法的同義詞。 這個函數(shù)主要用于為以前的 Python 版本提供向后兼容的 reduce 值。
持久化外部對象
為了獲取對象持久化的利益, pickle 模塊支持引用已封存數(shù)據(jù)流之外的對象。 這樣的對象是通過一個持久化 ID 來引用的,它應(yīng)當(dāng)是一個由字母數(shù)字類字符組成的字符串 (對于第 0 版協(xié)議) 5 或是一個任意對象 (用于任意新版協(xié)議)。
pickle 模塊不提供對持久化 ID 的解析工作,它將解析工作分配給用戶定義的方法,分別是 pickler 中的 persistent_id() 方法和 unpickler 中的 persistent_load() 方法。
要通過持久化 ID 將外部對象封存,必須在 pickler 中實現(xiàn) persistent_id() 方法,該方法接受需要被封存的對象作為參數(shù),返回一個 None 或返回該對象的持久化 ID。如果返回 None,該對象會被按照默認(rèn)方式封存為數(shù)據(jù)流。如果返回字符串形式的持久化 ID,則會封存這個字符串并加上一個標(biāo)記,這樣 unpickler 才能將其識別為持久化 ID。
要解封外部對象,Unpickler 必須實現(xiàn) persistent_load() 方法,接受一個持久化 ID 對象作為參數(shù)并返回一個引用的對象。
下面是一個全面的例子,展示了如何使用持久化 ID 來封存外部對象。
# Simple example presenting how persistent ID can be used to pickle# external objects by reference.import pickleimport sqlite3from 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 Noneclass DBUnpickler(pickle.Unpickler):def __init__(self, file, connection):super().__init__(file)self.connection = connectiondef 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 = pidif 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 ioimport 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 表
如果想對某些類進(jìn)行自定義封存,而又不想在類中增加用于封存的代碼,就可以創(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)建了一個帶有自有 dispatch 表的 pickle.Pickler 實例,它可以對 SomeClass 類進(jìn)行特殊處理。另外,下列代碼
class MyPickler(pickle.Pickler):dispatch_table = copyreg.dispatch_table.copy()dispatch_table[SomeClass] = reduce_SomeClassf = io.BytesIO()p = MyPickler(f)
完成同樣的操作,但所有 MyPickler 的實例都會共享一個私有分發(fā)表。 另一方面,代碼
copyreg.pickle(SomeClass, reduce_SomeClass)f = io.BytesIO()p = pickle.Pickler(f)
會修改由 copyreg 模塊的所有用戶共享的全局分發(fā)表。
處理有狀態(tài)的對象
下面的示例展示了如何修改類在封存時的行為。其中 TextReader 類打開了一個文本文件,每次調(diào)用其 readline() 方法則返回行號和該行的字符。 在封存這個 TextReader 的實例時,除了 文件對象,其他屬性都會被保存。 當(dāng)解封實例時,需要重新打開文件,然后從上次的位置開始繼續(xù)讀取。實現(xiàn)這些功能需要實現(xiàn) __setstate__() 和 __getstate__() 方法。
class TextReader:"""Print and number lines in a text file."""def __init__(self, filename):self.filename = filenameself.file = open(filename)self.lineno = 0def readline(self):self.lineno += 1line = self.file.readline()if not line:return Noneif 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 statedef __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!'
類型,函數(shù)和其他對象的自定義歸約
3.8 新版功能.
有時,dispatch_table 可能不夠靈活。 特別是當(dāng)我們想要基于對象類型以外的其他規(guī)則來對封存進(jìn)行定制,或是當(dāng)我們想要對函數(shù)和類的封存進(jìn)行定制的時候。
對于那些情況,可能要基于 Pickler 類進(jìn)行子類化并實現(xiàn) reducer_override() 方法。 此方法可返回任意的歸約元組 (參見 __reduce__())。 它也可以選擇返回 NotImplemented 來回退到傳統(tǒng)行為。
如果同時定義了 dispatch_table 和 reducer_override(),則 reducer_override() 方法具有優(yōu)先權(quán)。
備注
出于性能理由,可能不會為以下對象調(diào)用 reducer_override(): None, True, False, 以及 int, float, bytes, str, dict, set, frozenset, list 和 tuple 的具體實例。
以下是一個簡單的例子,其中我們允許封存并重新構(gòu)建一個給定的類:
import ioimport pickleclass MyClass:my_attribute = 1class 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 reductionreturn NotImplementedf = io.BytesIO()p = MyPickler(f)p.dump(MyClass)del MyClassunpickled_class = pickle.loads(f.getvalue())assert isinstance(unpickled_class, type)assert unpickled_class.__name__ == "MyClass"assert unpickled_class.my_attribute == 1
外部緩沖區(qū)
3.8 新版功能.
在某些場景中,pickle 模塊會被用來傳輸海量的數(shù)據(jù)。 因此,最小化內(nèi)存復(fù)制次數(shù)以保證性能和節(jié)省資源是很重要的。 但是 pickle 模塊的正常運(yùn)作會將圖類對象結(jié)構(gòu)轉(zhuǎn)換為字節(jié)序列流,因此在本質(zhì)上就要從封存流中來回復(fù)制數(shù)據(jù)。
如果 provider (待傳輸對象類型的實現(xiàn)) 和 consumer (通信系統(tǒng)的實現(xiàn)) 都支持 pickle 第 5 版或更高版本所提供的外部傳輸功能,則此約束可以被撤銷。
提供方 API
大的待封存數(shù)據(jù)對象必須實現(xiàn)協(xié)議 5 及以上版本專屬的 __reduce_ex__() 方法,該方法將為任意大的數(shù)據(jù)返回一個 PickleBuffer 實例(而不是 bytes 對象等)。
PickleBuffer 對象會 表明 底層緩沖區(qū)可被用于外部數(shù)據(jù)傳輸。 那些對象仍將保持與 pickle 模塊的正常用法兼容。 但是,使用方也可以選擇告知 pickle 它們將自行處理那些緩沖區(qū)。
使用方 API
當(dāng)序列化一個對象圖時,通信系統(tǒng)可以啟用對所生成 PickleBuffer 對象的定制處理。
發(fā)送端需要傳遞 buffer_callback 參數(shù)到 Pickler (或是到 dump() 或 dumps() 函數(shù)),該回調(diào)函數(shù)將在封存對象圖時附帶每個所生成的 PickleBuffer 被調(diào)用。 由 buffer_callback 所累積的緩沖區(qū)的數(shù)據(jù)將不會被拷貝到 pickle 流,而是僅插入一個簡單的標(biāo)記。
接收端需要傳遞 buffers 參數(shù)到 Unpickler (或是到 load() 或 loads() 函數(shù)),其值是一個由緩沖區(qū)組成的可迭代對象,它會被傳遞給 buffer_callback。 該可迭代對象應(yīng)當(dāng)按其被傳遞給 buffer_callback 時的順序產(chǎn)生緩沖區(qū)。 這些緩沖區(qū)將提供對象重構(gòu)造器所期望的數(shù)據(jù),對這些數(shù)據(jù)的封存產(chǎn)生了原本的 PickleBuffer 對象。
在發(fā)送端和接受端之間,通信系統(tǒng)可以自由地實現(xiàn)它自己用于外部緩沖區(qū)的傳輸機(jī)制。 潛在的優(yōu)化包括使用共享內(nèi)存或基于特定數(shù)據(jù)類型的壓縮等。
示例
下面是一個小例子,在其中我們實現(xiàn)了一個 bytearray 的子類,能夠用于外部緩沖區(qū)封存:
class ZeroCopyByteArray(bytearray):def __reduce_ex__(self, protocol):if protocol >= 5:return type(self)._reconstruct, (PickleBuffer(self),), Noneelse:# PickleBuffer is forbidden with pickle protocols <= 4.return type(self)._reconstruct, (bytearray(self),)@classmethoddef _reconstruct(cls, obj):with memoryview(obj) as m:# Get a handle over the original buffer objectobj = m.objif type(obj) is cls:# Original buffer object is a ZeroCopyByteArray, return it# as-is.return objelse:return cls(obj)
重構(gòu)造器 (_reconstruct 類方法) 會在緩沖區(qū)的提供對象具有正確類型時返回該對象。 在此小示例中這是模擬零拷貝行為的便捷方式。
在使用方,我們可以按通常方式封存那些對象,它們在反序列化時將提供原始對象的一個副本:
b = ZeroCopyByteArray(b"abc")data = pickle.dumps(b, protocol=5)new_b = pickle.loads(data)print(b == new_b) # Trueprint(b is new_b) # False: a copy was made
但是如果我們傳入 buffer_callback 然后在反序列化時給回累積的緩沖區(qū),我們就能夠取回原始對象:
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) # Trueprint(b is new_b) # True: no copy was made
這個例子受限于 bytearray 會自行分配內(nèi)存這一事實:你無法基于另一個對象的內(nèi)存創(chuàng)建 bytearray 的實例。 但是,第三方數(shù)據(jù)類型例如 NumPy 數(shù)組則沒有這種限制,允許在單獨(dú)進(jìn)程或系統(tǒng)間傳輸時使用零拷貝的封存(或是盡可能少地拷貝) 。
參見
PEP 574 — 帶有外部數(shù)據(jù)緩沖區(qū)的 pickle 協(xié)議 5
限制全局變量
默認(rèn)情況下,解封將會導(dǎo)入在 pickle 數(shù)據(jù)中找到的任何類或函數(shù)。 對于許多應(yīng)用來說,此行為是不可接受的,因為它會允許解封器導(dǎo)入并發(fā)起調(diào)用任意代碼。 只須考慮當(dāng)這個手工構(gòu)建的 pickle 數(shù)據(jù)流被加載時會做什么:
>>> import pickle>>> pickle.loads(b"cos\nsystem\n(S'echo hello world'\ntR.")hello world0
在這個例子里,解封器導(dǎo)入 os.system() 函數(shù)然后應(yīng)用字符串參數(shù) “echo hello world”。 雖然這個例子不具攻擊性,但是不難想象別人能夠通過此方式對你的系統(tǒng)造成損害。
出于這樣的理由,你可能會希望通過定制 Unpickler.find_class() 來控制要解封的對象。 與其名稱所提示的不同,Unpickler.find_class() 會在執(zhí)行對任何全局對象(例如一個類或一個函數(shù))的請求時被調(diào)用。 因此可以完全禁止全局對象或是將它們限制在一個安全的子集中。
下面的例子是一個解封器,它只允許某一些安全的來自 builtins 模塊的類被加載:
import builtinsimport ioimport picklesafe_builtins = {'range','complex','set','frozenset','slice',}class RestrictedUnpickler(pickle.Unpickler):<
本文名稱:創(chuàng)新互聯(lián)Python教程:pickle—-Python對象序列化
本文URL:http://m.fisionsoft.com.cn/article/dpecice.html


咨詢
建站咨詢
