I was recently attended a couple of Python talks in Bangalore and I encountered a couple of things that got me thinking.

Copying lists with an empty slice operator

This is a python idiom that’s new to me, but apparently it’s used fairly commonly:

l1 = [1, 2, 3]
l2 = l1[:] # l2 is now a copy of l1

Whenever I’ve had to do this, I’ve always preferred to instead do:

l1 = [1, 2, 3]
l2 = list(l1)

I like my way better. The slice operator way assumes that the reader knows how it works (the fact that it makes a copy). To me, this isn’t the primary intention of the slice operator (the primary intention is to… create slices). My way shows the writer’s intent much more clearly: I want to make a new list, and oh by the way initialize it with the elements from this other list.

Default function arguments

This part blew my mind. It’s a little crazy that I haven’t discovered this until now.

Consider this code:

def bar(ret=[]):
    ret.append('foo')
    return ret

print bar()
print bar()

I would expect this to result in:

['foo']
['foo']

But, that isn’t what happens. Here’s what happens:

['foo']
['foo', 'foo']

Now if you think about it for a second, this kinda sorta makes sense. ret is initialized to an empty list when the function is created. Therefore, if you call bar multiple times, it’s the same ret instance that’s being appended to.

However, this is absolutely not the behavior I expect. I would like ret to be initialized when the function is called, not when the function is created, so I’m going to add this behavior to my list of python warts.

But, this is how things work and it’s unlikely this will change. Therefore, from now on, my default args will have either singletons (True, False, None, etc.) or immutable objects (strings, tuples, etc.).

A way to achieve the result I expect is:

def bar(ret=None):
    if ret is None:
        ret = []
    ret.append('foo')
    return ret

print bar()
print bar()

As expected, it produces:

['foo']
['foo']