Python good practices – part 1

For a good few years we’ve been developing software in Python. Today we want to share with you the basics of crucial good practices we’ve been implementing - and we recommend you to do it as well.

python-development-programming-language

Python, has currently been on the top position in the ranking of most popular programming languages [1]; it is considered simple, easy and enjoyable. It won’t be an exaggeration if I say it is even perfect for people who want to start their adventure with programming. It is well suited for numerical calculation, developing web applications, and writing quick console scripts.

Clarity and conciseness of the Python language helps to avoid many mistakes, but unfortunately not all. Below we have collected pieces of a code that may - or may not - cause interpreter errors, but surely sooner or later it will be a burden for you or the person that inherits the code from you.

As a side note, despite numerous associations the name Python doesn’t come from a friendly animal, but from well-known British comedy series Monty Python's Flying Circus broadcasted in the 1970s. The creator of the programming language Guido van Rossum was a big fan of the series and the language is playful and fun to use as well.


0) Style guides
PEP8 or other style guide (eg. Google Python Style guide [2] ).
We don’t need to convince anyone that code, which does not contain its own structure, looks very bad. For this instance, in good restaurants the dishes served are beautifully decorated, therefore you should serve your code the same way, even if the only person who reads it is the one checking pull requests.

Don’t be afraid of white spaces. This is not just a requirement of Python syntax, but also a huge convenience for other people reading the code.


1) Beware of mutable objects
Avoid mutable objects as the default arguments in functions or methods.

Try to run this code:

def foo(mutable_object=[]):
mutable_object.append('some string')
return mutable_object
view raw python.py hosted with ❤ by GitHub

running this feature for the first time one gets the result consistent with the assumptions:

>>> foo()
['some string']
view raw gistfile1.py hosted with ❤ by GitHub

but calling the function for the second time one gets the result, which can cause a lot of strange and hard to debug errors:

>>> foo()
['some string', 'some string']
view raw input.py hosted with ❤ by GitHub

and for the third time we get this:

>>> foo()
['some string', 'some string', 'some string']
view raw somestring.py hosted with ❤ by GitHub

and so on...
Always use immutable objects
See how you can improve the foo() function, so it will become harmless:

def foo(unmutable_object=None):
if unmutable_object is None:
unmutable_object = []
unmutable_object.append('some string')
return unmutable_object
view raw foo.py hosted with ❤ by GitHub

from now on when you call foo() function it will return exactly what you want:

>>> foo()
['some string']
>>> foo()
['some string']
view raw foo.py hosted with ❤ by GitHub

This error occurs because the default values are evaluated only once, when defining the function. Later throughout the lifetime of the function you use the same mutable object.
Other errors associated with this aspect of language can occur when the default argument of a function is another function.

For example:

>>> import datetime
>>> def print_time_now(now=datetime.datetime.now()):
... print now
view raw import.py hosted with ❤ by GitHub

The print_time_now function will always return the time, in which that function was defined:

>>> print_time_now()
2015-08-13 12:24:49.655825
>>> print_time_now()
2015-08-13 12:24:52.349839
#after two years
>>> print_time_now()
2017-08-13 12:24:50.243535
view raw print.py hosted with ❤ by GitHub
2) Handling Exceptions
Catching Exceptions in Python 2.x is a standard procedure: try, except and optionally finally.
However, in the except block a certain linguistic nuance appears, that may give less experienced developers a headache.

If you want to catch more than one exception you do it as follows:

try:
...
except (TypeError, IndexError)
...
view raw try.except.py hosted with ❤ by GitHub

The attempt of catching an exception through except TypeError, IndexError (without the parentheses) causes catching only TypeError exception, and the IndexError exception won’t be caught and will generate an error.


3) Avoid except without definipng an exception

Continuing the catching exceptions topic, pay attention to the following piece of code: except without defined exception. If you notice any, immediately refactor it because it is like a bomb with a delayed ignition:

try:
...
except:
...

The above code snippet will cause errors in various, sometimes very strange situations.


4) Avoid the import with an asterisk

If you import with an asterisk you must be very careful because you might encounter a very popular Python error - a loop.

I always advise against importing the following way:

from foo.bar import *

It is much better for our future mental health to import modules this way:

from foo.bar import bar1, bar2

You can intentionally use "import by star" if in the file you want to import you put the variable "__all__" as in the example below:

#file bar.py
__all__ = ['bar1', 'bar2']
def bar1():
pass
def bar2():
pass
def bar3():
pass
view raw bar.py hosted with ❤ by GitHub

Then, even if you use the from foo.bar import * only two functions bar1 and bar2 will be imported. bar3 function will not be imported.

Hope that the post was useful and clarified some Python language aspects. If you still have any questions dont hesitate and ask them in comments, I will try to explain it in more detailed way. The second part of "Python - good practices" you will find soon.


References:
[1] http://blog.codeeval.com/codeevalblog/2015#.VVH0MNOqqko=
[2] https://google-styleguide.googlecode.com/svn/trunk/pyguide.html
[3] http://www.toptal.com/python/top-10-mistakes-that-python-programmers-make
[4] http://blog.amir.rachum.com/blog/2013/07/06/python-common-newbie-mistakes-part-1/