自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Python 持久性管理

開發(fā) 后端
持久性就是指保持對象,甚至在多次執(zhí)行同一程序之間也保持對象。通過本文,您會(huì)對 Python對象的各種持久性機(jī)制(從關(guān)系數(shù)據(jù)庫到 Python 的 pickle以及其它機(jī)制)有一個(gè)總體認(rèn)識(shí)。

[[401415]]

 持久性就是指保持對象,甚至在多次執(zhí)行同一程序之間也保持對象。通過本文,您會(huì)對 Python對象的各種持久性機(jī)制(從關(guān)系數(shù)據(jù)庫到 Python 的 pickle以及其它機(jī)制)有一個(gè)總體認(rèn)識(shí)。另外,還會(huì)讓您更深一步地了解Python 的對象序列化能力。

什么是持久性?

持久性的基本思想很簡單。假定有一個(gè) Python程序,它可能是一個(gè)管理日常待辦事項(xiàng)的程序,您希望在多次執(zhí)行這個(gè)程序之間可以保存應(yīng)用程序?qū)ο螅ùk事項(xiàng))。換句話說,您希望將對象存儲(chǔ)在磁盤上,便于以后檢索。這就是持久性。要達(dá)到這個(gè)目的,有幾種方法,每一種方法都有其優(yōu)缺點(diǎn)。

例如,可以將對象數(shù)據(jù)存儲(chǔ)在某種格式的文本文件中,譬如 CSV文件?;蛘呖梢杂藐P(guān)系數(shù)據(jù)庫,譬如 Gadfly、MySQL、PostgreSQL 或者 DB2。這些文件格式和數(shù)據(jù)庫都非常優(yōu)秀,對于所有這些存儲(chǔ)機(jī)制,Python都有健壯的接口。

這些存儲(chǔ)機(jī)制都有一個(gè)共同點(diǎn):存儲(chǔ)的數(shù)據(jù)是獨(dú)立于對這些數(shù)據(jù)進(jìn)行操作的對象和程序。這樣做的好處是,數(shù)據(jù)可以作為共享的資源,供其它應(yīng)用程序使用。缺點(diǎn)是,用這種方式,可以允許其它程序訪問對象的數(shù)據(jù),這違背了面向?qū)ο蟮姆庋b性原則 — 即對象的數(shù)據(jù)只能通過這個(gè)對象自身的公共(public)接口來訪問。

另外,對于某些應(yīng)用程序,關(guān)系數(shù)據(jù)庫方法可能不是很理想。尤其是,關(guān)系數(shù)據(jù)庫不理解對象。相反,關(guān)系數(shù)據(jù)庫會(huì)強(qiáng)行使用自己的類型系統(tǒng)和關(guān)系數(shù)據(jù)模型(表),每張表包含一組元組(行),每行包含具有固定數(shù)目的靜態(tài)類型字段(列)。如果應(yīng)用程序的對象模型不能夠方便地轉(zhuǎn)換到關(guān)系模型,那么在將對象映射到元組以及將元組映射回對象方面,會(huì)碰到一定難度。這種困難常被稱為阻礙性不匹配(impedence-mismatch)問題。

對象持久性

如果希望透明地存儲(chǔ) Python 對象,而不丟失其身份和類型等信息,則需要某種形式的對象序列化:它是一個(gè)將任意復(fù)雜的對象轉(zhuǎn)成對象的文本或二進(jìn)制表示的過程。同樣,必須能夠?qū)ο蠼?jīng)過序列化后的形式恢復(fù)到原有的對象。在 Python中,這種序列化過程稱為pickle,可以將對象 pickle 成字符串、磁盤上的文件或者任何類似于文件的對象,也可以將這些字符串、文件或任何類似于文件的對象 unpickle 成原來的對象。我們將在本文后面詳細(xì)討論 pickle。

假定您喜歡將任何事物都保存成對象,而且希望避免將對象轉(zhuǎn)換成某種基于非對象存儲(chǔ)的開銷;那么pickle 文件可以提供這些好處,但有時(shí)可能需要比這種簡單的 pickle文件更健壯以及更具有可伸縮性的事物。例如,只用 pickle 不能解決命名和查找 pickle文件這樣的問題,另外,它也不能支持并發(fā)地訪問持久性對象。如果需要這些方面的功能,則要求助類似于ZODB(針對 Python 的 Z 對象數(shù)據(jù)庫)這類數(shù)據(jù)庫。ZODB 是一個(gè)健壯的、多用戶的和面向?qū)ο蟮臄?shù)據(jù)庫系統(tǒng),它能夠存儲(chǔ)和管理任意復(fù)雜的 Python對象,并支持事務(wù)操作和并發(fā)控制。(請參閱參考資料,以下載ZODB。)令人足夠感興趣的是,甚至 ZODB 也依靠 Python 的本機(jī)序列化能力,而且要有效地使用ZODB,必須充分了解 pickle。

另一種令人感興趣的解決持久性問題的方法是 Prevayler,它最初是用 Java實(shí)現(xiàn)的(有關(guān) Prevaylor 方面的 developerWorks文章,請參閱參考資料)。最近,一群 Python 程序員將 Prevayler 移植到了 Python 上,另起名為 PyPerSyst,由 SourceForge 托管(有關(guān)至 PyPerSyst項(xiàng)目的鏈接,請參閱參考資料)。Prevayler/PyPerSyst概念也是建立在 Java 和 Python 語言的本機(jī)序列化能力之上。PyPerSyst 將整個(gè)對象系統(tǒng)保存在內(nèi)存中,并通過不時(shí)地將系統(tǒng)快照pickle 到磁盤以及維護(hù)一個(gè)命令日志(通過此日志可以重新應(yīng)用最新的快照)來提供災(zāi)難恢復(fù)。所以,盡管使用 PyPerSyst 的應(yīng)用程序受到可用內(nèi)存的限制,但好處是本機(jī)對象系統(tǒng)可以完全裝入到內(nèi)存中,因而速度極快,而且實(shí)現(xiàn)起來要比如ZODB 這樣的數(shù)據(jù)庫簡單,ZODB 允許對象的數(shù)目比同時(shí)在能內(nèi)存中所保持的對象要多。

既然我們已經(jīng)簡要討論了存儲(chǔ)持久對象的各種方法,那么現(xiàn)在該詳細(xì)探討 pickle過程了。雖然我們主要感興趣的是探索以各種方式來保存 Python 對象,而不必將其轉(zhuǎn)換成某種其它格式,但我們?nèi)匀贿€有一些需要關(guān)注的地方,譬如:如何有效地 pickle 和 unpickle 簡單對象以及復(fù)雜對象,包括定制類的實(shí)例;如何維護(hù)對象的引用,包括循環(huán)引用和遞歸引用;以及如何處理類定義發(fā)生的變化,從而使用以前經(jīng)過 pickle 的實(shí)例時(shí)不會(huì)發(fā)生問題。我們將在隨后關(guān)于Python 的 pickle 能力探討中涉及所有這些問題。

一些經(jīng)過 pickle 的 Python

pickle 模塊及其同類模塊 cPickle 向 Python 提供了 pickle支持。后者是用 C 編碼的,它具有更好的性能,對于大多數(shù)應(yīng)用程序,推薦使用該模塊。我們將繼續(xù)討論pickle ,但本文的示例實(shí)際是利用了 cPickle 。由于其中大多數(shù)示例要用 Python shell來顯示,所以先展示一下如何導(dǎo)入cPickle ,并可以作為 pickle 來引用它:   

  1. >>> import cPickle as pickle 

現(xiàn)在已經(jīng)導(dǎo)入了該模塊,接下來讓我們看一下 pickle 接口。 pickle 模塊提供了以下函數(shù)對: dumps(object) 返回一個(gè)字符串,它包含一個(gè) pickle 格式的對象;loads(string) 返回包含在 pickle 字符串中的對象; dump(object, file) 將對象寫到文件,這個(gè)文件可以是實(shí)際的物理文件,但也可以是任何類似于文件的對象,這個(gè)對象具有write() 方法,可以接受單個(gè)的字符串參數(shù); load(file) 返回包含在 pickle 文件中的對象。

缺省情況下, dumps() 和 dump() 使用可打印的 ASCII 表示來創(chuàng)建pickle。兩者都有一個(gè) final 參數(shù)(可選),如果為True ,則該參數(shù)指定用更快以及更小的二進(jìn)制表示來創(chuàng)建pickle。 loads() 和 load() 函數(shù)自動(dòng)檢測 pickle 是二進(jìn)制格式還是文本格式。

清單 1 顯示了一個(gè)交互式會(huì)話,這里使用了剛才所描述的 dumps() 和 loads() 函數(shù):

清單 1. dumps() 和 loads() 的演示 

  1. >>> import cPickle as pickle    
  2. >>> t1 = ('this is a string', 42, [1, 2, 3], None)    
  3. >>> t1    
  4. ('this is a string', 42, [1, 2, 3], None)   
  5. >>> p1 = pickle.dumps(t1)    
  6. >>> p1    
  7. "(S'this is a string'/nI42/n(lp1/nI1/naI2/naI3/naNtp2/n."  
  8. >>> print p1    
  9. (S'this is a string'  
  10. I42    
  11. (lp1    
  12. I1    
  13. aI2    
  14. aI3    
  15. aNtp2    
  16. .    
  17. >>> t2 = pickle.loads(p1)    
  18. >>> t2    
  19. ('this is a string', 42, [1, 2, 3], None)    
  20. >>> p2 = pickle.dumps(t1, True)    
  21. >>> p2    
  22. '(U/x10this is a stringK*]q/x01(K/x01K/x02K/x03eNtq/x02.'  
  23. >>> t3 = pickle.loads(p2)    
  24. >>> t3    
  25. ('this is a string', 42, [1, 2, 3], None) 

注:該文本 pickle 格式很簡單,這里就不解釋了。事實(shí)上,在 pickle 模塊中記錄了所有使用的約定。我們還應(yīng)該指出,在我們的示例中使用的都是簡單對象,因此使用二進(jìn)制 pickle格式不會(huì)在節(jié)省空間上顯示出太大的效率。然而,在實(shí)際使用復(fù)雜對象的系統(tǒng)中,您會(huì)看到,使用二進(jìn)制格式可以在大小和速度方面帶來顯著的改進(jìn)。

接下來,我們看一些示例,這些示例用到了 dump() 和 load() ,它們使用文件和類似文件的對象。這些函數(shù)的操作非常類似于我們剛才所看到的dumps() 和 loads() ,區(qū)別在于它們還有另一種能力 — dump() 函數(shù)能一個(gè)接著一個(gè)地將幾個(gè)對象轉(zhuǎn)儲(chǔ)到同一個(gè)文件。隨后調(diào)用load() 來以同樣的順序檢索這些對象。清單 2 顯示了這種能力的實(shí)際應(yīng)用:

清單 2. dump() 和 load() 示例 

  1. >>> a1 = 'apple'  
  2. >>> b1 = {1: 'One', 2: 'Two', 3: 'Three'}    
  3. >>> c1 = ['fee', 'fie', 'foe', 'fum']    
  4. >>> f1 = file('temp.pkl', 'wb')    
  5. >>> pickle.dump(a1, f1, True)    
  6. >>> pickle.dump(b1, f1, True)    
  7. >>> pickle.dump(c1, f1, True)    
  8. >>> f1.close()    
  9. >>> f2 = file('temp.pkl', 'rb')    
  10. >>> a2 = pickle.load(f2)    
  11. >>> a2    
  12. 'apple'  
  13. >>> b2 = pickle.load(f2)    
  14. >>> b2    
  15. {1: 'One', 2: 'Two', 3: 'Three'}    
  16. >>> c2 = pickle.load(f2)    
  17. >>> c2    
  18. ['fee', 'fie', 'foe', 'fum']    
  19. >>> f2.close() 

Pickle 的威力

到目前為止,我們講述了關(guān)于 pickle 方面的基本知識(shí)。在這一節(jié),將討論一些高級問題,當(dāng)您開始 pickle復(fù)雜對象時(shí),會(huì)遇到這些問題,其中包括定制類的實(shí)例。幸運(yùn)的是,Python 可以很容易地處理這種情形。

可移植性

從空間和時(shí)間上說,Pickle 是可移植的。換句話說,pickle 文件格式獨(dú)立于機(jī)器的體系結(jié)構(gòu),這意味著,例如,可以在 Linux下創(chuàng)建一個(gè) pickle,然后將它發(fā)送到在 Windows 或 Mac OS 下運(yùn)行的 Python程序。并且,當(dāng)升級到更新版本的 Python 時(shí),不必?fù)?dān)心可能要廢棄已有的 pickle。Python 開發(fā)人員已經(jīng)保證 pickle 格式將可以向后兼容Python 各個(gè)版本。事實(shí)上,在pickle 模塊中提供了有關(guān)目前以及所支持的格式方面的詳細(xì)信息:

清單 3. 檢索所支持的格式 

  1. >>> pickle.format_version  
  2. '1.3'  
  3. >>> pickle.compatible_formats  
  4. ['1.0', '1.1', '1.2'] 

多個(gè)引用,同一對象

在 Python 中,變量是對象的引用。同時(shí),也可以用多個(gè)變量引用同一個(gè)對象。經(jīng)證明,Python 在用經(jīng)過pickle 的對象維護(hù)這種行為方面絲毫沒有困難,如清單 4 所示:

清單 4. 對象引用的維護(hù)   

  1. >>> a = [1, 2, 3]  
  2.    >>> b = a  
  3.    >>> a  
  4.    [1, 2, 3]  
  5.    >>> b  
  6.    [1, 2, 3]  
  7.    >>> a.append(4)  
  8.    >>> a  
  9.    [1, 2, 3, 4]  
  10.    >>> b  
  11.    [1, 2, 3, 4]  
  12.    >>> c = pickle.dumps((a, b))  
  13.    >>> d, e = pickle.loads(c)  
  14.    >>> d  
  15.    [1, 2, 3, 4]  
  16.    >>> e  
  17.    [1, 2, 3, 4]  
  18.    >>> d.append(5)  
  19.    >>> d 
  20.    [1, 2, 3, 4, 5]  
  21.    >>> e  
  22.    [1, 2, 3, 4, 5] 

循環(huán)引用和遞歸引用

可以將剛才演示過的對象引用支持?jǐn)U展到 循環(huán)引用(兩個(gè)對象各自包含對對方的引用)和 遞歸引用(一個(gè)對象包含對其自身的引用)。下面兩個(gè)清單著重顯示這種能力。我們先看一下遞歸引用:

>清單 5. 遞歸引用 

  1. >>> l = [1, 2, 3]  
  2. >>> l.append(l)  
  3. >>> l  
  4. [1, 2, 3, [...]]  
  5. >>> l[3]  
  6. [1, 2, 3, [...]]  
  7. >>> l[3][3]  
  8. [1, 2, 3, [...]]  
  9. >>> p = pickle.dumps(l)  
  10. >>> l2 = pickle.loads(p)  
  11. >>> l2  
  12. [1, 2, 3, [...]]  
  13. >>> l2[3]  
  14. [1, 2, 3, [...]]  
  15. >>> l2[3][3]  
  16. [1, 2, 3, [...]] 

現(xiàn)在,看一個(gè)循環(huán)引用的示例:

清單 6. 循環(huán)引用 

  1. >>> a = [1, 2]  
  2. >>> b = [3, 4]  
  3. >>> a.append(b)  
  4. >>> a  
  5. [1, 2, [3, 4]]  
  6. >>> b.append(a)  
  7. >>> a  
  8. [1, 2, [3, 4, [...]]]  
  9. >>> b  
  10. [3, 4, [1, 2, [...]]]  
  11. >>> a[2]  
  12. [3, 4, [1, 2, [...]]] 
  13. >>> b[2]  
  14. [1, 2, [3, 4, [...]]]  
  15. >>> a[2] is b  
  16.  
  17. >>> b[2] is a  
  18.  
  19. >>> f = file('temp.pkl', 'w')  
  20. >>> pickle.dump((a, b), f)  
  21. >>> f.close()  
  22. >>> f = file('temp.pkl', 'r')  
  23. >>> c, d = pickle.load(f)  
  24. >>> f.close()  
  25. >>> c  
  26. [1, 2, [3, 4, [...]]]  
  27. >>> d  
  28. [3, 4, [1, 2, [...]]]  
  29. >>> c[2]  
  30. [3, 4, [1, 2, [...]]]  
  31. >>> d[2]  
  32. [1, 2, [3, 4, [...]]]  
  33. >>> c[2] is d  
  34.  
  35. >>> d[2] is c  

注意,如果分別 pickle 每個(gè)對象,而不是在一個(gè)元組中一起 pickle 所有對象,會(huì)得到略微不同(但很重要)的結(jié)果,如清單 7 所示:

清單 7. 分別 pickle vs. 在一個(gè)元組中一起 pickle 

  1. >>> f = file('temp.pkl', 'w')  
  2. >>> pickle.dump(a, f)  
  3. >>> pickle.dump(b, f)  
  4. >>> f.close()  
  5. >>> f = file('temp.pkl', 'r')  
  6. >>> c = pickle.load(f)  
  7. >>> d = pickle.load(f)  
  8. >>> f.close()  
  9. >>> c  
  10. [1, 2, [3, 4, [...]]]  
  11. >>> d  
  12. [3, 4, [1, 2, [...]]]  
  13. >>> c[2]  
  14. [3, 4, [1, 2, [...]]]  
  15. >>> d[2]  
  16. [1, 2, [3, 4, [...]]]  
  17. >>> c[2] is d  
  18.  
  19. >>> d[2] is c  

相等,但并不總是相同

正如在上一個(gè)示例所暗示的,只有在這些對象引用內(nèi)存中同一個(gè)對象時(shí),它們才是相同的。在 pickle情形中,每個(gè)對象被恢復(fù)到一個(gè)與原來對象相等的對象,但不是同一個(gè)對象。換句話說,每個(gè) pickle都是原來對象的一個(gè)副本:

清單 8. 作為原來對象副本的被恢復(fù)的對象 

  1. >>> j = [1, 2, 3]  
  2. >>> k = j  
  3. >>> k is j  
  4.  
  5. >>> x = pickle.dumps(k)  
  6. >>> y = pickle.loads(x)  
  7. >>> y  
  8. [1, 2, 3]  
  9. >>> y == k  
  10.  
  11. >>> y is k  
  12.  
  13. >>> y is j  
  14.  
  15. >>> k is j  

同時(shí),我們看到 Python 能夠維護(hù)對象之間的引用,這些對象是作為一個(gè)單元進(jìn)行 pickle 的。然而,我們還看到分別調(diào)用 dump() 會(huì)使 Python 無法維護(hù)對在該單元外部進(jìn)行 pickle的對象的引用。相反,Python 復(fù)制了被引用對象,并將副本和被 pickle 的對象存儲(chǔ)在一起。對于 pickle和恢復(fù)單個(gè)對象層次結(jié)構(gòu)的應(yīng)用程序,這是沒有問題的。但要意識(shí)到還有其它情形。

值得指出的是,有一個(gè)選項(xiàng)確實(shí)允許分別 pickle 對象,并維護(hù)相互之間的引用,只要這些對象都是 pickle 到同一文件即可。 pickle 和cPickle 模塊提供了一個(gè) Pickler (與此相對應(yīng)是 Unpickler ),它能夠跟蹤已經(jīng)被pickle 的對象。通過使用這個(gè)Pickler ,將會(huì)通過引用而不是通過值來 pickle 共享和循環(huán)引用:

清單 9. 維護(hù)分別 pickle 的對象間的引用 

  1. >>> f = file('temp.pkl', 'w') 
  2. >>> picklepickler = pickle.Pickler(f)  
  3. >>> pickler.dump(a)  
  4. <cPickle.Pickler object at 0x89b0bb8>  
  5. >>> pickler.dump(b)  
  6. <cPickle.Pickler object at 0x89b0bb8>  
  7. >>> f.close()  
  8. >>> f = file('temp.pkl', 'r')  
  9. >>> unpickler = pickle.Unpickler(f)  
  10. >>> c = unpickler.load()  
  11. >>> d = unpickler.load()  
  12. >>> c[2]  
  13. [3, 4, [1, 2, [...]]]  
  14. >>> d[2]  
  15. [1, 2, [3, 4, [...]]]  
  16. >>> c[2] is d  
  17.  
  18. >>> d[2] is c  

不可 pickle 的對象

一些對象類型是不可 pickle 的。例如,Python 不能 pickle 文件對象(或者任何帶有對文件對象引用的對象),因?yàn)?Python 在 unpickle 時(shí)不能保證它可以重建該文件的狀態(tài)(另一個(gè)示例比較難懂,在這類文章中不值得提出來)。試圖pickle 文件對象會(huì)導(dǎo)致以下錯(cuò)誤:

清單 10. 試圖 pickle 文件對象的結(jié)果 

  1. >>> f = file('temp.pkl', 'w')  
  2. >>> p = pickle.dumps(f)  
  3. Traceback (most recent call last):  
  4.   File "<input>", line 1, in ?  
  5.   File "/usr/lib/python2.2/copy_reg.py", line 57, in _reduce  
  6.     raise TypeError, "can't pickle %s objects" % base.__name__  
  7. TypeError: can't pickle file objects 

類實(shí)例

與 pickle 簡單對象類型相比,pickle 類實(shí)例要多加留意。這主要由于 Python 會(huì) pickle 實(shí)例數(shù)據(jù)(通常是 _dict_ 屬性)和類的名稱,而不會(huì) pickle 類的代碼。當(dāng) Python unpickle類的實(shí)例時(shí),它會(huì)試圖使用在 pickle 該實(shí)例時(shí)的確切的類名稱和模塊名稱(包括任何包的路徑前綴)導(dǎo)入包含該類定義的模塊。另外要注意,類定義必須出現(xiàn)在模塊的最頂層,這意味著它們不能是嵌套的類(在其它類或函數(shù)中定義的類)。

當(dāng) unpickle 類的實(shí)例時(shí),通常不會(huì)再調(diào)用它們的 _init_() 方法。相反,Python 創(chuàng)建一個(gè)通用類實(shí)例,并應(yīng)用已進(jìn)行過 pickle的實(shí)例屬性,同時(shí)設(shè)置該實(shí)例的_class_ 屬性,使其指向原來的類。

對 Python 2.2 中引入的新型類進(jìn)行 unpickle 的機(jī)制與原來的略有不同。雖然處理的結(jié)果實(shí)際上與對舊型類處理的結(jié)果相同,但Python 使用 copy_reg 模塊的 _reconstructor() 函數(shù)來恢復(fù)新型類的實(shí)例。

如果希望對新型或舊型類的實(shí)例修改缺省的 pickle 行為,則可以定義特殊的類的方法 _getstate_() 和 _setstate_() ,在保存和恢復(fù)類實(shí)例的狀態(tài)信息期間,Python會(huì)調(diào)用這些方法。在以下幾節(jié)中,我們會(huì)看到一些示例利用了這些特殊的方法。

現(xiàn)在,我們看一個(gè)簡單的類實(shí)例。首先,創(chuàng)建一個(gè) persist.py 的 Python模塊,它包含以下新型類的定義:

清單 11. 新型類的定義 

  1. class Foo(object):  
  2.     def __init__(self, value):  
  3.         self.value = value 

現(xiàn)在可以 pickle Foo 實(shí)例,并看一下它的表示:

清單 12. pickle Foo 實(shí)例 

  1. >>> import cPickle as pickle  
  2. >>> from Orbtech.examples.persist import Foo  
  3. >>> foo = Foo('What is a Foo?')  
  4. >>> p = pickle.dumps(foo)  
  5. >>> print p  
  6. ccopy_reg  
  7. _reconstructor  
  8. p1  
  9. (cOrbtech.examples.persist  
  10. Foo  
  11. p2  
  12. c__builtin__  
  13. object  
  14. p3  
  15. NtRp4  
  16. (dp5  
  17. S'value'  
  18. p6  
  19. S'What is a Foo?'  
  20. sb.  
  21. >>> 

可以看到這個(gè)類的名稱 Foo 和全限定的模塊名稱 Orbtech.examples.persist 都存儲(chǔ)在 pickle中。如果將這個(gè)實(shí)例 pickle 成一個(gè)文件,稍后再 unpickle它或在另一臺(tái)機(jī)器上 unpickle,則 Python 會(huì)試圖導(dǎo)入Orbtech.examples.persist 模塊,如果不能導(dǎo)入,則會(huì)拋出異常。如果重命名該類和該模塊或者將該模塊移到另一個(gè)目錄,則也會(huì)發(fā)生類似的錯(cuò)誤。

這里有一個(gè) Python 發(fā)出錯(cuò)誤消息的示例,當(dāng)我們重命名 Foo 類,然后試圖裝入先前進(jìn)行過pickle 的 Foo 實(shí)例時(shí)會(huì)發(fā)生該錯(cuò)誤:

清單 13. 試圖裝入一個(gè)被重命名的 Foo 類的經(jīng)過 pickle 的實(shí)例 

  1. >>> import cPickle as pickle  
  2. >>> f = file('temp.pkl', 'r')  
  3. >>> foo = pickle.load(f)  
  4. Traceback (most recent call last):  
  5.   File "<input>", line 1, in ?  
  6. AttributeError: 'module' object has no attribute 'Foo'

在重命名 persist.py 模塊之后,也會(huì)發(fā)生類似的錯(cuò)誤:

清單 14. 試圖裝入一個(gè)被重命名的 persist.py 模塊的經(jīng)過 pickle 的實(shí)例 

  1. >>> import cPickle as pickle  
  2. >>> f = file('temp.pkl', 'r')  
  3. >>> foo = pickle.load(f)  
  4. Traceback (most recent call last):  
  5.   File "<input>", line 1, in ?  
  6. ImportError: No module named persist 

我們會(huì)在下面 模式改進(jìn)這一節(jié)提供一些技術(shù)來管理這類更改,而不會(huì)破壞現(xiàn)有的 pickle。

特殊的狀態(tài)方法

前面提到對一些對象類型(譬如,文件對象)不能進(jìn)行 pickle。處理這種不能pickle 的對象的實(shí)例屬性時(shí)可以使用特殊的方法( _getstate_() 和_setstate_() )來修改類實(shí)例的狀態(tài)。這里有一個(gè) Foo 類的示例,我們已經(jīng)對它進(jìn)行了修改以處理文件對象屬性:

清單 15. 處理不能 pickle 的實(shí)例屬性 

  1. class Foo(object):  
  2.     def __init__(self, value, filename):  
  3.         self.value = value  
  4.         self.logfile = file(filename, 'w')  
  5.     def __getstate__(self):  
  6.         """Return state values to be pickled."""  
  7.         f = self.logfile  
  8.         return (self.value, f.name, f.tell())  
  9.     def __setstate__(self, state):  
  10.         """Restore state from the unpickled state values."""  
  11.         self.value, name, position = state  
  12.         f = file(name, 'w')  
  13.         f.seek(position)  
  14.         self.logfile = f 

pickle Foo 的實(shí)例時(shí),Python 將只 pickle 當(dāng)它調(diào)用該實(shí)例的 _getstate_() 方法時(shí)返回給它的值。類似的,在 unpickle 時(shí),Python 將提供經(jīng)過 unpickle 的值作為參數(shù)傳遞給實(shí)例的_setstate_() 方法。在 _setstate_() 方法內(nèi),可以根據(jù)經(jīng)過 pickle 的名稱和位置信息來重建文件對象,并將該文件對象分配給這個(gè)實(shí)例的logfile 屬性。

模式改進(jìn)

隨著時(shí)間的推移,您會(huì)發(fā)現(xiàn)自己必須要更改類的定義。如果已經(jīng)對某個(gè)類實(shí)例進(jìn)行了pickle,而現(xiàn)在又需要更改這個(gè)類,則您可能要檢索和更新那些實(shí)例,以便它們能在新的類定義下繼續(xù)正常工作。而我們已經(jīng)看到在對類或模塊進(jìn)行某些更改時(shí),會(huì)出現(xiàn)一些錯(cuò)誤。幸運(yùn)的是,pickle和 unpickle 過程提供了一些 hook,我們可以用它們來支持這種模式改進(jìn)的需要。

在這一節(jié),我們將探討一些方法來預(yù)測常見問題以及如何解決這些問題。由于不能 pickle類實(shí)例代碼,因此可以添加、更改和除去方法,而不會(huì)影響現(xiàn)有的經(jīng)過 pickle的實(shí)例。出于同樣的原因,可以不必?fù)?dān)心類的屬性。您必須確保包含類定義的代碼模塊在 unpickle環(huán)境中可用。同時(shí)還必須為這些可能導(dǎo)致 unpickle 問題的更改做好規(guī)劃,這些更改包括:更改類名、添加或除去實(shí)例的屬性以及改變類定義模塊的名稱或位置。

類名的更改

要更改類名,而不破壞先前經(jīng)過 pickle 的實(shí)例,請遵循以下步驟。首先,確保原來的類的定義沒有被更改,以便在 unpickle現(xiàn)有實(shí)例時(shí)可以找到它。不要更改原來的名稱,而是在與原來類定義所在的同一個(gè)模塊中,創(chuàng)建該類定義的一個(gè)副本,同時(shí)給它一個(gè)新的類名。然后使用實(shí)際的新類名來替代NewClassName ,將以下方法添加到原來類的定義中:

清單 16. 更改類名:添加到原來類定義的方法 

  1. def __setstate__(self, state):  
  2.     self.__dict__.update(state)  
  3.     self.__class__ = NewClassName 

當(dāng) unpickle 現(xiàn)有實(shí)例時(shí),Python 將查找原來類的定義,并調(diào)用實(shí)例的 _setstate_() 方法,同時(shí)將給新的類定義重新分配該實(shí)例的_class_ 屬性。一旦確定所有現(xiàn)有的實(shí)例都已經(jīng) unpickle、更新和重新 pickle 后,可以從源代碼模塊中除去舊的類定義。

屬性的添加和刪除

這些特殊的狀態(tài)方法 _getstate_() 和 _setstate_() 再一次使我們能控制每個(gè)實(shí)例的狀態(tài),并使我們有機(jī)會(huì)處理實(shí)例屬性中的更改。讓我們看一個(gè)簡單的類的定義,我們將向其添加和除去一些屬性。這是是最初的定義:

清單 17. 最初的類定義 

  1. class Person(object):  
  2.     def __init__(self, firstname, lastname):  
  3.         self.firstname = firstname  
  4.         self.lastname = lastname 

假定已經(jīng)創(chuàng)建并 pickle 了 Person 的實(shí)例,現(xiàn)在我們決定真的只想存儲(chǔ)一個(gè)名稱屬性,而不是分別存儲(chǔ)姓和名。這里有一種方式可以更改類的定義,它將先前經(jīng)過pickle 的實(shí)例遷移到新的定義:

清單 18. 新的類定義 

  1. class Person(object):  
  2.     def __init__(self, fullname):  
  3.         self.fullname = fullname  
  4.     def __setstate__(self, state):  
  5.         if 'fullname' not in state:  
  6.             first = ''  
  7.             last = ''  
  8.             if 'firstname' in state:  
  9.                 first = state['firstname']  
  10.                 del state['firstname']  
  11.             if 'lastname' in state:  
  12.                 last = state['lastname']  
  13.                 del state['lastname']  
  14.             self.fullname = " ".join([first, last]).strip()  
  15.         self.__dict__.update(state) 

在這個(gè)示例,我們添加了一個(gè)新的屬性 fullname ,并除去了兩個(gè)現(xiàn)有的屬性 firstname 和 lastname 。當(dāng)對先前進(jìn)行過 pickle 的實(shí)例執(zhí)行 unpickle 時(shí),其先前進(jìn)行過 pickle的狀態(tài)會(huì)作為字典傳遞給 _setstate_() ,它將包括firstname 和 lastname 屬性的值。接下來,將這兩個(gè)值組合起來,并將它們分配給新屬性 fullname 。在這個(gè)過程中,我們刪除了狀態(tài)字典中舊的屬性。更新和重新 pickle先前進(jìn)行過 pickle 的所有實(shí)例之后,現(xiàn)在可以從類定義中除去_setstate_() 方法。

模塊的修改

在概念上,模塊的名稱或位置的改變類似于類名稱的改變,但處理方式卻完全不同。那是因?yàn)槟K的信息存儲(chǔ)在 pickle中,而不是通過標(biāo)準(zhǔn)的 pickle 接口就可以修改的屬性。事實(shí)上,改變模塊信息的唯一辦法是對實(shí)際的 pickle文件本身執(zhí)行查找和替換操作。至于如何確切地去做,這取決于具體的操作系統(tǒng)和可使用的工具。很顯然,在這種情況下,您會(huì)想備份您的文件,以免發(fā)生錯(cuò)誤。但這種改動(dòng)應(yīng)該非常簡單,并且對二進(jìn)制pickle 格式進(jìn)行更改與對文本 pickle 格式進(jìn)行更改應(yīng)該一樣有效。

結(jié)束語

對象持久性依賴于底層編程語言的對象序列化能力。對于 Python 對象即意味著pickle。Python 的 pickle 為 Python 對象有效的持久性管理提供了健壯的和可靠的基礎(chǔ)。 

 

責(zé)任編輯:龐桂玉 來源: 馬哥Linux運(yùn)維
相關(guān)推薦

2013-07-09 10:18:58

VDI虛擬化

2009-09-27 09:55:38

Hibernate持久

2022-10-27 08:00:32

DockerAWS云容器

2009-09-23 15:25:08

Hibernate 3

2021-01-22 10:40:08

Linux文件內(nèi)存

2009-09-15 16:37:06

Google App 持久性

2017-05-08 08:32:51

2019-09-06 09:50:52

云存儲(chǔ)硬盤云服務(wù)

2021-06-02 08:00:00

MOSH開源工具

2021-12-29 14:04:14

惡意軟件黑客網(wǎng)絡(luò)攻擊

2023-12-08 08:26:05

數(shù)據(jù)存儲(chǔ)持久性

2014-06-05 14:41:05

亞馬遜AWS

2022-01-06 15:26:59

固件攻擊SSD硬盤惡意軟件

2009-09-24 16:39:20

Hibernate傳播

2020-12-11 11:40:37

RDBAOFRedis

2013-12-02 10:10:35

Python工具庫

2022-09-23 13:24:21

MySQL數(shù)據(jù)庫

2022-11-29 08:05:48

KubernetesPVCSI

2024-01-22 08:15:42

API協(xié)議設(shè)計(jì)

2023-07-07 08:16:53

Redis持久化
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)