Common pitfalls in Python [duplicate]

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+ / 3:

with open(file_path) as file :

It is much better because it's much more generical. You can apply "try / except" to almost anything. You don't need to care about what to do to prevent it, just 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. E.G, you expect a string in a function, then use str() to convert any object in a string. You expect a list, use list() to convert any iterable in 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

Just don't. You would cry.

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 can declare class Father: is fine.

Don't initialize class attributes outside the __init__ method

People coming from other languages find it tempting because that what you do the job 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()]

When you need a population of arrays you might be tempted to type something like this:

>>> a=[[1,2,3,4,5]]*4

And sure enough it will give you what you expect when you look at it

>>> from pprint import pprint
>>> pprint(a)

[[1, 2, 3, 4, 5],
 [1, 2, 3, 4, 5],
 [1, 2, 3, 4, 5],
 [1, 2, 3, 4, 5]]

But don't expect the elements of your population to be seperate objects:

>>> a[0][0] = 2
>>> pprint(a)

[[2, 2, 3, 4, 5],
 [2, 2, 3, 4, 5],
 [2, 2, 3, 4, 5],
 [2, 2, 3, 4, 5]]

Unless this is what you need...

It is worth mentioning a workaround:

a = [[1,2,3,4,5] for _ in range(4)]