Statically Typed

because Hindley-Milner rocks

Enforcing Parameter Constness in Python with Decorators?


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:

  1. It doesn’t work with objects lacking a __dict__ method.
  2. It doesn’t work on iterators (or generators.)
  3. It performs a shallow copy.
  4. It only handles a single argument.
  5. I’m not wrapping the function call like I should.

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:

  1. I haven’t addressed the failure to handle iterators (generators too.)
  2. It still only handles a single argument.
  3. I’m not wrapping the function call like I should.
  4. If an object defines its own semantics for __deepcopy__ I may not retrieve a deep copy at all.
  5. deepcopy “…does not copy types like module, method, stack trace, stack frame, file, socket, window, array or any similar types.”
  6. I still require that bar posses a __dict__.

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.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Information

This entry was posted on June 23, 2010 by in Python.
%d bloggers like this: