paint-brush
5 Best Practices for Writing Efficient Python Codeby@akdev
560 reads
560 reads

5 Best Practices for Writing Efficient Python Code

by AK DEVMarch 14th, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Python is a popular programming language used by developers for various purposes, such as data analysis, web development, machine learning, and more. In this article, we'll discuss some basic principles of coding that can help you optimize your programs' performance. I will also provide some code examples to show these concepts.
featured image - 5 Best Practices for Writing Efficient Python Code
AK DEV HackerNoon profile picture
The following introductory paragraph was generated with the help of AI.

Python is a popular programming language used by developers for various purposes, such as data analysis, web development, machine learning, and more. However, this language has a pretty low entry threshold, and without a broad understanding of the principles you may occasionally write some redundant or over-complex code.

In this article, we'll discuss some basic principles of coding in python that can help you optimize your programs' performance.

I will also provide some code examples to show these concepts.

The code in this article was generated with the help of AI

1. Use built-in functions and libraries instead of writing your own implementations

Python comes with many built-in functions and libraries that can save you time and effort when writing code. Also, built-in functions are usually more optimized by underlying C implementation calls. For example, you can use these functions instead of writing your own:

# maximum numeric
max([4, 1, 8]) 
# Result: 8

# minimum numeric
min([4, 1, 8])
# Result: 1

# sorted ascending list
sorted([4, 1, 8])
# Result: [1, 4, 8]

# sorted descending list
reversed([4, 1, 8])
# Result: [8, 4, 1]

# sum of a list
sum([4, 1, 8])
# Result: 13

# list of tuples where each tuple
# contains one item from each list
list(zip([4, 1, 8], ["orange", "banana", "apple"]))
# Result: [(4, 'orange'), (1, 'banana'), (8, 'apple')]

# tuples of the form (index, value)
# for each item in a list
list(enumerate(["orange", "banana", "apple"]))
# Result: [(0, 'orange'), (1, 'banana'), (2, 'apple')]

# iterator containing only the items
# in the iterable for which the function
# returns True
list(filter(lambda x: x % 2 == 0, [4, 1, 8]))
# Result: [4, 8]

# applies the function to each item
# in the iterable
list(map(lambda x: x ** 2, [4, 1, 8]))
# Result: [16, 1, 64]

# apply a function to each item in
# an iterable and reduce the iterable
# to a single cumulative value

from functools import reduce

reduce(lambda x, y: x * y, [4, 1, 8])
# Result: 32

# returns True if all items in the iterable are true 
all(char.isupper() for char in "HELLO WORLD")
# Result: True

# returns True if any item in the iterable is true
any(char.isdigit() for char in "hello 123 world")
# Result: True

Similarly, instead of writing your own function to calculate statistical values you can use functions from the statistics library:

import statistics

# Example 1: Using mean()
numbers = [1, 2, 3, 4, 5]
mean = statistics.mean(numbers)
print(mean)
# Output: 3

# Example 2: Using median()
numbers = [1, 2, 3, 4, 5]
median = statistics.median(numbers)
print(median)
# Output: 3

# Example 3: Using mode()
numbers = [1, 2, 3, 4, 4, 5, 5, 5]
mode = statistics.mode(numbers)
print(mode)
# Output: 5

Using built-in functions and libraries can be faster and more reliable than writing your own code, especially for performing routine tasks.

2. Avoid unnecessary computations

It’s a good idea to overview the task and try to optimize the code whenever possible.

  • Avoiding repeated calculations using memorization technique:
# Define a storage to keep previous results
results = {}

# Define a function to perform a calculation
def calculate(x):
    if x in results:
        # Return the result from the dictionary if it has already been calculated
        return results[x]
    else:
        # Perform the calculation if it hasn't been done before
        result = x ** 2
        # Put the result in the dictionary
        results[x] = result
        return result

# Use the function to calculate the squares of some numbers
print(calculate(5)) # Output: 25
print(calculate(3)) # Output: 9
print(calculate(5)) # Output: 25 (retrieved from the storage)
  • Avoiding unnecessary copies of large data structures (especially of large dataframes)
  • Avoiding unnecessary function calls

Function calls in Python are relatively expensive in comparison to for example C. That's why you need to try to avoid nested function calls when possible.

# Instead of this

def func1(i):
    return i + 1

def func2(n):
    x = 0
    for i in range(n):
        x = func1(x)


# It is better to do this

def func(n):
    x = 0
    for i in range(n):
        x = x + 1

Avoiding unnecessary computations can save you time and improve your program's performance, especially for large data sets.

3. Use list and dictionary comprehension

List comprehensions are a concise and efficient way to create lists or dictionaries in Python. They allow you to write a single line of code that generates a new list/dict based on an existing object. When to use it:

# Creating a new list
squares = [i * i for i in range(6)]
# Result: [0, 1, 4, 9, 16, 25]

# Filtering an existing list
squares = [0, 1, 4, 9, 16, 25]
squares = [i for i in squares if i > 10]
# Result: [16, 25]

When not to use it:

# Long or complicated computation (reduced readability)

squares = [0, 1, 4, 9, 16, 25]
squares = [i for i in squares if i > 0 and (i % 2 == 0 or i % 5 == 0)]
# Result: [4, 16, 25]

List comprehensions can be faster and more readable than for loops, especially for simple operations.

4. Use generators

Generators are a type of iterable, similar to lists or tuples, but they generate values on-the-fly instead of storing them in memory. They can be more memory-efficient than lists, especially for creating an infinite or large sequence (especially with heavy computations)

# Generator for squares

squares = (i ** 2 for i in range(1000000))
next(squares)
# Result: 0
next(squares)
# Result: 1
next(squares)
# Result: 4

5. Use data structures that are appropriate for the task

Python offers a wide range of built-in data structures, such as lists, tuples, sets, and dictionaries. Each data structure has its own strengths and weaknesses, and choosing the right one for the task can make a big difference in performance. For example, if you need to perform set operations, such as union or intersection, use a set instead of a list. Sets are optimized for these operations and can be much faster than lists.

Here's an example:

# Using a set to perform set operations

set1 = {1, 2, 3}
set2 = {2, 3, 4}
union = set1.union(set2)
intersection = set1.intersection(set2)
print(union)  # Output: {1, 2, 3, 4}
print(intersection)  # Output: {2, 3}