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
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:
the " beautiful " version:
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:
the "Explicit" approach:
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
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:
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.