在〈Python 3 Tutorial 第二堂(1)Unicode 支援、基本 I/O〉中談過基本輸入輸出,除了利用基本 I/O,自行決定要保存的運算結果之外,如果想直接保存物件狀態,在下次重新執行程式時讀取以恢復運算時必要的資料,這類的技術稱為物件序列化(Object serialization),在 Python 中,提供標準模組 pickle
、shelve
等來進行這方面的支援。
使用 pickle
如果要序列化 Python 物件,使用 pickle
模組會是比較好的方式,pickle
會記錄已經序列化的物件,如果後續有物件參考到相同物件,才不會再度被序列化。pickle
可以序列化使用者自定義的類別及實例,在格式方面,pickle
格證向後相容於新的 Python 版本。
cPickle
模組則是用 C 實作的模組,介面上與 pickle
相同,速度在理想上可達 pickle
的 1000 倍。
來看看使用 pickle
的一些程式範例,這個範例也示範了實作永續機制時的一種模式,用來序列化 DVD 物件的狀態:
class DVD:
def __init__(self, title, year=None,
duration=None, director_id=None):
self.title = title
self.year = year
self.duration = duration
self.director_id = director_id
self.filename = self.title.replace(' ', '_') + '.pkl'
def check_filename(self, filename):
if filename is not None:
self.filename = filename
這個 DVD
物件有 title
、year
、duration
、director_id
四個狀態,每個 DVD
物件會以 title
作主檔名,空白以底線取代,並加上 .pkl 副檔名進行儲存。接下來列出儲存物件的 save
方法:
def save(self, filename=None):
self.check_filename(filename)
fh = None
try:
data = (self.title, self.year,
self.duration, self.director_id)
fh = open(self.filename, 'wb')
pickle.dump(data, fh)
except (EnvironmentError, pickle.PicklingError) as err:
raise SaveError(str(err))
finally:
if fh is not None:
fh.close()
最主要地,你要以 'wb'
模式開啟檔案,然後使用 pickle.dump
進行物件序列化。程式中也使用了〈Python 3 Tutorial 第五堂(1)Shit happens!〉中談到的 try...except...finally
等語法,作了一些例外處理與資源收尾的動作。
接下來列出載入檔案 load
方法定義:
def load(self, filename=None):
self.check_filename(filename)
fh = None
try:
fh = open(self.filename, 'rb')
data = pickle.load(fh)
(self.title, self.year,
self.duration, self.director_id) = data
except (EnvironmentError, pickle.PicklingError) as err:
raise LoadError(str(err))
finally:
...
這次是讀取,因此你要用 'rb'
模式開啟檔案,然後使用 pickle.load
載入檔案。這個 DVD
物件可以這麼使用:
dvd1 = DVD('Python 3 Tutorial', 2016, 1, 'Justin Lin')
dvd1.save()
dvd2 = DVD('PyCon Tutorial')
dvd2.load()
print(dvd2)
使用 shelve
shelve
物件行為上像是字典的物件,值的部份可以是 pickle
模組可處理的 Python 物件。以下來看個實例,搭配 DAO 模式 來使用 shelve
模組的功能:
class DvdDao:
def __init__(self, shelve_name):
self.shelve_name = shelve_name
def save(self, dvd):
shelve_db = None
try:
shelve_db = shelve.open(self.shelve_name)
shelve_db[dvd.title] = (dvd.year,
dvd.duration, dvd.director_id)
shelve_db.sync()
finally:
if shelve_db is not None:
shelve_db.close()
save
方法中,主要是使用 shelve.open
來開啟永續化時的字典檔案,在指定鍵值之後,使用 sync
方法將資料從快取中寫回檔案。接下來列出的 DAO 方法實作也是類似的操作:
def all(self):
shelve_db = None
try:
shelve_db = shelve.open(self.shelve_name)
return [DVD(title, *shelve_db[title])
for title in sorted(shelve_db, key=str.lower)]
finally:
if shelve_db is not None:
shelve_db.close()
return []
def load(self, title):
shelve_db = None
try:
shelve_db = shelve.open(self.shelve_name)
if title in shelve_db:
return DVD(title, *shelve_db[title])
finally:
if shelve_db is not None:
shelve_db.close()
return None
def remove(self, title):
shelve_db = None
try:
shelve_db = shelve.open(self.shelve_name)
del shelve_db[title]
shelve_db.sync()
finally:
if shelve_db is not None:
shelve_db.close()
以下是個使用 DvdDao
的例子:
filename = 'dvd_library.slv'
dao = DvdDao(filename)
dvd1 = DVD('Python 2 Tutorial', 2013, 1, 'Justin Lin')
dvd2 = DVD('Python 3 Tutorial', 2016, 1, 'Justin Lin')
dao.save(dvd1)
dao.save(dvd2)
print(dao.all())
print(dao.load('Python 2 Tutorial'))
dao.remove('Python 3 Tutorial')
print dao.all()