Common Python Pitfalls

This page is for students to record mistakes that commonly occur while learning Python.

Contents

Making a list of lists with [[1] * 2] * 3 ------------------------------------------

The result does indeed look like a list of lists:

*>>> x = [[1]*2]*3*
>>> x
[[1, 1],
 [1, 1],
 [1, 1]]

But the inner lists all reference the same list. You can see this by checking the memory location of the lists with id:

>>> id(x[0]) == id(x[1]) == id(x[2])
True

This can lead to unexpected behavior because modifying one item in x affects all items in x:

>>> x[1][0] = 2
>>> x
[[2, 1],
 [2, 1],
 [2, 1]]

One way to create a list of independent lists in Python is to use a list comprehension instead:

>>> x = [[1]*2 for _ in range(3)]
>>> x
1, 1], [1, 1], [1, 1

Now when you modify an item in one list, the other lists are not also modified:

>>> x[1][0] = 2
>>> x
1, 1], [2, 1], [1, 1

Beware Default Parameters of Mutable Types

This is a common pitfall for new and experienced Python developers. Lets start with an example:

>>> def foo(x=[]):
...     x.append(1)
...     print x
...
>>> foo()
[1]
>>> foo()
[1, 1]
>>> foo()
[1, 1, 1]

The default parameter x, is a list, which is a mutable type. So, why does foo keep appending instead of creating a new list? For a clue, lets look at the object the function is returning.

>>> id(function())
42415216
>>> id(function())
42415216
>>> id(function())
42415216

Here we can see that the id of the object being returned is identical each time, so this means that the function is using the same object over and over, and since it is mutable (i.e. changeable), we keep appending to it.

So why does this happen? It is because default parameter values are always evaluated when, and only when, the "def" statement they belong to is executed.

To understand how we can get the desired results, lets look at more code:

>>> def foo(x=None):
...     if x is None:
...         x = []
...     x.append(1)
...     print x
>>> foo()
[1]
>>> foo()
[1]

In summary, you should use a placeholder value denoting "not given" and replace with the mutable you'd like as default. In this case, we used 'None' as the placeholder, and created a new (mutable) list when it was found.

Take care with +=

For mutable objects, x+=y need not be the same as x = x + y.

Consider:

>>> x = [5,7,11]
>>> id(x)
47091224
>>> x += [4]
>>> id(x)
47091224
>>> x = x + [5]
>>> id(x)
32259608

x += y changes the list in-place, similar to the extend method. x = x+y creates a new list and rebinds it to x.

Care needs to be taken when mixing mutable and immutable objects.

Consider:

>>> tp = ([1],[2],)
>>> tp[0] += [3,4]

Traceback (most recent call last):
  File "<pyshell#11>", line 1, in <module>
    tp[0] += [3,4]
TypeError: 'tuple' object does not support item assignment

>>> tp
([1, 3, 4], [2])

Item assignment fails, but += has still changed the list inside the tuple, which happens in place.

Floating point and integers

On Python versions lower than 3, all numbers are considered integers unless explicitly written with a decimal point. This can cause unexpected results as it returns the floor of integer divisions.

>>> 1/2
0
>>> 1.0/2
0.5

There are several way for dealing with this. You can explicitly write the number with the decimal point as shown above. You can cast the dividing integer to a float:

>>> float(1)/2
0.5

You can add a floating point number to it:

>>> (1+0.0)/2
0.5

Or in Python 2.2 or later, you can import the division method from the __future__ module. Just remember to import this at the beginning of your Python file.

>>> from __future__ import division
>>> 1/2
0.5

Conversely, in Python 3 and above, all numbers are treated as floats in division operations. But this also can lead to unexpected results, when the result of a division is used as an index in an iterable.

>>> a = [1,2,3,4]
>>> a[4/2]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: list indices must be integers, not float

So you have to cast the resulting float to an int before using it as indexes, or use the integer division operator "//".

>>> a = [1,2,3,4]
>>> a[int(4/2)]
3
>>> a[4//2]
3