I personally would implement it like this:
#! /usr/bin/python3
import copy
class Room:
def __init__ (self, state = 'Initial'):
self.state = state
self.history = []
def morph (self, state):
clone = copy.deepcopy (self)
self.state = state
self.history.append (clone)
Keep in mind, that I don't know if your real setup has some features that restrict deep copying.
This yields:
>>> r = Room ('Kitchen')
>>> r.morph ('Loo')
>>> r.morph ('Spaceship')
>>> r.state
'Spaceship'
>>> [a.state for a in r.history]
['Kitchen', 'Loo']
>>> [type (a) for a in r.history]
[<class 'test.Room'>, <class 'test.Room'>]
I guess normally you don't need to save the whole state of an object, but only attributes which are worth tracking. You could pack this behaviour into a decorator along these lines:
#! /usr/bin/python3
import datetime
import copy
class Track:
def __init__ (self, *args, saveState = False):
self.attrs = args
self.saveState = saveState
def __call__ (self, cls):
cls._track = []
this = self
oGetter = cls.__getattribute__
def getter (self, attr):
if attr == 'track': return self._track
if attr == 'trackTrace': return '\n'.join ('{}: "{}" has changed to "{}"'.format (*t) for t in self._track)
return oGetter (self, attr)
cls.__getattribute__ = getter
oSetter = cls.__setattr__
def setter (self, attr, value):
if attr in this.attrs:
self._track.append ( (datetime.datetime.now (), attr, copy.deepcopy (value) if this.saveState else value) )
return oSetter (self, attr, value)
cls.__setattr__ = setter
return cls
Now we can use this decorator like this:
@Track ('holder')
class Book:
def __init__ (self, title):
self.title = title
self.holder = None
self.price = 8
class Person:
def __init__ (self, firstName, lastName):
self.firstName = firstName
self.lastName = lastName
def __str__ (self):
return '{} {}'.format (self.firstName, self.lastName)
r = Book ('The Hitchhiker\'s Guide to the Galaxy')
p = Person ('Pedro', 'Párramo')
q = Person ('María', 'Del Carmen')
r.holder = p
r.price = 12
r.holder = q
q.lastName = 'Del Carmen Orozco'
print (r.trackTrace)
If called with @Track ('holder'), it yields:
2013-10-01 14:02:43.748855: "holder" has changed to "None"
2013-10-01 14:02:43.748930: "holder" has changed to "Pedro Párramo"
2013-10-01 14:02:43.748938: "holder" has changed to "María Del Carmen Orozco"
If called with @Track ('holder', 'price'), it yields:
2013-10-01 14:05:59.433086: "holder" has changed to "None"
2013-10-01 14:05:59.433133: "price" has changed to "8"
2013-10-01 14:05:59.433142: "holder" has changed to "Pedro Párramo"
2013-10-01 14:05:59.433147: "price" has changed to "12"
2013-10-01 14:05:59.433151: "holder" has changed to "María Del Carmen Orozco"
If called with @Track ('holder', saveState = True), it yields:
2013-10-01 14:06:36.815674: "holder" has changed to "None"
2013-10-01 14:06:36.815710: "holder" has changed to "Pedro Párramo"
2013-10-01 14:06:36.815779: "holder" has changed to "María Del Carmen"
def __init__(self,current="Any", history = []):means that eachroomwhich isn't passed a value forhistoryupon initialization will share the same history. IOW, onehistorylist is constructed when thedefis executed, not each time an object is made. Read this question about mutable default arguments for the details.