Pickle序列化的優(yōu)點(diǎn)和缺點(diǎn)

圖片來源:RitaE from Pixabay
除非你知道所有這些要點(diǎn),否則不要使用Python Pickle
Pickle序列化的優(yōu)點(diǎn)和缺點(diǎn),以及我們應(yīng)該何時(shí)使用它
與其他大多數(shù)流行的編程語言相比,Python可能擁有最靈活的對(duì)象序列化。在Python中,所有的東西都是一個(gè)對(duì)象,所以我們可以說,幾乎所有的東西都可以被序列化。是的,我說的這個(gè)模塊就是 Pickle。

然而,與其他 "常規(guī) "的序列化方法如JSON相比,Pickle有更多的方面需要我們?cè)谑褂脮r(shí)注意。這就是標(biāo)題所說的,除非你知道這些事實(shí),否則不要使用Pickle。

在這篇文章中,我將組織一些關(guān)于Pickle的重要注意事項(xiàng),希望它們能對(duì)你有所幫助。

  1. 基本用法

圖片:Photo Mix from Pixabay
通過使用Python的Pickle模塊,我們可以輕松地將幾乎所有類型的對(duì)象序列化到一個(gè)文件中。在我們使用它之前,需要先導(dǎo)入它。

導(dǎo)入 pickle
讓我們以一個(gè)字典為例。

import pickle
Let’s take a dictionary as an example.

my_dict = {
    'name': 'Chris',
    'age': 33
}

我們可以使用 pickle.dump() 方法來序列化字典并將其寫入文件。

with open('my_dict.pickle', 'wb') as f:
    pickle.dump(my_dict, f)

然后,我們可以讀取該文件并將其加載到一個(gè)變量中。之后,我們就有了準(zhǔn)確的字典回來。它們?cè)趦?nèi)容上是 100% 相同的。

with open('my_dict.pickle', 'rb') as f:
    my_dict_unpickled = pickle.load(f)
my_dict == my_dict_unpickled
  1. 為什么是Pickle?有什么優(yōu)點(diǎn)和缺點(diǎn)?

圖片來自Pixabay的Christine Sponchia
的確,如果我們?cè)谏厦娴睦又惺褂肑SON來序列化Python字典,會(huì)有更多的好處。一般來說,Pickle序列化有三個(gè)主要缺點(diǎn)。

缺點(diǎn)-1:Pickle是不安全的
不像JSON只是一個(gè)字符串,有可能構(gòu)建惡意的Pickle數(shù)據(jù),在解壓過程中執(zhí)行任意代碼。
大喵補(bǔ)充:pickle是一串二進(jìn)制的數(shù)字!因此,我們絕對(duì)不應(yīng)該解開那些可能來自不信任的源頭或可能被篡改的數(shù)據(jù)。

缺點(diǎn)2:Pickle是不可讀的
將Python字典序列化為JSON字符串的最大意義在于,其結(jié)果是人類可讀的。然而,對(duì)于Pickle文件來說,這并不是真的。這里是我們剛剛腌制的字典的 pickle 文件。如果我們?cè)噲D把它作為一個(gè)文本文件打開,我們會(huì)得到這樣的結(jié)果。

缺點(diǎn) 3:Python 中的 Pickle 是有限的
一個(gè) pickle 對(duì)象只能用 Python 加載。其他語言可能可以這樣做,但需要第三方庫的參與,而且可能仍然沒有得到完美的支持。

相比之下,JSON字符串在編程領(lǐng)域非常常用,并且被大多數(shù)編程語言所支持。

Pickle的優(yōu)點(diǎn)
Pickle通過調(diào)用任意的函數(shù)來構(gòu)造任意的Python對(duì)象,這就是為什么它不安全。然而,這使得它能夠序列化幾乎所有的Python對(duì)象,而JSON和其他序列化方法是做不到的。

解開一個(gè)對(duì)象通常不需要 "模板"。所以,它非常適用于快速和簡(jiǎn)單的序列化。例如,你可以把所有的變量轉(zhuǎn)儲(chǔ)到pickle文件中,然后終止你的程序。稍后,你可以啟動(dòng)另一個(gè)Python會(huì)話,并從序列化的文件中恢復(fù)一切。所以,這使我們能夠以一種更靈活的方式運(yùn)行一段程序。

另一個(gè)例子將是多線程。當(dāng)我們使用多進(jìn)程模塊在多線程中運(yùn)行一個(gè)程序時(shí),我們可以很容易地將任意的Python對(duì)象發(fā)送到其他進(jìn)程或計(jì)算節(jié)點(diǎn)。

在這些情況下,安全問題通常并不適用,人類也不會(huì)去讀這些對(duì)象。我們只需要快速、簡(jiǎn)單和兼容性。在這些情況下,Pickle可以完美地被利用。

  1. 還有什么可以被腌制 pickle?

好吧,我一直在談?wù)搸缀跛锌梢员籔ickle序列化的東西。現(xiàn)在,讓我給你看一些例子。

Do Not Use Python Pickle Unless You Know All These Points2.jpeg

pickle 一個(gè)函數(shù)
第一個(gè)例子將是一個(gè)函數(shù)。是的,我們可以在 Python 中序列化一個(gè)函數(shù),因?yàn)楹瘮?shù)在 Python 中也是一個(gè)對(duì)象。

def my_func(num):
    print(f'my function will add 1 to the number {num}')
    return num + 1

只是為演示目的定義了一個(gè)簡(jiǎn)單的函數(shù)?,F(xiàn)在,讓我們把它提取出來并載入一個(gè)新的變量中。

with open('my_func.pickle', 'wb') as f:
    pickle.dump(my_func, f)
with open('my_func.pickle', 'rb') as f:
    my_func_unpickled = pickle.load(f)
my_func_unpickled(10)

新的變量可以作為一個(gè)函數(shù)使用,而且這個(gè)函數(shù)將與原來的函數(shù)完全相同。

Pickle一個(gè)Pandas數(shù)據(jù)框
另一個(gè)例子是一個(gè)Pandas數(shù)據(jù)框。讓我們來定義一個(gè)Pandas數(shù)據(jù)框。

import pandas as pd
my_df = pd.DataFrame({
    'name': ['Alice', 'Bob', 'Chris'],
    'age': [25, 29, 33]
})

現(xiàn)在,我們可以腌制它并將其解壓到一個(gè)新的變量中,新的DataFrame將是相同的。

with open('my_df.pickle', 'wb') as f:
    pickle.dump(my_df, f)
with open('my_df.pickle', 'rb') as f:
    my_df_unpickled = pickle.load(f)

請(qǐng)注意,Pandas有內(nèi)置的方法,可以對(duì)數(shù)據(jù)幀進(jìn)行pickle和unpickle處理
請(qǐng)注意,Pandas有內(nèi)置的方法,可以對(duì)數(shù)據(jù)幀進(jìn)行糾錯(cuò)和解錯(cuò)。它們會(huì)做與上面相同的工作,但代碼會(huì)更干凈。其性能也是相同的。

那么,可能會(huì)有一個(gè)問題,為什么我們要對(duì)數(shù)據(jù)框使用Pickle而不是CSV?

第一個(gè)答案是速度。CSV是人類可讀的,但它幾乎是存儲(chǔ)Pandas數(shù)據(jù)幀的最慢的方式。
對(duì)序列化Pandas數(shù)據(jù)框架的不同方式的性能進(jìn)行了基準(zhǔn)測(cè)試。

image.png
pickle.png
image.png
image.png
image.png

Reference benchmark test:
What is the fastest way to upload a big csv file in notebook to work with python pandas? - Stack Overflow

Pickle pandas dataframe的第二個(gè)好處是數(shù)據(jù)類型。當(dāng)我們把數(shù)據(jù)框架寫到CSV文件時(shí),所有的東西都要轉(zhuǎn)換成文本。有時(shí),當(dāng)我們把它加載回來時(shí),這將造成一些不便或麻煩。例如,如果我們把一個(gè)日期時(shí)間列寫到CSV中,我們很可能需要在加載回來時(shí)指定格式字符串。

然而,這個(gè)問題對(duì)于一個(gè)pickle對(duì)象來說并不存在。你腌制的東西,當(dāng)你加載它時(shí),你保證會(huì)有完全相同的東西回來。不需要做任何其他事情。

  1. 腌制協(xié)議版本

像我在前面的例子中所做的那樣使用Pickle是很常見的。他們沒有錯(cuò),但如果我們能指定Pickle的協(xié)議版本(通常是最高的),那就更好了。簡(jiǎn)單地說,Pickle的序列化有不同的版本。隨著Python版本的不斷迭代,Pickle模塊也在不斷發(fā)展。

如果你對(duì)現(xiàn)有的版本和改進(jìn)的內(nèi)容感興趣,這里有一個(gè)官方文檔的列表。

協(xié)議版本0是原始的 "人類可讀 "協(xié)議,并且向后兼容早期的Python版本。

協(xié)議版本1是一種舊的二進(jìn)制格式,也與早期的Python版本兼容。

協(xié)議版本 2 是在 Python 2.3 中引入的。它提供了一個(gè)更有效的新式類的腌制方法。

協(xié)議版本 3 是在 Python 3.0 中加入的。它對(duì)字節(jié)對(duì)象有明確的支持,并且不能被Python 2.x解開。這是Python 3.0-3.7的默認(rèn)協(xié)議。

協(xié)議版本 4 是在 Python 3.4 中添加的。它增加了對(duì)非常大的對(duì)象的支持,腌制了更多種類的對(duì)象,以及一些數(shù)據(jù)格式的優(yōu)化。從Python 3.8開始,它是默認(rèn)的協(xié)議。

協(xié)議版本5是在Python 3.8中添加的。它增加了對(duì)帶外數(shù)據(jù)的支持和對(duì)帶內(nèi)數(shù)據(jù)的加速。

一般來說,在以下方面,高版本總是比低版本好一些

腌制對(duì)象的大小
解除pickling的性能
如果我們使用不同的版本對(duì)Pandas數(shù)據(jù)幀進(jìn)行pickle,我們可以看到其大小上的差異。

with open('my_df_p4.pickle', 'wb') as f:
    pickle.dump(my_df, f, protocol=4)
with open('my_df_p3.pickle', 'wb') as f:
    pickle.dump(my_df, f, protocol=3)
with open('my_df_p2.pickle', 'wb') as f:
    pickle.dump(my_df, f, protocol=2)
with open('my_df_p1.pickle', 'wb') as f:
    pickle.dump(my_df, f, protocol=1)

path.getsize

import os
print('P4:', os.path.getsize('my_df_p4.pickle'))
print('P3:', os.path.getsize('my_df_p3.pickle'))
print('P2:', os.path.getsize('my_df_p2.pickle'))
print('P1:', os.path.getsize('my_df_p1.pickle'))

為什么 Python 仍然保留舊版本而新版本總是更好?這是因?yàn)閰f(xié)議并不總是向后兼容。這意味著,如果我們想要更好的兼容性,就必須選擇一個(gè)較低的版本。

然而,如果我們使用pickle對(duì)象而不需要向后兼容,我們可以使用枚舉來保證我們的程序使用最新的版本(最好的版本)。例子如下。

pickle.dump(my_df, f, protocol=pickle.HIGHEST_PROTOCOL)
  1. 拾取一個(gè)自定義類

盡管Pickle支持Python中幾乎所有的對(duì)象,但當(dāng)我們拾取一個(gè)從自定義類中實(shí)例化出來的對(duì)象時(shí),我們?nèi)匀恍枰⌒?。?jiǎn)而言之,當(dāng)我們加載被拾取的對(duì)象時(shí),該類需要是存在的。

例如,讓我們定義一個(gè)有兩個(gè)屬性和一個(gè)方法的簡(jiǎn)單類 "Person"。

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
def self_introduce(self):
        print(f'My name is {self.name} and my age is {self.age}')
p = Person('Chris', 33)
p.self_introduce()

現(xiàn)在,讓我們用Pickle來序列化對(duì)象 "p"。

with open('person.pickle', 'wb') as f:
    pickle.dump(p, f)

如果這個(gè)類不存在,問題就會(huì)發(fā)生。如果我們?cè)噲D在一個(gè)新的會(huì)話中加載pickle對(duì)象,而這個(gè)類并沒有被定義,這就會(huì)發(fā)生。我們可以通過刪除類的定義來模擬這種情況。

del Person

然后,如果我們?cè)噲D重新加載腌制對(duì)象,就會(huì)出現(xiàn)一個(gè)異常。

with open('person.pickle', 'rb') as f:
    p_unpickled = pickle.load(f)
image.png

因此,我們需要確保當(dāng)我們加載對(duì)象回來時(shí),這個(gè)類是存在的。然而,如果類的定義稍有不同,可能不會(huì)造成問題,但對(duì)象的行為可能會(huì)根據(jù)新的類定義而改變。

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
def self_introduce(self):
        print(f'(Modified) My name is {self.name} and my age is {self.age}')

在新的類定義中,我修改了自我介紹方法的打印信息。然后,如果我們把腌制好的對(duì)象裝回去,就不會(huì)有任何錯(cuò)誤,但自我介紹方法會(huì)與原來的方法不同。

with open('person.pickle', 'rb') as f:
    p_unpickled = pickle.load(f)
p_unpickled.self_introduce()
image.png
  1. 不是所有的對(duì)象都可以被pickled

在最后一節(jié)中,我不得不回到我最初的聲明 "幾乎所有的 Python 對(duì)象都可以被腌制"。我使用 "幾乎所有 "是因?yàn)槿匀挥幸恍╊愋偷膶?duì)象不能被Pickle序列化。

一個(gè)典型的不能被腌制的類型是實(shí)時(shí)連接對(duì)象,比如網(wǎng)絡(luò)或數(shù)據(jù)庫連接。這是有道理的,因?yàn)镻ickle在關(guān)閉后將無法建立連接。這些對(duì)象只能用適當(dāng)?shù)膽{證和其他資源重新創(chuàng)建。

另一個(gè)需要提到的類型將是一個(gè)模塊。一個(gè)重要的模塊也不能被腌制。請(qǐng)看下面的例子。

import datetime
with open('datetime.pickle', 'wb') as f:
    pickle.dump(datetime, f)

這一點(diǎn)很重要,因?yàn)檫@意味著我們將不能在global()中腌制所有的東西,因?yàn)閷?dǎo)入的模塊會(huì)在里面。

總結(jié)

在這篇文章中,我介紹了Python中內(nèi)置的序列化方法 - Pickle。它可以用于快速而簡(jiǎn)單的序列化。它幾乎支持所有類型的Python對(duì)象,如函數(shù),甚至Pandas數(shù)據(jù)幀。當(dāng)對(duì)不同版本的Python使用Pickle時(shí),我們還需要記住,Pickle的版本也可能不同。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1 前言 在“通過簡(jiǎn)單示例來理解什么是機(jī)器學(xué)習(xí)”這篇文章里提到了pickle庫的使用,本文來做進(jìn)一步的闡述。 pi...
    leenard閱讀 2,048評(píng)論 0 2
  • 0x01 前言 前兩天在安全客中看了一篇python對(duì)象注入的文章,感覺寫的很好,就學(xué)習(xí)一下,在此記錄一下。 0x...
    Pino_HD閱讀 1,136評(píng)論 0 0
  • 一個(gè)人最重要的品質(zhì),是懂得克制,克制自己,不是沖動(dòng)任性,克制自己的情緒。 而真正成熟的人,首先應(yīng)該是一個(gè)懂得克制自...
    BeautifulSoulpy閱讀 591評(píng)論 0 1
  • pickle模塊實(shí)現(xiàn)Python對(duì)象結(jié)構(gòu)的二進(jìn)制協(xié)議序列化和反序列化。pickling將Python對(duì)象層次結(jié)構(gòu)轉(zhuǎn)...
    巧生于緣閱讀 709評(píng)論 0 0
  • 序列化定義:將內(nèi)存中的數(shù)據(jù)寫入磁盤或者傳輸?shù)骄W(wǎng)絡(luò)中。 反序列化:將本地?cái)?shù)據(jù)或者網(wǎng)絡(luò)數(shù)據(jù)寫入內(nèi)存中。 Python ...
    傻傻笨笨寶寶閱讀 690評(píng)論 0 0

友情鏈接更多精彩內(nèi)容