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:
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 (
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: