because Hindley-Milner rocks
I should have suspected that memoization is one of those run-of-the-mill type decorators. You know what I mean, everyone has probably done one at some point. And just like everyone else mine isn’t perfect. Other notables are logging functionality and threading locks. So how can I differentiate myself?
An SO post just a bit ago asked how to create a method to guarantee that an object passed into a function remain unchanged. My solution was poor, I know. However, that’s not stopped me from iterating and attempting to improve.
A “constant” decorator
That is, a decorator that attempts to remove state changes to public items in the class’ dictionary. I should go down each object in the object hierarchy to guarantee true constness but then I might run into an infinite loop (a circular reference to an object higher in the object nesting.)
def constant( func ): def action( bar ): state = dict( bar.__dict__ ) try: return func( bar ) finally: if( bar.__dict__ != state ): bar.__dict__ = state return action
For a first attempt, not bad but far from perfect. I see 5 issues:
Shallow copies are never good if we’re trying to preserve state. Luckily Python has two modules which provide mechanisms to obtain a deep copy: pickle and copy. Since I don’t need the effort of serializing and deserializing I’ll go with copy.
from copy import deepcopy def constant( func ): def action( bar ): state = deepcopy( bar ) try: return func( bar ) finally: if( bar.__dict__ != state.__dict__ ): bar.__dict__ = state.__dict__ return action
This is an improvement. The careful bookkeeping and the use of the __new__ method by deepcopy avoids the issues that could occur with shared objects. More importantly I don’t have to reinvent the wheel. Unfortunately I’ve only added to the issues:
All said and done I can live with the limitations deepcopy imposes on me. These are the same issues faced with object serialization via pickle and most developers should be aware of such limitations. In that same light I don’t believe you can deepcopy a generator object which leaves me very little left to accomplish:
@decorator def constant( func, *args, **keywords ): argState = map( deepcopy, args ) keywordState = dict( izip( keywords.iterkeys(), imap( deepcopy, keywords.itervalues() ) ) ) try: return func( args, keywords ) finally: for i in xrange( 0, len( args ) ): args[ i ].__dict__ = argState.__dict__ for key in keywords.iterkeys(): keywords[ key ].__dict__ = keywordState[ key ].__dict__
The third iteration is starting to come around. The decorator decorator removes one level of nesting, wraps the __module__ and the like, and finally provides access to arbitrary argument function. It’s surely not done yet but since this post has been sitting here for over a week and it’s 11:40 at night I’ll come back and finish it another time. I’ve been very busy as of late.