Monday, October 31, 2005


Traps for Python players

I've just been tracking down errors. Before getting into the details, here is a quick pop-quiz. What is the difference between the functions f and g?
def f(x,y=0):
    y += 1
    print x,y
def g(x,y={}):
    y[x] = y.get(x,0) + 1
    print x,y[x]

What happens if the functions are called repeatedly? E.g.

print "f", f(0)
print "f", f(0)
print "g", g(0)
print "g", g(0)

The output is:

f 0 1
f 0 1
g 0 1
g 0 2

As you can see, the 'function' g is not a pure function. There are side-effects with the initialisation of parameter 'y' to a mutable object (in this case, a dictionary) so that repeated invocations have differing behaviours. If you want purely functional behaviour a direct way of working around this problem is to explicitly initialise each parameter. E.g.

> g(0,{})
0 1
> g(0,{})
0 1         <<< no side effect
> g(0,{0:27})
0 28

The problem with this approach is that it requires the caller to be aware of which default parameters are mutable, and that get mutated. A cleaner solution is to use a default value of None, and to explicitly establish the default when required inside the code body. This requires a small change to the code, but the caller no longer cares about the side-effect. Applying this idea to the function g() above gives the new code:

def g(x,y=None):
    if y is None: y = {}
    y[x] = y.get(x,0) + 1
    print x,y[x]

Now to the error that I had this morning. The function looked like:

def dostuff( t=Tree() ):
    # Add new nodes to the tree
    return tree

It all worked beautifully for a single invocation, but trying to repeatedly run the function in the same process failed for the reasons outlined above. I'm sure the original author of the code was aware of the side-effects, but it is certainly no fun for the poor sucker who has to track the errors down.

On a related issue, a feature that I would love to see in Python is a const modifier for function/method parameters that would raise an exception when an attempt is made to modify a mutable parameter. Whilst const would not solve the problem that I had this morning, it would make Python more readable given the subtle differences between mutable and immutable objects.


Made some quick updates to rip3, and removed the Windows specific code (CreateFile() etc...) with standard I/O functions - it all worked, and I was unable to recreate the error that I had earlier. I'll put it all down to a stupid error caused by sleep-deprived coding (note to self: go to bed at sensible times!). It would be nice to get rip3 running on other platforms, but there some endian issues to be resolved - the Minix 3 filesystem has been written for the x86 (little-endian) architecture, so there is a little bit of byte swapping to be done before it will run on Sun/Alpha/... platforms. Hope to release the changes by the end of the week.

Comments: Post a Comment

Links to this post:

Create a Link

<< Home

This page is powered by Blogger. Isn't yours?