Source From Here
Preface
這邊要介紹 copyreg 這個內建的 module (需 Python3+),搭配 pickle 使用的使用情境. 首先 pickle 使用上很簡單,假設我們有個 class:
我們可以使用
pickle 如下保存/讀取 object:
但是如果增加了新的 field,
game_state.bin load 回來的 object 當然不會有新的 field (points),可是它仍然是 GameState 的 instance,這會造成混亂。
使用
copyreg 可以解決這個問題,它可以註冊用來 serialize Python 物件的函式。
Default Attribute Values
pickle_game_state() 回傳一個 tuple ,包含了拿來 unpickle 的函式以及傳入該函式的引數。
Versioning Classes
copyreg 也可以拿來記錄版本,達到向後相容的目的。假設原先的 class 如下:
後來修改了,拿掉
lives ,這時原先使用預設參數的做法不能用了。
在 serialize 時多加上版號, deserialize 時加以判斷:
Stable Import Paths
重構程式時,如果 class 改名了,想要 load 舊的 serialized 物件當然不能用,但還是可以使用 copyreg 解決。
可以發現 unpickle_game_state() 的 path 寫入 dump 出來的資料中,當然這樣做的缺點就是 unpickle_game_state() 所在的 module 不能改 path 了。
輸出:
Full Example
底下為完整範例:
- demo.py
執行結果:
Preface
這邊要介紹 copyreg 這個內建的 module (需 Python3+),搭配 pickle 使用的使用情境. 首先 pickle 使用上很簡單,假設我們有個 class:
- class GameState(object):
- def __init__(self):
- self.level = 0
- self.lives = 4
- state = GameState()
- state.level += 1 # Player beat a level
- state.lives -= 1 # Player had to try again
- import pickle
- state_path = '/tmp/game_state.bin'
- with open(state_path, 'wb') as f:
- pickle.dump(state, f)
- with open(state_path, 'rb') as f:
- state_after = pickle.load(f)
- # {'lives': 3, 'level': 1}
- print(state_after.__dict__)
- class GameState(object):
- def __init__(self):
- self.level = 0
- self.lives = 4
- self.points = 0
- with open(state_path, 'rb') as :
- state_after = pickle.load(f)
- # {'lives': 3, 'level': 1}
- print(state_after.__dict__)
- assert isinstance(state_after, GameState)
Default Attribute Values
pickle_game_state() 回傳一個 tuple ,包含了拿來 unpickle 的函式以及傳入該函式的引數。
- import copyreg
- class GameState(object):
- def __init__(self, level=0, lives=4, points=0):
- self.level = level
- self.lives = lives
- self.points = points
- def pickle_game_state(game_state):
- kwargs = game_state.__dict__
- return unpickle_game_state, (kwargs,)
- def unpickle_game_state(kwargs):
- return GameState(**kwargs)
- copyreg.pickle(GameState, pickle_game_state)
copyreg 也可以拿來記錄版本,達到向後相容的目的。假設原先的 class 如下:
- class GameState(object):
- def __init__(self, level=0, lives=4, points=0, magic=5):
- self.level = level
- self.lives = lives
- self.points = points
- self.magic = magic
- state = GameState()
- state.points += 1000
- serialized = pickle.dumps(state)
- class GameState(object):
- def __init__(self, level=0, points=0, magic=5):
- self.level = level
- self.points = points
- self.magic = magic
- # TypeError: __init__() got an unexpected keyword argument 'lives'
- pickle.loads(serialized)
- def pickle_game_state(game_state):
- kwargs = game_state.__dict__
- kwargs['version'] = 2
- return unpickle_game_state, (kwargs,)
- def unpickle_game_state(kwargs):
- version = kwargs.pop('version', 1)
- if version == 1:
- kwargs.pop('lives')
- return GameState(**kwargs)
- copyreg.pickle(GameState, pickle_game_state)
重構程式時,如果 class 改名了,想要 load 舊的 serialized 物件當然不能用,但還是可以使用 copyreg 解決。
- class BetterGameState(object):
- def __init__(self, level=0, points=0, magic=5):
- self.level = level
- self.points = points
- self.magic = magic
- copyreg.pickle(BetterGameState, pickle_game_state)
- state = BetterGameState()
- serialized = pickle.dumps(state)
- print(serialized[:35])
Full Example
底下為完整範例:
- demo.py
- #!/usr/bin/env python3
- import copyreg
- import pickle
- state_path = '/tmp/game_state.bin'
- class GameState(object):
- def __init__(self, level=0, lives=4, points=0):
- self.level = level
- self.lives = lives
- self.points = points
- def __str__(self):
- return "Level{}; Lives={}; Points={}".format(self.level, self.lives, self.points)
- def pickle_game_state(game_state):
- kwargs = game_state.__dict__
- print('Call pickle_game_state:\n{}\n'.format(kwargs))
- return unpickle_game_state, (kwargs,)
- def unpickle_game_state(kwargs):
- print('Call unpickle_game_state:\n{}\n'.format(kwargs))
- return GameState(**kwargs)
- copyreg.pickle(GameState, pickle_game_state)
- gs = GameState()
- gs.points += 1000
- print('Serialized GameState...')
- with open(state_path, 'wb') as fh:
- pickle.dump(gs, fh)
- print('Unserialized GameState...')
- with open(state_path, 'rb') as fh:
- ngs = pickle.load(fh)
- print("New GameState:\n{}\n".format(str(ngs)))
沒有留言:
張貼留言