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

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


咨詢
建站咨詢
