Python Language List multiplication and common references


Example

Consider the case of creating a nested list structure by multiplying:

li = [[]] * 3
print(li)
# Out: [[], [], []]

At first glance we would think we have a list of containing 3 different nested lists. Let's try to append 1 to the first one:

li[0].append(1)
print(li)
# Out: [[1], [1], [1]]

1 got appended to all of the lists in li.

The reason is that [[]] * 3 doesn't create a list of 3 different lists. Rather, it creates a list holding 3 references to the same list object. As such, when we append to li[0] the change is visible in all sub-elements of li. This is equivalent of:

li = []
element = [[]]
li = element + element + element
print(li)
# Out: [[], [], []]
element.append(1)
print(li)
# Out: [[1], [1], [1]]

This can be further corroborated if we print the memory addresses of the contained list by using id:

li = [[]] * 3
print([id(inner_list) for inner_list in li])
# Out: [6830760, 6830760, 6830760]

The solution is to create the inner lists with a loop:

li = [[] for _ in range(3)]

Instead of creating a single list and then making 3 references to it, we now create 3 different distinct lists. This, again, can be verified by using the id function:

print([id(inner_list) for inner_list in li])
# Out: [6331048, 6331528, 6331488]

You can also do this. It causes a new empty list to be created in each append call.

>>> li = []
>>> li.append([])
>>> li.append([])
>>> li.append([])
>>> for k in li: print(id(k))
... 
4315469256
4315564552
4315564808

Don't use index to loop over a sequence.

Don't:

for i in range(len(tab)):
    print(tab[i])

Do:

for elem in tab:
    print(elem)

for will automate most iteration operations for you.

Use enumerate if you really need both the index and the element.

for i, elem in enumerate(tab):
     print((i, elem))

Be careful when using "==" to check against True or False

if (var == True):
    # this will execute if var is True or 1, 1.0, 1L

if (var != True):
    # this will execute if var is neither True nor 1

if (var == False):
    # this will execute if var is False or 0 (or 0.0, 0L, 0j)

if (var == None):
    # only execute if var is None

if var:
    # execute if var is a non-empty string/list/dictionary/tuple, non-0, etc

if not var:
    # execute if var is "", {}, [], (), 0, None, etc.

if var is True:
    # only execute if var is boolean True, not 1

if var is False:
    # only execute if var is boolean False, not 0

if var is None:
    # same as var == None

Do not check if you can, just do it and handle the error

Pythonistas usually say "It's easier to ask for forgiveness than permission".

Don't:

if os.path.isfile(file_path):
    file = open(file_path)
else:
    # do something

Do:

try:
    file = open(file_path)
except OSError as e:
    # do something

Or even better with Python 2.6+:

with open(file_path) as file:

It is much better because it is much more generic. You can apply try/except to almost anything. You don't need to care about what to do to prevent it, just care about the error you are risking.

Do not check against type

Python is dynamically typed, therefore checking for type makes you lose flexibility. Instead, use duck typing by checking behavior. If you expect a string in a function, then use str() to convert any object to a string. If you expect a list, use list() to convert any iterable to a list.

Don't:

def foo(name):
    if isinstance(name, str):
        print(name.lower())

def bar(listing):
    if isinstance(listing, list):
        listing.extend((1, 2, 3))
        return ", ".join(listing)

Do:

def foo(name) :
    print(str(name).lower())

def bar(listing) :
    l = list(listing)
    l.extend((1, 2, 3))
    return ", ".join(l)

Using the last way, foo will accept any object. bar will accept strings, tuples, sets, lists and much more. Cheap DRY.

Don't mix spaces and tabs

Use object as first parent

This is tricky, but it will bite you as your program grows. There are old and new classes in Python 2.x. The old ones are, well, old. They lack some features, and can have awkward behavior with inheritance. To be usable, any of your class must be of the "new style". To do so, make it inherit from object.

Don't:

class Father:
    pass

class Child(Father):
    pass

Do:

class Father(object):
    pass


class Child(Father):
    pass

In Python 3.x all classes are new style so you don't need to do that.

Don't initialize class attributes outside the init method

People coming from other languages find it tempting because that is what you do in Java or PHP. You write the class name, then list your attributes and give them a default value. It seems to work in Python, however, this doesn't work the way you think. Doing that will setup class attributes (static attributes), then when you will try to get the object attribute, it will gives you its value unless it's empty. In that case it will return the class attributes. It implies two big hazards:

  • If the class attribute is changed, then the initial value is changed.

  • If you set a mutable object as a default value, you'll get the same object shared across instances.

Don't (unless you want static):

class Car(object):
    color = "red"
    wheels = [Wheel(), Wheel(), Wheel(), Wheel()]

Do :

class Car(object):
    def __init__(self):
        self.color = "red"
        self.wheels = [Wheel(), Wheel(), Wheel(), Wheel()]