持久性就是指保持對(duì)象,甚至在多次執(zhí)行同一程序之間也保持對(duì)象。通過(guò)本文,您會(huì)對(duì) Python對(duì)象的各種持久性機(jī)制(從關(guān)系數(shù)據(jù)庫(kù)到 Python 的 pickle以及其它機(jī)制)有一個(gè)總體認(rèn)識(shí)。另外,還會(huì)讓您更深一步地了解Python 的對(duì)象序列化能力。
什么是持久性?
持 久性的基本思想很簡(jiǎn)單。假定有一個(gè) Python 程序,它可能是一個(gè)管理日常待辦事項(xiàng)的程序,您希望在多次執(zhí)行這個(gè)程序之間可以保存應(yīng)用程序?qū)ο螅ùk事項(xiàng))。換句話說(shuō),您希望將對(duì)象存儲(chǔ)在磁盤(pán)上,便于 以后檢索。這就是持久性。要達(dá)到這個(gè)目的,有幾種方法,每一種方法都有其優(yōu)缺點(diǎn)。
例如,可以將對(duì)象數(shù)據(jù)存儲(chǔ)在某種格式的文本文件中,譬如 CSV 文件。或者可以用關(guān)系數(shù)據(jù)庫(kù),譬如 Gadfly、MySQL、PostgreSQL 或者 DB2。這些文件格式和數(shù)據(jù)庫(kù)都非常優(yōu)秀,對(duì)于所有這些存儲(chǔ)機(jī)制,Python 都有健壯的接口。
這 些存儲(chǔ)機(jī)制都有一個(gè)共同點(diǎn):存儲(chǔ)的數(shù)據(jù)是獨(dú)立于對(duì)這些數(shù)據(jù)進(jìn)行操作的對(duì)象和程序。這樣做的好處是,數(shù)據(jù)可以作為共享的資源,供其它應(yīng)用程序使用。缺點(diǎn) 是,用這種方式,可以允許其它程序訪問(wèn)對(duì)象的數(shù)據(jù),這違背了面向?qū)ο蟮姆庋b性原則 — 即對(duì)象的數(shù)據(jù)只能通過(guò)這個(gè)對(duì)象自身的公共(public)接口來(lái)訪問(wèn)。
另外,對(duì)于某些應(yīng)用程序,關(guān)系數(shù)據(jù)庫(kù) 方法可能不是很理想。尤其是,關(guān)系數(shù)據(jù)庫(kù)不理解對(duì)象。相反,關(guān)系數(shù)據(jù)庫(kù)會(huì)強(qiáng)行 使用自己的類型系統(tǒng)和關(guān)系數(shù)據(jù)模型(表),每張表包含一組元組(行),每行包含具有固定數(shù)目的靜態(tài)類型字段(列)。如果應(yīng)用程序的對(duì)象模型不能夠方便地轉(zhuǎn) 換到關(guān)系模型,那么在將對(duì)象映射到元組以及將元組映射回對(duì)象方面,會(huì)碰到一定難度。這種困難常被稱為阻礙性不匹配(impedence- mismatch)問(wèn)題。
一些經(jīng)過(guò) pickle 的 Python
pickle 模塊及其同類模塊 cPickle 向 Python 提供了 pickle 支持。后者是用 C 編碼的,它具有更好的性能,對(duì)于大多數(shù)應(yīng)用程序,推薦使用該模塊。我們將繼續(xù)討論 pickle ,但本文的示例實(shí)際是利用了 cPickle 。由于其中大多數(shù)示例要用 Python shell 來(lái)顯示,所以先展示一下如何導(dǎo)入 cPickle ,并可以作為 pickle 來(lái)引用它:
>>> import cPickle as pickle
現(xiàn)在已經(jīng)導(dǎo)入了該模塊,接下來(lái)讓我們看一下 pickle 接口。 pickle 模塊提供了以下函數(shù)對(duì): dumps(object) 返回一個(gè)字符串,它包含一個(gè) pickle 格式的對(duì)象; loads(string) 返回包含在 pickle 字符串中的對(duì)象; dump(object, file) 將對(duì)象寫(xiě)到文件,這個(gè)文件可以是實(shí)際的物理文件,但也可以是任何類似于文件的對(duì)象,這個(gè)對(duì)象具有 write() 方法,可以接受單個(gè)的字符串參數(shù); load(file) 返回包含在 pickle 文件中的對(duì)象。
缺省情況下, dumps() 和 dump() 使用可打印的 ASCII 表示來(lái)創(chuàng)建 pickle。兩者都有一個(gè) final 參數(shù)(可選),如果為 True ,則該參數(shù)指定用更快以及更小的二進(jìn)制表示來(lái)創(chuàng)建 pickle。 loads() 和 load() 函數(shù)自動(dòng)檢測(cè) pickle 是二進(jìn)制格式還是文本格式。
清單 1 顯示了一個(gè)交互式會(huì)話,這里使用了剛才所描述的 dumps() 和 loads() 函數(shù):
清單 1. dumps() 和 loads() 的演示
Welcome To PyCrust 0.7.2 - The Flakiest Python Shell
Sponsored by Orbtech - Your source for Python programming expertise.
Python 2.2.1 (#1, Aug 27 2002, 10:22:32)
[GCC 3.2 (Mandrake Linux 9.0 3.2-1mdk)] on linux-i386
Type "copyright", "credits" or "license" for more information.
>>> import cPickle as pickle
>>> t1 = ('this is a string', 42, [1, 2, 3], None)
>>> t1
('this is a string', 42, [1, 2, 3], None)
>>> p1 = pickle.dumps(t1)
>>> p1
"(S'this is a string'\nI42\n(lp1\nI1\naI2\naI3\naNtp2\n."
>>> print p1
(S'this is a string'
I42
(lp1
I1
aI2
aI3
aNtp2
.
>>> t2 = pickle.loads(p1)
>>> t2
('this is a string', 42, [1, 2, 3], None)
>>> p2 = pickle.dumps(t1, True)
>>> p2
'(U\x10this is a stringK*]q\x01(K\x01K\x02K\x03eNtq\x02.'
>>> t3 = pickle.loads(p2)
>>> t3
('this is a string', 42, [1, 2, 3], None)
注:該文本 pickle 格式很簡(jiǎn)單,這里就不解釋了。事實(shí)上,在 pickle 模塊中記錄了所有使用的約定。我們還應(yīng)該指出,在我們的示例中使用的都是簡(jiǎn)單對(duì)象,因此使用二進(jìn)制 pickle 格式不會(huì)在節(jié)省空間上顯示出太大的效率。然而,在實(shí)際使用復(fù)雜對(duì)象的系統(tǒng)中,您會(huì)看到,使用二進(jìn)制格式可以在大小和速度方面帶來(lái)顯著的改進(jìn)。
接下來(lái),我們看一些示例,這些示例用到了 dump() 和 load() ,它們使用文件和類似文件的對(duì)象。這些函數(shù)的操作非常類似于我們剛才所看到的 dumps() 和 loads() ,區(qū)別在于它們還有另一種能力 — dump() 函數(shù)能一個(gè)接著一個(gè)地將幾個(gè)對(duì)象轉(zhuǎn)儲(chǔ)到同一個(gè)文件。隨后調(diào)用 load() 來(lái)以同樣的順序檢索這些對(duì)象。清單 2 顯示了這種能力的實(shí)際應(yīng)用:
清單 2. dump() 和 load() 示例
>>> a1 = 'apple'
>>> b1 = {1: 'One', 2: 'Two', 3: 'Three'}
>>> c1 = ['fee', 'fie', 'foe', 'fum']
>>> f1 = file('temp.pkl', 'wb')
>>> pickle.dump(a1, f1, True)
>>> pickle.dump(b1, f1, True)
>>> pickle.dump(c1, f1, True)
>>> f1.close()
>>> f2 = file('temp.pkl', 'rb')
>>> a2 = pickle.load(f2)
>>> a2
'apple'
>>> b2 = pickle.load(f2)
>>> b2
{1: 'One', 2: 'Two', 3: 'Three'}
>>> c2 = pickle.load(f2)
>>> c2
['fee', 'fie', 'foe', 'fum']
>>> f2.close()
Pickle 的威力
到目前為止,我們講述了關(guān)于 pickle 方面的基本知識(shí)。在這一節(jié),將討論一些高級(jí)問(wèn)題,當(dāng)您開(kāi)始 pickle 復(fù)雜對(duì)象時(shí),會(huì)遇到這些問(wèn)題,其中包括定制類的實(shí)例。幸運(yùn)的是,Python 可以很容易地處理這種情形。
可移植性
從 空間和時(shí)間上說(shuō),Pickle 是可移植的。換句話說(shuō),pickle 文件格式獨(dú)立于機(jī)器的體系結(jié)構(gòu),這意味著,例如,可以在 Linux 下創(chuàng)建一個(gè) pickle,然后將它發(fā)送到在 Windows 或 Mac OS 下運(yùn)行的 Python 程序。并且,當(dāng)升級(jí)到更新版本的 Python 時(shí),不必?fù)?dān)心可能要廢棄已有的 pickle。Python 開(kāi)發(fā)人員已經(jīng)保證 pickle 格式將可以向后兼容 Python 各個(gè)版本。事實(shí)上,在 pickle 模塊中提供了有關(guān)目前以及所支持的格式方面的詳細(xì)信息:
清單 3. 檢索所支持的格式
>>> pickle.format_version
'1.3'
>>> pickle.compatible_formats
['1.0', '1.1', '1.2']
多個(gè)引用,同一對(duì)象
在 Python 中,變量是對(duì)象的引用。同時(shí),也可以用多個(gè)變量引用同一個(gè)對(duì)象。經(jīng)證明,Python 在用經(jīng)過(guò) pickle 的對(duì)象維護(hù)這種行為方面絲毫沒(méi)有困難,如清單 4 所示:
清單 4. 對(duì)象引用的維護(hù)
>>> a = [1, 2, 3]
>>> b = a
>>> a
[1, 2, 3]
>>> b
[1, 2, 3]
>>> a.append(4)
>>> a
[1, 2, 3, 4]
>>> b
[1, 2, 3, 4]
>>> c = pickle.dumps((a, b))
>>> d, e = pickle.loads(c)
>>> d
[1, 2, 3, 4]
>>> e
[1, 2, 3, 4]
>>> d.append(5)
>>> d
[1, 2, 3, 4, 5]
>>> e
[1, 2, 3, 4, 5]
循環(huán)引用和遞歸引用
可以將剛才演示過(guò)的對(duì)象引用支持?jǐn)U展到 循環(huán)引用(兩個(gè)對(duì)象各自包含對(duì)對(duì)方的引用)和 遞歸引用(一個(gè)對(duì)象包含對(duì)其自身的引用)。下面兩個(gè)清單著重顯示這種能力。我們先看一下遞歸引用:
>清單 5. 遞歸引用
>>> l = [1, 2, 3]
>>> l.append(l)
>>> l
[1, 2, 3, [...]]
>>> l[3]
[1, 2, 3, [...]]
>>> l[3][3]
[1, 2, 3, [...]]
>>> p = pickle.dumps(l)
>>> l2 = pickle.loads(p)
>>> l2
[1, 2, 3, [...]]
>>> l2[3]
[1, 2, 3, [...]]
>>> l2[3][3]
[1, 2, 3, [...]]
現(xiàn)在,看一個(gè)循環(huán)引用的示例:
清單 6. 循環(huán)引用
>>> a = [1, 2]
>>> b = [3, 4]
>>> a.append(b)
>>> a
[1, 2, [3, 4]]
>>> b.append(a)
>>> a
[1, 2, [3, 4, [...]]]
>>> b
[3, 4, [1, 2, [...]]]
>>> a[2]
[3, 4, [1, 2, [...]]]
>>> b[2]
[1, 2, [3, 4, [...]]]
>>> a[2] is b
>>> b[2] is a
>>> f = file('temp.pkl', 'w')
>>> pickle.dump((a, b), f)
>>> f.close()
>>> f = file('temp.pkl', 'r')
>>> c, d = pickle.load(f)
>>> f.close()
>>> c
[1, 2, [3, 4, [...]]]
>>> d
[3, 4, [1, 2, [...]]]
>>> c[2]
[3, 4, [1, 2, [...]]]
>>> d[2]
[1, 2, [3, 4, [...]]]
>>> c[2] is d
>>> d[2] is c
注意,如果分別 pickle 每個(gè)對(duì)象,而不是在一個(gè)元組中一起 pickle 所有對(duì)象,會(huì)得到略微不同(但很重要)的結(jié)果,如清單 7 所示:
清單 7. 分別 pickle vs. 在一個(gè)元組中一起 pickle
>>> f = file('temp.pkl', 'w')
>>> pickle.dump(a, f)
>>> pickle.dump(b, f)
>>> f.close()
>>> f = file('temp.pkl', 'r')
>>> c = pickle.load(f)
>>> d = pickle.load(f)
>>> f.close()
>>> c
[1, 2, [3, 4, [...]]]
>>> d
[3, 4, [1, 2, [...]]]
>>> c[2]
[3, 4, [1, 2, [...]]]
>>> d[2]
[1, 2, [3, 4, [...]]]
>>> c[2] is d
>>> d[2] is c
相等,但并不總是相同
正如在上一個(gè)示例所暗示的,只有在這些對(duì)象引用內(nèi)存中同一個(gè)對(duì)象時(shí),它們才是相同的。在 pickle 情形中,每個(gè)對(duì)象被恢復(fù)到一個(gè)與原來(lái)對(duì)象相等的對(duì)象,但不是同一個(gè)對(duì)象。換句話說(shuō),每個(gè) pickle 都是原來(lái)對(duì)象的一個(gè)副本:
清單 8. 作為原來(lái)對(duì)象副本的被恢復(fù)的對(duì)象
>>> j = [1, 2, 3]
>>> k = j
>>> k is j
>>> x = pickle.dumps(k)
>>> y = pickle.loads(x)
>>> y
[1, 2, 3]
>>> y == k
>>> y is k
>>> y is j
>>> k is j
同時(shí),我們看到 Python 能夠維護(hù)對(duì)象之間的引用,這些對(duì)象是作為一個(gè)單元進(jìn)行 pickle 的。然而,我們還看到分別調(diào)用 dump() 會(huì)使 Python 無(wú)法維護(hù)對(duì)在該單元外部進(jìn)行 pickle 的對(duì)象的引用。相反,Python 復(fù)制了被引用對(duì)象,并將副本和被 pickle 的對(duì)象存儲(chǔ)在一起。對(duì)于 pickle 和恢復(fù)單個(gè)對(duì)象層次結(jié)構(gòu)的應(yīng)用程序,這是沒(méi)有問(wèn)題的。但要意識(shí)到還有其它情形。
值得指出的是,有一個(gè)選項(xiàng)確實(shí)允許分別 pickle 對(duì)象,并維護(hù)相互之間的引用,只要這些對(duì)象都是 pickle 到同一文件即可。 pickle 和 cPickle 模塊提供了一個(gè) Pickler (與此相對(duì)應(yīng)是 Unpickler ),它能夠跟蹤已經(jīng)被 pickle 的對(duì)象。通過(guò)使用這個(gè) Pickler ,將會(huì)通過(guò)引用而不是通過(guò)值來(lái) pickle 共享和循環(huán)引用:
清單 9. 維護(hù)分別 pickle 的對(duì)象間的引用
>>> f = file('temp.pkl', 'w')
>>> pickler = pickle.Pickler(f)
>>> pickler.dump(a)
<cPickle.Pickler object at 0x89b0bb8>
>>> pickler.dump(b)
<cPickle.Pickler object at 0x89b0bb8>
>>> f.close()
>>> f = file('temp.pkl', 'r')
>>> unpickler = pickle.Unpickler(f)
>>> c = unpickler.load()
>>> d = unpickler.load()
>>> c[2]
[3, 4, [1, 2, [...]]]
>>> d[2]
[1, 2, [3, 4, [...]]]
>>> c[2] is d
>>> d[2] is c
不可 pickle 的對(duì)象
一 些對(duì)象類型是不可 pickle 的。例如,Python 不能 pickle 文件對(duì)象(或者任何帶有對(duì)文件對(duì)象引用的對(duì)象),因?yàn)?Python 在 unpickle 時(shí)不能保證它可以重建該文件的狀態(tài)(另一個(gè)示例比較難懂,在這類文章中不值得提出來(lái))。試圖 pickle 文件對(duì)象會(huì)導(dǎo)致以下錯(cuò)誤:
清單 10. 試圖 pickle 文件對(duì)象的結(jié)果
>>> f = file('temp.pkl', 'w')
>>> p = pickle.dumps(f)
Traceback (most recent call last):
File "<input>", line 1, in ?
File "/usr/lib/python2.2/copy_reg.py", line 57, in _reduce
raise TypeError, "can't pickle %s objects" % base.__name__
TypeError: can't pickle file objects
類實(shí)例
與 pickle 簡(jiǎn)單對(duì)象類型相比,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)行過(guò) pickle 的實(shí)例屬性,同時(shí)設(shè)置該實(shí)例的 _class_ 屬性,使其指向原來(lái)的類。
對(duì) Python 2.2 中引入的新型類進(jìn)行 unpickle 的機(jī)制與原來(lái)的略有不同。雖然處理的結(jié)果實(shí)際上與對(duì)舊型類處理的結(jié)果相同,但 Python 使用 copy_reg 模塊的 _reconstructor() 函數(shù)來(lái)恢復(fù)新型類的實(shí)例。
如果希望對(duì)新型或舊型類的實(shí)例修改缺省的 pickle 行為,則可以定義特殊的類的方法 _getstate_() 和 _setstate_() ,在保存和恢復(fù)類實(shí)例的狀態(tài)信息期間,Python 會(huì)調(diào)用這些方法。在以下幾節(jié)中,我們會(huì)看到一些示例利用了這些特殊的方法。
現(xiàn)在,我們看一個(gè)簡(jiǎn)單的類實(shí)例。首先,創(chuàng)建一個(gè) persist.py 的 Python 模塊,它包含以下新型類的定義:
清單 11. 新型類的定義
class Foo(object):
def __init__(self, value):
self.value = value
現(xiàn)在可以 pickle Foo 實(shí)例,并看一下它的表示:
清單 12. pickle Foo 實(shí)例
>>> import cPickle as pickle
>>> from Orbtech.examples.persist import Foo
>>> foo = Foo('What is a Foo?')
>>> p = pickle.dumps(foo)
>>> print p
ccopy_reg
_reconstructor
p1
(cOrbtech.examples.persist
Foo
p2
c__builtin__
object
p3
NtRp4
(dp5
S'value'
p6
S'What is a Foo?'
sb.
>>>
可以看到這個(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)行過(guò) pickle 的 Foo 實(shí)例時(shí)會(huì)發(fā)生該錯(cuò)誤:
清單 13. 試圖裝入一個(gè)被重命名的 Foo 類的經(jīng)過(guò) pickle 的實(shí)例
>>> import cPickle as pickle
>>> f = file('temp.pkl', 'r')
>>> foo = pickle.load(f)
Traceback (most recent call last):
File "<input>", line 1, in ?
AttributeError: 'module' object has no attribute 'Foo'
在重命名 persist.py 模塊之后,也會(huì)發(fā)生類似的錯(cuò)誤:
清單 14. 試圖裝入一個(gè)被重命名的 persist.py 模塊的經(jīng)過(guò) pickle 的實(shí)例
>>> import cPickle as pickle
>>> f = file('temp.pkl', 'r')
>>> foo = pickle.load(f)
Traceback (most recent call last):
File "<input>", line 1, in ?
ImportError: No module named persist
我們會(huì)在下面 模式改進(jìn)這一節(jié)提供一些技術(shù)來(lái)管理這類更改,而不會(huì)破壞現(xiàn)有的 pickle。
特殊的狀態(tài)方法
前面提到對(duì)一些對(duì)象類型(譬如,文件對(duì)象)不能進(jìn)行 pickle。處理這種不能 pickle 的對(duì)象的實(shí)例屬性時(shí)可以使用特殊的方法( _getstate_() 和 _setstate_() )來(lái)修改類實(shí)例的狀態(tài)。這里有一個(gè) Foo 類的示例,我們已經(jīng)對(duì)它進(jìn)行了修改以處理文件對(duì)象屬性:
清單 15. 處理不能 pickle 的實(shí)例屬性
class Foo(object):
def __init__(self, value, filename):
self.value = value
self.logfile = file(filename, 'w')
def __getstate__(self):
"""Return state values to be pickled."""
f = self.logfile
return (self.value, f.name, f.tell())
def __setstate__(self, state):
"""Restore state from the unpickled state values."""
self.value, name, position = state
f = file(name, 'w')
f.seek(position)
self.logfile = f
模式改進(jìn)
隨 著時(shí)間的推移,您會(huì)發(fā)現(xiàn)自己必須要更改類的定義。如果已經(jīng)對(duì)某個(gè)類實(shí)例進(jìn)行了 pickle,而現(xiàn)在又需要更改這個(gè)類,則您可能要檢索和更新那些實(shí)例,以便它們能在新的類定義下繼續(xù)正常工作。而我們已經(jīng)看到在對(duì)類或模塊進(jìn)行某些更改 時(shí),會(huì)出現(xiàn)一些錯(cuò)誤。幸運(yùn)的是,pickle 和 unpickle 過(guò)程提供了一些 hook,我們可以用它們來(lái)支持這種模式改進(jìn)的需要。
在 這一節(jié),我們將探討一些方法來(lái)預(yù)測(cè)常見(jiàn)問(wèn)題以及如何解決這些問(wèn)題。由于不能 pickle 類實(shí)例代碼,因此可以添加、更改和除去方法,而不會(huì)影響現(xiàn)有的經(jīng)過(guò) pickle 的實(shí)例。出于同樣的原因,可以不必?fù)?dān)心類的屬性。您必須確保包含類定義的代碼模塊在 unpickle 環(huán)境中可用。同時(shí)還必須為這些可能導(dǎo)致 unpickle 問(wèn)題的更改做好規(guī)劃,這些更改包括:更改類名、添加或除去實(shí)例的屬性以及改變類定義模塊的名稱或位置。
類名的更改
要 更改類名,而不破壞先前經(jīng)過(guò) pickle 的實(shí)例,請(qǐng)遵循以下步驟。首先,確保原來(lái)的類的定義沒(méi)有被更改,以便在 unpickle 現(xiàn)有實(shí)例時(shí)可以找到它。不要更改原來(lái)的名稱,而是在與原來(lái)類定義所在的同一個(gè)模塊中,創(chuàng)建該類定義的一個(gè)副本,同時(shí)給它一個(gè)新的類名。然后使用實(shí)際的新類 名來(lái)替代 NewClassName ,將以下方法添加到原來(lái)類的定義中:
清單 16. 更改類名:添加到原來(lái)類定義的方法
def __setstate__(self, state):
self.__dict__.update(state)
self.__class__ = NewClassName
當(dāng) unpickle 現(xiàn)有實(shí)例時(shí),Python 將查找原來(lái)類的定義,并調(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è)簡(jiǎn)單的類的定義,我們將向其添加和除去一些屬性。這是是最初的定義:
清單 17. 最初的類定義
class Person(object):
def __init__(self, firstname, lastname):
self.firstname = firstname
self.lastname = lastname
假定已經(jīng)創(chuàng)建并 pickle 了 Person 的實(shí)例,現(xiàn)在我們決定真的只想存儲(chǔ)一個(gè)名稱屬性,而不是分別存儲(chǔ)姓和名。這里有一種方式可以更改類的定義,它將先前經(jīng)過(guò) pickle 的實(shí)例遷移到新的定義:
class Person(object):
def __init__(self, fullname):
self.fullname = fullname
def __setstate__(self, state):
if 'fullname' not in state:
first = ''
last = ''
if 'firstname' in state:
first = state['firstname']
del state['firstname']
if 'lastname' in state:
last = state['lastname']
del state['lastname']
self.fullname = " ".join([first, last]).strip()
self.__dict__.update(state)
在這個(gè)示例,我們添加了一個(gè)新的屬性 fullname ,并除去了兩個(gè)現(xiàn)有的屬性 firstname 和 lastname 。當(dāng)對(duì)先前進(jìn)行過(guò) pickle 的實(shí)例執(zhí)行 unpickle 時(shí),其先前進(jìn)行過(guò) pickle 的狀態(tài)會(huì)作為字典傳遞給 _setstate_() ,它將包括 firstname 和 lastname 屬性的值。接下來(lái),將這兩個(gè)值組合起來(lái),并將它們分配給新屬性 fullname 。在這個(gè)過(guò)程中,我們刪除了狀態(tài)字典中舊的屬性。更新和重新 pickle 先前進(jìn)行過(guò) pickle 的所有實(shí)例之后,現(xiàn)在可以從類定義中除去 _setstate_() 方法。
模塊的修改
在概念上,模塊的名稱或位置的改變類似于類名稱的改變,但處理方式卻完全不同。那是因?yàn)槟K的信息存儲(chǔ)在 pickle 中,而不是通過(guò)標(biāo)準(zhǔn)的 pickle 接口就可以修改的屬性。事實(shí)上,改變模塊信息的唯一辦法是對(duì)實(shí)際的 pickle 文件本身執(zhí)行查找和替換操作。至于如何確切地去做,這取決于具體的操作系統(tǒng)和可使用的工具。很顯然,在這種情況下,您會(huì)想備份您的文件,以免發(fā)生錯(cuò)誤。但 這種改動(dòng)應(yīng)該非常簡(jiǎn)單,并且對(duì)二進(jìn)制 pickle 格式進(jìn)行更改與對(duì)文本 pickle 格式進(jìn)行更改應(yīng)該一樣有效。
結(jié)束語(yǔ)
對(duì)象持久性依賴于底層編程語(yǔ)言的對(duì)象序列化能力。對(duì)于 Python 對(duì)象即意味著 pickle。Python 的 pickle 為 Python 對(duì)象有效的持久性管理提供了健壯的和可靠的基礎(chǔ)。在下面的 參考資料中,您將會(huì)找到有關(guān)建立在 Python pickle 能力之上的系統(tǒng)的信息。