| # Simple example presenting how persistent ID can be used to pickle | 
 | # external objects by reference. | 
 |  | 
 | import pickle | 
 | import sqlite3 | 
 | from collections import namedtuple | 
 |  | 
 | # Simple class representing a record in our database. | 
 | MemoRecord = namedtuple("MemoRecord", "key, task") | 
 |  | 
 | class DBPickler(pickle.Pickler): | 
 |  | 
 |     def persistent_id(self, obj): | 
 |         # Instead of pickling MemoRecord as a regular class instance, we emit a | 
 |         # persistent ID. | 
 |         if isinstance(obj, MemoRecord): | 
 |             # Here, our persistent ID is simply a tuple, containing a tag and a | 
 |             # key, which refers to a specific record in the database. | 
 |             return ("MemoRecord", obj.key) | 
 |         else: | 
 |             # If obj does not have a persistent ID, return None. This means obj | 
 |             # needs to be pickled as usual. | 
 |             return None | 
 |  | 
 |  | 
 | class DBUnpickler(pickle.Unpickler): | 
 |  | 
 |     def __init__(self, file, connection): | 
 |         super().__init__(file) | 
 |         self.connection = connection | 
 |  | 
 |     def persistent_load(self, pid): | 
 |         # This method is invoked whenever a persistent ID is encountered. | 
 |         # Here, pid is the tuple returned by DBPickler. | 
 |         cursor = self.connection.cursor() | 
 |         type_tag, key_id = pid | 
 |         if type_tag == "MemoRecord": | 
 |             # Fetch the referenced record from the database and return it. | 
 |             cursor.execute("SELECT * FROM memos WHERE key=?", (str(key_id),)) | 
 |             key, task = cursor.fetchone() | 
 |             return MemoRecord(key, task) | 
 |         else: | 
 |             # Always raises an error if you cannot return the correct object. | 
 |             # Otherwise, the unpickler will think None is the object referenced | 
 |             # by the persistent ID. | 
 |             raise pickle.UnpicklingError("unsupported persistent object") | 
 |  | 
 |  | 
 | def main(): | 
 |     import io | 
 |     import pprint | 
 |  | 
 |     # Initialize and populate our database. | 
 |     conn = sqlite3.connect(":memory:") | 
 |     cursor = conn.cursor() | 
 |     cursor.execute("CREATE TABLE memos(key INTEGER PRIMARY KEY, task TEXT)") | 
 |     tasks = ( | 
 |         'give food to fish', | 
 |         'prepare group meeting', | 
 |         'fight with a zebra', | 
 |         ) | 
 |     for task in tasks: | 
 |         cursor.execute("INSERT INTO memos VALUES(NULL, ?)", (task,)) | 
 |  | 
 |     # Fetch the records to be pickled. | 
 |     cursor.execute("SELECT * FROM memos") | 
 |     memos = [MemoRecord(key, task) for key, task in cursor] | 
 |     # Save the records using our custom DBPickler. | 
 |     file = io.BytesIO() | 
 |     DBPickler(file).dump(memos) | 
 |  | 
 |     print("Pickled records:") | 
 |     pprint.pprint(memos) | 
 |  | 
 |     # Update a record, just for good measure. | 
 |     cursor.execute("UPDATE memos SET task='learn italian' WHERE key=1") | 
 |  | 
 |     # Load the records from the pickle data stream. | 
 |     file.seek(0) | 
 |     memos = DBUnpickler(file, conn).load() | 
 |  | 
 |     print("Unpickled records:") | 
 |     pprint.pprint(memos) | 
 |  | 
 |  | 
 | if __name__ == '__main__': | 
 |     main() |