ASPN ActiveState Programmer Network  
ActiveState, a division of Sophos
/ Home / Perl / PHP / Python / Tcl / XSLT /
/ Safari / My ASPN /
Cookbooks | Documentation | Mailing Lists | Modules | News Feeds | Products | User Groups
Submit Recipe
My Recipes

All Recipes
All Cookbooks


View by Category

Title: Memento Closure
Submitter: Zoran Isailovski (other recipes)
Last Updated: 2005/05/11
Version no: 1.2
Category: OOP

 

4 stars 3 vote(s)


Description:

The memento pattern is great for transaction-like processing. Having a handy implementation around might not be the worst thing.

Source: Text Source

import copy

def Memento(obj, deep=False):
   state = (copy.copy, copy.deepcopy)[bool(deep)](obj.__dict__)
   def Restore():
      obj.__dict__.clear()
      obj.__dict__.update(state)
   return Restore

class Transaction:
   """A transaction guard. This is realy just 
      syntactic suggar arount a memento closure.
   """
   deep = False
   def __init__(self, *targets):
      self.targets = targets
      self.Commit()
   def Commit(self):
      self.states = [Memento(target, self.deep) for target in self.targets]
   def Rollback(self):
      for state in self.states:
         state()

class transactional(object):
   """Adds transactional semantics to methods. Methods decorated 
      with @transactional will rollback to entry state upon exceptions.
   """
   def __init__(self, method):
      self.method = method
   def __get__(self, obj, T):
      def transaction(*args, **kwargs):
         state = Memento(obj)
         try:
            return self.method(obj, *args, **kwargs)
         except:
            state()
            raise
      return transaction

if __name__ == '__main__':

   class NumObj(object):
      def __init__(self, value):
         self.value = value
      def __repr__(self):
         return '<%s: %r>' % (self.__class__.__name__, self.value)
      def Increment(self):
         self.value += 1
      @transactional
      def DoStuff(self):
         self.value = '1111' # <- invalid value
         self.Increment()    # <- will fail and rollback

   print
   n = NumObj(-1)
   print n
   t = Transaction(n)
   try:
      for i in range(3):
         n.Increment()
         print n
      t.Commit()
      print '-- commited'
      for i in range(3):
         n.Increment()
         print n
      n.value += 'x' # will fail
      print n
   except:
      t.Rollback()
      print '-- rolled back'
   print n
   print '-- now doing stuff ...'
   try:
      n.DoStuff()
   except:
      print '-> doing stuff failed!'
      import traceback
      traceback.print_exc(0)
      pass
   print n

Discussion:

Closures offer truly charming solution opportunities. In this example, the Memento function returns a closure that keeps the originator as well as the captured state in its scope, and restores the originator's state when called. Now, according to the memento pattern, the object representing the originator's state should be opaque, so the only thing you should be able to do with it is return it to the originator for state restoration. Well, I think the presented closure is as close (rem: note that subtle wording ;) to this definition as it can be.

Okay, we knew this was going to be easy with python. (After all, it seems to be the perfect design pattern implementation language, as you can almost always find a way to implement a pattern in a generic and reusable way.) And, once we've got it, we can build on top of it all sorts of useful (or at least nice-sounding ;) things:

- Transaction guards that work with multiple objects
- Decorators to add transactional action semantics to methods (a transactional action either entirely succeeds or fails w/o changing system state)
- Undo/Redo semantics (by swapping before/after states)
- ...

Cheers and happy transacting!

[See Also]
Memento pattern description at http://en.wikipedia.org/wiki/Memento_pattern



Add comment

Number of comments: 3

Wrapping with a descriptor?, Oran Looney, 2007/05/31
Great recipe. Todd Proebsting mentions built-in local transaction support as a possible direction for future languages:
http://research.microsoft.com/~toddpro/papers/disruptive.ppt
I see you're using a descriptor class for the 'transactional' decorator. The following alternative:

def transactional(method):
    def wrappedMethod(self, *args, **kwargs):
        state = Memento(self)
        try:
            return self.method(*args, **kwargs)
        except:
            state()
            raise
seems a little shorter and clearer. It seems to work, at least for the provided test case. Is there a design constraint I'm missing? Perhaps it needs to be read-only?
I also have some style suggestions for Memento:
def Memento(obj, deep=False):
   # state = (copy.copy, copy.deepcopy)[bool(deep)](obj.__dict__)
   state = (copy.copy if deep else copy.deepcopy)(obj.__dict__)
   def Restore():
     # obj.__dict__.clear()
     # obj.__dict__.update(state)
       obj.__dict__ = state
   return Restore
The first suggestion is simply an update to the new 2.5 syntax. The second releases the orignal __dict__ for garbage collection and changes obj.__dict__ directly into a reference to state. state is part of the closure and will not have any other references. Again, great recipe; it really came in handy.
Add comment

Thx Oran, Zoran Isailovski, 2007/10/26
(and sorry for my VERY late response)

Obviously, you're a fan of closures - like me ;)

Concerning your suggestion to switch to the new 2.5 syntax: I'm rather conservative on this. I like recipes that are reasonably backward compatible, so people who are stuck to older python versions for legacy reasons can benefit, too. However, your suggestion is a nice hint for those NOT stuck to pre-2.5 versions. Thanks again.
Add comment

See also, Harald Hoyer, 2008/03/23
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/551788
Add comment



Highest rated recipes:

1. A simple XML-RPC server

2. Web service accessible ...

3. IPy Notify

4. Treat the Win32 Registry ...

5. a friendly mkdir()

6. Wrapping template engine ...

7. Assignment in expression

8. Changing return value ...

9. Implementation of sets ...

10. bag collection class




Privacy Policy | Email Opt-out | Feedback | Syndication
© 2006 ActiveState Software Inc. All rights reserved.