Lessons I learnt in quest of writing beautiful python code

Hello everyone. I always wonder what are the good practices in developing software in Python. I am young and  inexperienced few years back. But  people around me and situations I faced from past few years had taught me many things. Many things about coding style, good development patterns etc. Here I am going to discuss few things which are important to turn your normal coding style into an elegant one. These things are collected from my own , others code reviews.

If you keep all these points in mind  from tomorrow you will see a different aspect of coding. Thanks for my inspirational man, Chandra -Software Architect @ Knowlarity Communications for reviewing my code and giving valuable tips with his vast software development experience. Let us see how not to write code.

* Your code is a Baby. Protect it with Exception Handling

A Software or program fails when it accepts the wrong input. A good developer always handles his piece of code. No one can guess all possible bugs that creeps in. In statically typed languages like C, C++ type system enforces the kind of information passed to a variable. But in dynamic languages like Python and Ruby there are many chances of failure of a program due to entry of incorrect type. Duck typing is a comfort. But it comes with expense of more careful error handling. Here I always wrap my code in TRY | EXCEPT blocks. If you know what type of error you might encounter, it is easy to make your code function properly. At least it won’t break your code. Let us see the first illustration of handling JSON data.

import json

def handle_json(data_string):
    parsed_data = json.loads(data_string)
    return parsed_data

A newbie of Python just leaves the above code and thinks his job was finished. But code may break if ill formed JSON is passed through handle_json function. So it is better to handle error.

import json

def handle_json(data_string):
    try:   
        parsed_data = json.loads(data_string)
    except:
       return {}
    return parsed_data

This is basic error handling. It will turn into a good practice if we log a message when error occurs. Handling specific error will do more good.

import json

def handle_json(data_string):
    try:   
        parsed_data = json.loads(data_string)
    except ValueError as e:
        logger.info("Error occured: %s" % e.message)
        return {}
    return parsed_data

So never think error handling as an add on. It is a compulsory thing when writing software for reliable systems.

* Never put magic numbers in the code

It is common for us to use constants in the programs. We define few things as mapping to sequence of numbers. Enumerate data type is an example. It gives us a range of named constants. So use name of the constant instead of constant itself.

fruit = int(raw_input("1.Apple\n2.Mango\n3.Gauva\n4.Grape\n5.Orange\nEnter your favorite fruit: "))
if fruit == 1:
    print "Fruit is Apple"
elif fruit == 2:
    print "Fruit is Mango"
elif fruit == 3:
    print "Fruit is Gauva"
.....
else:
    print "Fruit is not available"

It is just a simple program which inputs a number and uses that input to select fruit type. But when one sees the code, he will be wondered what those 1,2,3 means. English names convey better messages than mere numbers. So good practice is not to hard code anything. Instead use your own Enum type to map meaningful names to Numbers.

class Fruit(object):
    APPLE, MANGO, GAUVA, GRAPE, ORANGE = range(1,6) 
 
    @classmethod
     def tostring(cls, val):
         """String representation of a Fruit type."""
         for k, v in vars(cls).iteritems():
             if v == val:
                 return k
fruit = int(raw_input("1.Apple\n2.Mango\n3.Gauva\n4.Grape\n5.Orange\nEnter your favorite fruit: "))
print "The fruit is: %s" % (Fruit.tostring(Fruit.APPLE)).capitalize()

See by building our own enumeration, we are able to transform a hard coded program into beautiful, meaningful one. Here we defined a class to store named constants. We are reverse looking a key from value using our tostring method. Never ever put magic numbers in the code because in larger systems it creates ambiguity. Code is for humans first and for computers next.

* Best ways of working with a dictionary

Many of us will be working with dictionaries in Python as frequently as we take a sip of coffee. When carefully observed beginner developers usually have a habit of accessing a dictionary value using bracket method.

students =  {1: "Naren", 2: "Sriman", 3:"Habeeb"}
print students[1]

Everybody does that, you might wonder. Yes it is the most trivial way of accessing a value from a dictionary. But as we shouted in our first tip, you should handle error when you try to query dictionary for non-existing key. Like this you can say

students =  {1: "Naren", 2: "Sriman",  3:"Habeeb", 4:"Ashwin"}
try:
    print students[1]
except KeyError as e:
    print None

Instead of doing all these things we can do one straight operation called GET on a dictionary. Python will return you value for a key if key exists else returns None.

students =  {1: "Naren", 2: "Sriman", 3:"Habeeb", 4:"Ashwin "}
print students.get(1)
# This prints None
print students.get(101)

But in my beginning of development career, I used to mix both the ways in a program which looks pretty awkward. So my advice is to use get function or bracket [] method according to your personal taste but two things.

  • Using get gives you automatic error handling
  • Keep your program uniform.

One more useful case is when you are processing a dictionary and want’s to update an existing dictionary with the new one. Many people does this thing.

students =  {1: "Naren", 2: "Sriman", 3:"Habeeb", 4:"Ashwin "}
new_students = {5: "Tony", 6:"Srikanth", 7:"Rajesh"}
# A trivial way to add new students to students map
students[5] = new_students[5]
students[6] = new_students[6]
students[7] = new_students[7]
But there is a handy method called  UPDATE on any python dictionary. It allows us to merge the second dictionary with the first.
students =  {1: "Naren", 2: "Sriman", 3:"Habeeb", 4:"Ashwin "}
new_students = {5: "Tony", 6:"Srikanth", 7:"Rajesh"}
students.update(new_students)
This function is crisp because it is avoiding lot of typing. And also it makes program looks cleaner.

* Always do Validation of the data first and then pre-processing

My context here is, many fellow programmers do return empty (None) from a function when they found data is invalid to proceed. But they do lot of pre-processing before checking for validity. Here computation is wasted. It is illogical for a program to spend time in doing useless things and checking whether it use useful or ignored. It may seems fine for many people but handling this design pattern cleverly can have huge impact on code performance.

 

valid_data = [1,2,3,4,5]
def process(value):
    new_value = preprocess(value)
    if value not in valid_data:
        return None
    return new_value

def preprocess(value):
    # Do a heavy computation task
    return value

print process(23)
In less code situations we will capture the inefficiency of checking condition last. I always feel of losing my common sense, if I see above mistake in my code later. I always check conditions in the first line of function and then do anything with that data. So process function should be like this
def process(value):
    # Make habit of filtering in first line itself
     if value not in valid_data:
         return None
    # Now do whatever you want
    new_value = preprocess(value)
    return new_value

I write code for a telephony company where product is built upon thousands of lines of legacy python code. There performance is critical. If I design one procedure using above mistake, it will have a huge business impact on the product. Even few seconds delay is not bearable. So keep in mind. Always return invalid cases and then do pre-processing.  

* Avoid trivial conditionals in code

This is not actually a mistake but a very good practice to avoid lot of IF and ELSE blocks in the code.

def  is_even(value):
    if value % 2 == 0:
        return True
    else:
        return False
print is_even(4)

But observing carefully we can remove else here because it is trivial that if condition is True, control won’t stay any more in the function. So we can modify code to

def  is_even(value):
    if value % 2 == 0:
        return True
    return False
print is_even(4)

So remember this as a thumb rule. “Always  try to use a single conditional when there is a truth checking and take another as trivial thing“.

* Other notable points

In addition to above points, there are few other important things.

  • Touch maximum level of abstraction by placing common logic on top level of code and specific implementations on bottom.
  • Follow PEP-8 and PEP-257. It will make code more readable. I hated it first but now loving the structure of code.
  • Make sure of doc strings of classes and methods conveying the right message in a Python program.
  • In ORM like Django or SQLAlchemy use filter rather than Get because the former one is safe. FILTER always return empty list, GET throws duplicate error which you should  handle explicitly.
  • Make a habit of removing print statements and debuggers before committing the code to GIT.
  • When you add a new feature, please do write a unit test case. It will help a new developer in understanding functionality of class or procedure you had defined.
  • Never push code without developer testing.

Once again thanks for my inspirational man, Chandra -Software Architect @ Knowlarity Communications and Mohammed Habeeb  for reviewing my code and giving valuable tips with their vast software development experience.

Advertisements

4 thoughts on “Lessons I learnt in quest of writing beautiful python code

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s