Python object history

懵懂的女人 提交于 2019-12-06 00:54:01

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"

This sounds like a case for a facade pattern - have an inner room object which captures current state, and use __setattr__ on the facade to capture assignments, archive the previous state (inner object), and create a new one with the updated property value. You'll want to override __getattr__ to delegate to the inner object, which will also have the appropriate methods for anything else.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!