常見的資料交換檔案格式 csv, xml, 與 json 等都是以人類可讀之字串形式儲存, 可透過相關套件的編解碼功能讀取後轉成 Python 的資料型別. Python 內建一個 pickle 套件可用來將 Python 原生資料型態以序列化 (serialized) 的方式寫入二進位檔 (通常使用 .pickle 或 .dat 為副檔名), 讀取時則依序直接還原成 Python 物件, 稱為反序列化 (先進先出).
二進位檔若以純文字編輯軟體開啟會看到一堆亂碼.
1. 檢視 pickle 套件內容 :
pickle 是 Python 內建標準函式庫,可直接匯入使用 :
>>> import pickle
使用下列自訂模組 members.py 來檢視其內容 :
# members.py
import inspect
def varname(x):
return [k for k,v in inspect.currentframe().f_back.f_locals.items() if v is x][0]
def list_members(parent_obj):
members=dir(parent_obj)
parent_obj_name=varname(parent_obj)
for mbr in members:
child_obj=eval(parent_obj_name + '.' + mbr)
if not mbr.startswith('_'):
print(mbr, type(child_obj))
將此函式存成 members.py 模組, 放在目前供作目錄下, 然後匯入其 list_members() 函式來檢視 pickle 模組 :
>>> list_members(pickle)
ADDITEMS <class 'bytes'>
APPEND <class 'bytes'>
APPENDS <class 'bytes'>
BINBYTES <class 'bytes'>
BINBYTES8 <class 'bytes'>
BINFLOAT <class 'bytes'>
BINGET <class 'bytes'>
BININT <class 'bytes'>
BININT1 <class 'bytes'>
BININT2 <class 'bytes'>
BINPERSID <class 'bytes'>
BINPUT <class 'bytes'>
BINSTRING <class 'bytes'>
BINUNICODE <class 'bytes'>
BINUNICODE8 <class 'bytes'>
BUILD <class 'bytes'>
BYTEARRAY8 <class 'bytes'>
DEFAULT_PROTOCOL <class 'int'>
DICT <class 'bytes'>
DUP <class 'bytes'>
EMPTY_DICT <class 'bytes'>
EMPTY_LIST <class 'bytes'>
EMPTY_SET <class 'bytes'>
EMPTY_TUPLE <class 'bytes'>
EXT1 <class 'bytes'>
EXT2 <class 'bytes'>
EXT4 <class 'bytes'>
FALSE <class 'bytes'>
FLOAT <class 'bytes'>
FRAME <class 'bytes'>
FROZENSET <class 'bytes'>
FunctionType <class 'type'>
GET <class 'bytes'>
GLOBAL <class 'bytes'>
HIGHEST_PROTOCOL <class 'int'>
INST <class 'bytes'>
INT <class 'bytes'>
LIST <class 'bytes'>
LONG <class 'bytes'>
LONG1 <class 'bytes'>
LONG4 <class 'bytes'>
LONG_BINGET <class 'bytes'>
LONG_BINPUT <class 'bytes'>
MARK <class 'bytes'>
MEMOIZE <class 'bytes'>
NEWFALSE <class 'bytes'>
NEWOBJ <class 'bytes'>
NEWOBJ_EX <class 'bytes'>
NEWTRUE <class 'bytes'>
NEXT_BUFFER <class 'bytes'>
NONE <class 'bytes'>
OBJ <class 'bytes'>
PERSID <class 'bytes'>
POP <class 'bytes'>
POP_MARK <class 'bytes'>
PROTO <class 'bytes'>
PUT <class 'bytes'>
PickleBuffer <class 'type'>
PickleError <class 'type'>
Pickler <class 'type'>
PicklingError <class 'type'>
PyStringMap <class 'NoneType'>
READONLY_BUFFER <class 'bytes'>
REDUCE <class 'bytes'>
SETITEM <class 'bytes'>
SETITEMS <class 'bytes'>
SHORT_BINBYTES <class 'bytes'>
SHORT_BINSTRING <class 'bytes'>
SHORT_BINUNICODE <class 'bytes'>
STACK_GLOBAL <class 'bytes'>
STOP <class 'bytes'>
STRING <class 'bytes'>
TRUE <class 'bytes'>
TUPLE <class 'bytes'>
TUPLE1 <class 'bytes'>
TUPLE2 <class 'bytes'>
TUPLE3 <class 'bytes'>
UNICODE <class 'bytes'>
Unpickler <class 'type'>
UnpicklingError <class 'type'>
bytes_types <class 'tuple'>
codecs <class 'module'>
compatible_formats <class 'list'>
decode_long <class 'function'>
dispatch_table <class 'dict'>
dump <class 'builtin_function_or_method'>
dumps <class 'builtin_function_or_method'>
encode_long <class 'function'>
format_version <class 'str'>
io <class 'module'>
islice <class 'type'>
load <class 'builtin_function_or_method'>
loads <class 'builtin_function_or_method'>
maxsize <class 'int'>
pack <class 'builtin_function_or_method'>
partial <class 'type'>
re <class 'module'>
sys <class 'module'>
unpack <class 'builtin_function_or_method'>
whichmodule <class 'function'>
常用的函式如下表 :
pickle 常用函式 | 說明 |
dump(obj, file) | 將物件 obj 寫入二進檔 file 中 (序列化) |
dumps(obj) | 將物件 obj 轉成二進位 (Byte 型別) 傳回 |
load(file) | 從二進檔 file 依序 (先進先出) 讀取物件傳回 |
loads(byte) | 從 Byte 型別資料讀取, 傳回物件 |
其中最常用的是 load() 與 dump() 函式. 注意, 讀寫 pickle 檔時須使用 "rb" 或 "wb" 模式開啟檔案.
2. 讀寫 pickle 檔 :
呼叫 pickle.dump() 可將物件依序寫入 pickle 檔, 呼叫 pickle.load() 則按先進先出順序讀取 pickle 檔內的物件, 例如 :
>>> import pickle
>>> with open('test.pickle', 'wb') as f:
pickle.dump(123, f)
pickle.dump([True, False], f)
pickle.dump({'a':1, 'b': 2}, f)
上面程式碼依序將 123, [True, False], 與 {'a': 1, 'b': 2} 這三個物件寫入二進位檔 test.pickle, 完成後會在目前工作目錄下建立一個 test.pickle 檔, 檢視內容會發現這三個物件占用了 51 個 bytes :
如果用記事本編輯將會看到一堆亂碼 :
呼叫 pickle.load() 則可從 pickle 檔依序讀回物件 (先進先出) :
>>> with open('test.pickle', 'rb') as f:
obj1=pickle.load(f)
obj2=pickle.load(f)
obj3=pickle.load(f)
print(obj1, type(obj1))
print(obj2, type(obj2))
print(obj3, type(obj3))
123 <class 'int'>
[True, False] <class 'list'>
{'a': 1, 'b': 2} <class 'dict'>
可見呼叫 load() 時是從 pickle 檔中按先進先出順序將儲存的物件讀出還原為 Python 物件.
pickle 檔唯一的缺點是無法得知裡面儲存了多少物件, 因此讀到檔尾時會拋出 EOFError 例外,例如上面的 pickle 檔內存有 3 個物件, 若做 4 次讀取動作就會出現例外 :
>>> with open('test.pickle', 'rb') as f:
obj1=pickle.load(f)
obj2=pickle.load(f)
obj3=pickle.load(f)
obj4=pickle.load(f)
print(obj1, type(obj1))
print(obj2, type(obj2))
print(obj3, type(obj3))
print(obj4, type(obj4))
Traceback (most recent call last):
File "<pyshell>", line 5, in <module>
EOFError: Ran out of input
因此, 保險的作法是用 try catch 捕捉讀寫例外, 例如 :
>>> objs=[]
>>> with open('test.pickle', 'rb') as f:
while True:
try:
obj=pickle.load(f)
except Exception as e:
print(f'載入 {len(objs)} 個物件')
break
print(obj, type(obj))
objs.append(obj)
123 <class 'int'>
[True, False] <class 'list'>
{'a': 1, 'b': 2} <class 'dict'>
載入 3 個物件
此例使用無窮迴圈來走訪 pickle 檔裡面讀取到的物件, 並將其存入一個串列, 當讀到檔尾拋出例外時便結束迴圈並計算已讀取的物件數量.
3. 讀寫 Byte 資料 :
pickle.loads() 與 pickle.dumps() 可用來讀寫 Byte 型態資料, 例如 :
>>> b1=pickle.dumps(123)
>>> b1
b'\x80\x04K{.'
>>> type(b1)
<class 'bytes'>
>>> b2=[True, False]
>>> b2=pickle.dumps([True, False])
>>> b2
b'\x80\x04\x95\x07\x00\x00\x00\x00\x00\x00\x00]\x94(\x88\x89e.'
>>> type(b2)
<class 'bytes'>
>>> b3=pickle.dumps({'a':1, 'b': 2})
>>> b3
b'\x80\x04\x95\x11\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x01a\x94K\x01\x8c\x01b\x94K\x02u.'
>>> type(b3)
<class 'bytes'>
呼叫 pickle.loads() 並傳入上面用 dumps() 得到的 Bytes 物件就可以將其還原為原生的物件 :
>>> pickle.loads(b1)
123
>>> type(pickle.loads(b1))
<class 'int'>
>>> pickle.loads(b2)
[True, False]
>>> type(pickle.loads(b2))
<class 'list'>
>>> pickle.loads(b3)
{'a': 1, 'b': 2}
>>> type(pickle.loads(b3))
<class 'dict'>
沒有留言:
張貼留言