1# (C) Copyright 2005-2021 Enthought, Inc., Austin, TX 2# All rights reserved. 3# 4# This software is provided without warranty under the terms of the BSD 5# license included in LICENSE.txt and may be redistributed only under 6# the conditions described in the aforementioned license. The license 7# is also available online at http://www.enthought.com/licenses/BSD.txt 8# 9# Thanks for using Enthought open source! 10 11# --(__getstate__/__setstate__ Changes and Improvements)----------------------- 12""" 13__getstate__/__setstate__ Changes and Improvements 14================================================== 15 16Originally, the **HasTraits** class did not define specific *__getstate__* or 17*__setstate__* methods for dealing with *pickling* and *unpickling* of 18traits-based objects. However, in the course of developing a number of fairly 19large-scale applications using Traits, experience has shown that some traits 20specific support in this area would be of benefit to most application 21developers. 22 23Accordingly, Traits 3.0 introduces *__getstate__* and *__setstate__* methods 24that implement several traits aware serialization and deserialization policies. 25 26The *__getstate__* Method 27------------------------- 28 29One of the most frequently occurring requirements for serializing an object is 30the ability to control which parts of the object's state are saved, and which 31parts are discarded. 32 33One typical approach is to define a *__getstate__* method which makes a copy of 34the object's *__dict__* attribute and deletes those items which should not be 35saved. While this approach works, there are some drawbacks, especially in cases 36where heavy use of subclassing is used. 37 38The **HasTraits** *__getstate__* method uses a somewhat different approach by 39providing a generic implementation which implements *policies* that developers 40can customize through the use of traits *metadata*, in many cases completely 41eliminating the need to override or define a *__getstate__* method in their 42application classes. 43 44In particular, the **HasTraits** *__getstate__* method saves the value of all 45traits which do not have *transient = True* metadata defined. This policy 46allows developers to easily mark which trait values should not be saved simply 47by adding *transient = True* metadata to them. Besides avoiding having to 48write a *__getstate__* method for their class, this approach also provides 49good documentation about the *pickling* behavior of the class. 50 51For example:: 52 53 class DataBase(HasTraits): 54 55 # The name of the data base file: 56 file_name = File 57 58 # The open file handle used to access the data base: 59 file = Any(transient = True) 60 61In this example, the **DataBase** class's *file* trait has been mark as 62*transient* because it normally contains an open file handle used to access a 63data base. Since file handles typically cannot be pickled and restored, the 64file handle should not be saved as part of the object's persistent state. 65Normally, the file handle would be re-opened by application code after the 66object has been restored from its persisted state. 67 68Predefined *transient* Traits 69----------------------------- 70 71The Traits package automatically assigns *transient = True* metadata to a 72number of predefined traits, thus avoiding the need to explicitly mark them as 73transient yourself. 74 75The predefined traits marked as *transient* are: 76 77- **Constant**. 78- **Event**. 79- *read-only* or *write-only* **Property** traits. 80- The *xxx_* trait for *mapped* traits. 81- All *_xxx* traits for classes that subclass **HasPrivateTraits**. 82 83Also, by default, delegated traits are only saved if they have a local value 84which overrides the value defined by its delegate. You can set *transient = 85True* on the delegate trait if you do not want its value to ever be saved. 86 87Overriding *__getstate__* 88------------------------- 89 90In general, you should avoid overriding *__getstate__* in subclasses of 91**HasTraits**. Instead, mark traits that should not be pickled with 92*transient = True* metadata. 93 94However, in cases where this strategy is insufficient, we recommend 95overriding *__getstate__* using the follow pattern to remove items that should 96not be persisted:: 97 98 def __getstate__(self): 99 state = super().__getstate__() 100 101 for key in [ 'foo', 'bar' ]: 102 if key in state: 103 del state[ key ] 104 105 return state 106 107The *__setstate__* Method 108------------------------- 109 110The main difference between the default Python *__setstate__*-like behavior and 111the new **HasTraits** class *__setstate__* method is that the **HasTraits** 112*__setstate__* method actually *sets* the value of each trait using the values 113passed to it via its state dictionary argument instead of simply storing or 114copying the state dictionary to its *__dict__* attribute. 115 116While slower, this has the advantage of causing trait change notifications to 117be generated, which can be very useful for classes which rely of receiving 118notifications in order to ensure that their internal object state remains 119consistent and up to date. 120 121Overriding *__setstate__* 122------------------------- 123 124For classes which do not want to receive change notifications during 125*__setstate__*, it is possible to override *__setstate__* and update the 126object's *__dict__* attribute directly. 127 128However, in such cases it is important to either call the *__setstate__* super 129method (with an empty state dictionary, for example), or to call the 130**HasTraits** class's private *_init_trait_listeners* method directly. This 131method has no arguments and does not return a result, but it must be called 132during *__setstate__* in order to ensure that all dynamic trait change 133notifications managed by traits are correctly initialized for the object. 134Failure to call this method may result in lost change notifications. 135""" 136 137from time import time 138 139from traits.api import Any, HasTraits, Str 140 141 142# --[Session Class]------------------------------------------------------------ 143class Session(HasTraits): 144 145 # The name of the session: 146 name = Str 147 148 # The time the session was created: 149 created = Any(transient=True) 150 151 def _name_changed(self): 152 self.created = time() 153 154 155# --[Example*]----------------------------------------------------------------- 156 157# The following shows an example of pickling and unpickling a Session object. 158# Unfortunately, it is not possible to successfully pickle objects created as 159# part of a tutorial, because of problems with pickling objects derived from 160# classes dynamically defined using 'exec'. So just use your imagination on 161# this one... 162 163# Create a new session: 164session = Session(name="session_1") 165 166# Display its contents: 167print("Session name:", session.name) 168print("Session created:", session.created) 169 170# # Simulate saving the session to a file/database: 171# 172# from pickle import dumps, loads 173# from time import sleep 174# 175# saved_session = dumps(session) 176# 177# # Simulate the passage of time (zzzzZZZZ...): 178# sleep(1) 179# 180# # Simulate restoring the session from a file/database: 181# restored_session = loads(saved_session) 182# 183# # Display the restored sessions contents (note that the 'created' 184# # time should be different from the original session): 185# print 'Restored session name:', restored_session.name 186# print 'Restored session created:', restored_session.created 187