Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Common pitfalls in Python [duplicate]

Tags:

python

People also ask

What are type errors in Python?

What is TypeError in Python? TypeError is an exception in Python programming language that occurs when the data type of objects in an operation is inappropriate. For example, If you attempt to divide an integer with a string, the data types of the integer and the string object will not be compatible.


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)]

Python Language Gotchas -- things that fail in very obscure ways

  • Using mutable default arguments.

  • Leading zeroes mean octal. 09 is a very obscure syntax error in Python 2.x

  • Misspelling overridden method names in a superclass or subclass. The superclass misspelling mistake is worse, because none of the subclasses override it correctly.

Python Design Gotchas

  • Spending time on introspection (e.g. trying to automatically determine types or superclass identity or other stuff). First, it's obvious from reading the source. More importantly, time spent on weird Python introspection usually indicates a fundamental failure to grasp polymorphism. 80% of the Python introspection questions on SO are failure to get Polymorphism.

  • Spending time on code golf. Just because your mental model of your application is four keywords ("do", "what", "I", "mean"), doesn't mean you should build a hyper-complex introspective decorator-driven framework to do that. Python allows you to take DRY to a level that is silliness. The rest of the Python introspection questions on SO attempts to reduce complex problems to code golf exercises.

  • Monkeypatching.

  • Failure to actually read through the standard library, and reinventing the wheel.

  • Conflating interactive type-as-you go Python with a proper program. While you're typing interactively, you may lose track of a variable and have to use globals(). Also, while you're typing, almost everything is global. In proper programs, you'll never "lose track of" a variable, and nothing will be global.


Mutating a default argument:

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

The default value is evaluated only once, and not every time the function is called. Repeated calls to foo() would return ['baz'], ['baz', 'baz'], ['baz', 'baz', 'baz'], ...

If you want to mutate bar do something like this:

def foo(bar=None):
    if bar is None:
        bar = []

    bar.append('baz')
    return bar

Or, if you like arguments to be final:

def foo(bar=[]):
    not_bar = bar[:]

    not_bar.append('baz')
    return not_bar

I don't know whether this is a common mistake, but while Python doesn't have increment and decrement operators, double signs are allowed, so

++i

and

--i

is syntactically correct code, but doesn't do anything "useful" or that you may be expecting.