In this article We will explore Good practices to follow when programming in Python.

The Zen of Python is a collection of 19 guiding principles for python programming. Following these principles will ensure that you write more Pythonic programs.

The principles were composed by  Tim Peters who was an active member of the Python Software Foundation. They are 19 because Tim Peters  left the  20th one intending it to be proposed by Python's original creator, Guido van Rossum, but this never happened so they stand at 19. These principles are available in  Python standard library in a module known as this

this module in Python.

To view the zen of Python from your python installation  you will need to import a builtin module called this, you can do this by opening  the Python shell  and type or paste  the below statement,

import this

the zen of Python from command interface

You can alternatively run the import statement above from an IDE/Editor .

As illustrated in the above picture, the 19 principle in the zen of Python are:

  • Beautiful is better than ugly.
  • Explicit is better than implicit.
  • Simple is better than complex.
  • Complex is better than complicated.
  • Flat is better than nested.
  • Sparse is better than dense.
  • Readability counts.
  • Special cases aren't special enough to break the rules.
  • Although practicality beats purity.
  • Errors should never pass silently.
  • Unless explicitly silenced.
  • In the face of ambiguity, refuse the temptation to guess.
  • There should be one-- and preferably only one --obvious way to do it.
  • Although that way may not be obvious at first unless you're Dutch.
  • Now is better than never.
  • Although never is often better than *right* now.
  • If the implementation is hard to explain, it's a bad idea.
  • If the implementation is easy to explain, it may be a good idea.
  • Namespaces are one honking great idea -- let's do more of those

The above principles are easy to understand, we will only look at a handful of them and how  they can take effect when programming in Python.

Beautiful is better than ugly.

Consider the below task and two possible approaches that can be used to solve it. 

task: Write a function that takes a list of  numbers and returns another list with only the odd numbers  squared.

the  "ugly" version:

square_odds = lambda nums : list(map(lambda i: i**2, filter(lambda i: i%2, nums)))

print(square_odds(range(10)))

the  " beautiful " version:

def square_odds(nums):
    return [i **2 for i in nums if i%2]

print(square_odds(range(10)))

The above functions accomplishes the same task, but the latter one is more readable and beautiful compared to the one using lambda approach.  The point here is that as a Python programmer you should not use an approach which intentionally makes it harder for humans to understand what the program is doing unless there is a clear performance gain.

Explicit is better than implicit.

Consider if you want to load pow, log and sin functions from the Python's builtin math module. 

the "Implicit" approach:

from math import *

#use the functions you need
print(pow(2,2))
print(log(2))
print(sin(2))

the "Explicit" approach:

from math import pow, log, sin

#use the functions
print(pow(2,2))
print(log(2))
print(sin(2))

In the "implicit" approach, you are not being specific on the functions you want to load, you end up loading all the functions from the math module without having to use many of them. In the latter approach , you are specifying clearly that you only want to use the 3 functions. Being Explicit means using  concise statements and stating clearly what you want to do. 

Simple is better than complex.

consider if you are given a task  to save  the following data in the disk

Books = [
    {
        "Title": "Tell me your dreams", "Author": "Sydney Sheldon", "Origin": "America"
    },
    {
        "Title": "The Diary of a Young Girl", "Author": "Anne Frank", "Origin": "Germany"
    },
    {
        "Title": "Book Thief", "Author": "Markus Zusak", "Origin": "Germany"
    }

]

the  "complex" approach:

def save_books(books):
   import psycopg2
   conn = psycopg2.connect(
      """dbname=db_name
         user=your_username
         password=your@secret
      """)
   conn.automomplete=True
  
   with conn.cursor() as cursor:
        query = """
               CREATE TABLE book (
                  title varchar(600),
                  author varchar(200),
                  country varchar(100)
            )"""
        cursor.execute(query)
        cursor.executemany("INSERT INTO book (title,author,origin) VALUES (%s, %s, %s)",books)
   cursor.close()
   conn.close()

*note that the above example assumes existing installations and setups , including a PostgreSQL database.

the  "simple" approach:

def save_books(books):
    import json
    with open("books.json","w") as file:
         file.write(json.dumps(books))

In this case, the first approach is unnecessarily complex because the data given can be easily  accommodated in a simple JSON file. 

Flat is better than nested

 task: You have 4 pets - 2 cats and 2 dogs, write a program to identify each animal 

"nested" approach:

def identify(animal):
    pet = None
    noise = animal.poke()
    if noise == "woof":
          if animal.color ==  "White":
              pet = "Max"
          elif animal.color == "Brown":
              pet = "Daisy"
    elif noise == "purrr":
          if animal.color == "Black":
              pet = "Bella"
          elif animal. color == "Orange":
              pet = "Loki"
    return pet

"flat" approach:

def identify(animal):
    pet = None
    noise = animal.poke()
    if noise == "woof":
        pet = identify_dog(animal)
    elif noise == "purrr":
        pet = identify_cat(animal)
    return pet

def identify_dog(dog):
    if dog.color ==  "White":
        return "Max"
    elif dog.color == "Brown":
        return "Daisy"

def identify_cat(cat):
    if cat.color == "Black":
        return "Bella"
    elif cat.color == "Orange":
        return "Loki"

Avoid nesting conditions and loops where using a flat approach can make the  program  easier to read and understand.

Sparse is better than dense.

Use blank lines to separate blocks of code , for example functions,  and  branching statement, the  "flat" approach above can be written more sparsley  as follows:

def identify(animal):
    pet = None

    noise = animal.poke()

    if noise == "woof":
        pet = identify_dog(animal)

    elif noise == "purrr":
        pet = identify_cat(animal)

    return pet

def identify_dog(dog):
    if dog.color ==  "White":
        return "Max"

    elif dog.color == "Brown":
        return "Daisy"

def identify_cat(cat):

    if cat.color == "Black":
        return "Bella"

    elif cat.color == "Orange":
        return "Loki"

Readability Counts

Codes are read more than they are written, it is therefore, a duty of the programmer to ensure that their program  can be read  and understood without much struggle. Python offers a lot of features that enhance code readability for example- requisite indentation . The programmer should ensure that they use  statements which are not cryptic and also document their program as necessary. 

the following is a program to perform the popular insertion sort

def srt(lst):
    for i in range(1, len(lst)):
        j = i
        while j > 0 and lst[j - 1] > lst[j]:
            lst[j], lst[j - 1] = lst[j - 1], lst[j]
            j -= 1
    return lst

L = [1, 5, 2, 4, 3]
print(srt(L))

Despite the fact that the above program will work correctly, the variables used in it's implementation are not descriptive, and someone will have a hard time understanding it's intended purpose.  You should generally use identifiers which are  more descriptive , for example the above program is more readable written as follows:

def insertion_sort(arr):
    for i in range(1, len(arr)):
        key = arr[i]
        j = i - 1
        while j >= 0 and key < arr[j]:
            arr[j + 1] = arr[j]
            j -= 1
        arr[j + 1] = key
    return arr

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

print(insertion_sort(L))

The only place where single letter identifiers can be used is in loops to avoid trivial repetitions .

Errors should never pass silently / Unless explicitly silenced.

consider a simple function which is intended to take two arguments and return the result of dividing the first with the second argument, we will be required to exit the function  if  division by Zero  is encountered. 

def divide( num1, num2):
    try:
       result = num1/num2
       return result
    except:
        return

The above program will work correctly if you Enter Zero as the second argument, but the except block catches all errors meaning that a lot of errors will pass silently . Consider for  example  the type of the arguments is invalid for division operation, the program will still exit without any error. The correct implementation should focus on catching the exact error(s) being avoided,  explicit is better than implicit, remember?

def divide( num1, num2):
    try:
       result = num1/num2
       return result
    except ZeroDivisionError:
        return

In this  later program, the except block only handles the ZeroDivisionError and any other error encountered will be raised as usual. You should generally avoid the catch all blocks i.e using the except  block without specifying the exception(s) that you want to handle.

In the face of ambiguity, refuse the temptation to guess.

Python  programs should be implemented in a robust way, as a programmer you should be thorough in describing what a program should be doing at any given point in order to avoid unpredictable and unexpected behavior. Consider a program to find the leftmost occurrence of  a given value in a  Python sequence:

def find(seq, value):
    for i in range(len(seq)):
        if seq[i] == value:
            return  i

The program above will return the index at which  the value is first encountered in the sequence, but consider what would happen if the value isn't found in the sequence. In such a circumstance the program will just return the default return value None making it unpredictable  since we cannot be guaranteed that the program returned it  because it didn't locate the value we wanted or because something awry happened. We can instead make the program to return a predictable value, which if we encounter, we will be assured that  the program didn't find the value we wanted in the sequence. 

def find(seq, value):
    val = None
    for i in range(len(seq)):
        if seq[i] == value:
            val = i
    return val or -1
    

The above program will return -1  if the value whose index we want  is not found in the sequence

There should be one-- and preferably only one --obvious way to do it.

While there are many ways that a python program can be tackled , there is usually an obvious and straight  way which is  more Pythonic. A Pythonic approach is usually more concise, efficient and readable. This principle is important because large programs are built upon small repeating chunks, making these chunks as Pythonic as possible will help to ensure that the whole program is Pythonic as well thus making it overall efficient , concise and readable.  

names = ["Eric", "Mike", "Daisy", "John", "Smith", "Jimmy"]
i = 0
while i < len(names):
    if names[i] == "John":
       print("John found")
       break
    i+=1

     

While the above loop can be used to iterate over a Python sequence, it is not the most Pythonic way. The same program  written in a Pythonic way would look like:

names = ["Eric", "Mike", "Daisy", "John", "Smith", "Jimmy"]
for name in names:
    if name == "John":
       print("John Found")
       break

That is just a simple example, the more you write and read Python programs, the more you will learn the Pythonic way of doing things.