Writing better Python code

Image by https://www.pexels.com/@mikhail-nilov/
Image by https://www.pexels.com/@mikhail-nilov/

When we write code in any programming language, we tend to look into how code is written in the language. There are some guidelines and conventions that can help us to write better code and more readable code. There is even a term created for this in Python and it is Pythonic.

In this blog, I share some of my points of view on writing better Python code.


1. Wildcards Imports 

Sometimes, when we look at a function and we do not know where it is imported from. For example

# bad practice to import all
from math import *

print(ceil(0.6))

This one is obvious however it is not when there are many imports and many lines of code in a file. A better way is

# good practice to import what we need
from math import ceil

print(ceil(0.6))


2. Unnecessary use of True and False

This is not particular to writing code in Python as we have seen this in other programming languages too.

names = ["ann", "beth", "candy", "debbie", "elaine", "faith", "geta"]


def abc_names(name):
    if name[0:1] == "a" or name[0:1] == "b" or name[0:1] == "c":
        return True
    return False

The function can be just

def abc_names(name):
    return name[0:1] == "a" or name[0:1] == "b" or name[0:1] == "c"

or can be

def abc_name(name):
    return name[0:1] in ["a", "b", "c"]


3. Unnecessary traversal of the entire list

This is also not particular to writing code in Python.

cars = [
    {"brand": "tesla", "owned": True},
    {"brand": "honda", "owned": False},
    {"brand": "toyota", "owned": True},
    {"brand": "bmw", "owned": False},
]


# materialized the entire list, just to get the first item
# this is bad practice when the list is huge.
def first_owned():
    return list(filter(lambda car: car["owned"], cars))[0]


print(first_owned())

this can be better written as

def first_owned():
    return next(filter(lambda car: car["owned"], cars), None)

where we get the next value from the filter generator and do not generate all the values. It is undesirable to do a list on the filter generator in this case.


4. Usage of generator

Among other things, Python makes it easy to create generators. We can avoid for loop entirely.

cars = [
    {"brand": "tesla", "owned": True},
    {"brand": "honda", "owned": False},
    {"brand": "toyota", "owned": True},
    {"brand": "bmw", "owned": False},
]

for i in range(len(cars)):
    if cars[i]["owned"]:
        print(cars[i]["brand"])

# simpler version
for car in cars:
    if car["owned"]:
        print(car["brand"])

these can be easily written as

# use filter generator
for car in filter(lambda car: car["owned"], cars):
    print(car["brand"])


5. Usage of comprehension list

Another cool thing about Python is the comprehension list.

cars = [
    {"brand": "tesla", "owned": True},
    {"brand": "honda", "owned": False},
    {"brand": "toyota", "owned": True},
    {"brand": "bmw", "owned": False},
]

brands = []
for car in cars:
    brands.append(car["brand"])

print(brands)

can be written as

print([car["brand"] for car in cars])

and we can even do filtering

# car that I owned
print([car["brand"] for car in cars if car["owned])


6. zip function

The zip function is extremely useful to reference multiple lists. An example

cars = ["tesla", "honda", "toyota", "bmw"]
owneds = [True, False, True, False]

for i in range(len(cars)):
    if owneds[i]:
        print(cars[i])

can be written as

for car, owned in zip(cars, owneds):
    if owned:
        print(car)


7. Usage of decorator

Yet another cool feature is the decorator.  There are many tutorials on Python's decorator online. Here is an example.

import timeit


def fibonacci(n):
    if n == 0:
        return 0
    if n == 1:
        return 1

    return fibonacci(n - 2) + fibonacci(n - 1)


start = timeit.default_timer()

for i in range(40):
    print(fibonacci(i))

stop = timeit.default_timer()
execution_time = stop - start
print(execution_time)

This prints the Fibonacci sequence. The execution time is 66 seconds on my laptop. Let's keep the fibonacci function unchanged and add a decorator to have dynamic programming.

import timeit

memo = [0, 1]


def memoize(func):
    def inner(n):
        if n < len(memo):
            return memo[n]

        val = func(n)
        memo.append(val)
        return val

    return inner


@memoize
def fibonacci(n):
    if n == 0:
        return 0
    if n == 1:
        return 1

    return fibonacci(n - 2) + fibonacci(n - 1)


start = timeit.default_timer()

for i in range(40):
    print(fibonacci(i))

stop = timeit.default_timer()
execution_time = stop - start
print(execution_time)

Now, the execution time is 0.000149 seconds.


Conclusion

Python language offers a lot of nice features and let's use them to write better code.


Comments

Popular posts from this blog

OpenAI: Functions Feature in 2023-07-01-preview API version

Storing embedding in Azure Database for PostgreSQL

Happy New Year, 2024 from DALL-E