5 Patterns to Write Better and Clean Code in Python

5 Patterns to Write Better and Clean Code in Python

Writing better code is always a challenge in any programming language. Before wring good code, you should have a better understanding of the programing language. Reading an entire book is time-consuming. At the same time, It will be hard to remember patterns. In this article, I will highlight a few tips and patterns to write better code in Python. I am still new to Python, So it may be the case my code will be not as performant as code from an experienced developer. You may be preparing for coding challenges for companies like Google, Facebook. To successfully pass such coding challenges, you have to solve lots of coding problems in Leetcode. In coding challenges, you have to solve multiple questions. Like Facebook ask you to solve 2 questions within 40mins. A Programing Language is not restricted to the coding challenge. You can pick any programming language to solve the problem. Whatever programming language you choose, you need some tricks and tweaks to solve problems in a limited time. This article will also help you in solving complex problems on Leetcode.

1. Looping with Index

While working on problems like Two Sum, You need to iterate the list and get the index of items. You can get the index either using a counter or you can use range iterator to loop through the length of the list. You can also use enumerate to iterate an iterable object with an index.

Using range operator:

def twoSum(nums, target):
    num_map = {}
    for i in range(len(nums)):
        if nums[i] in num_map:
            return [num_map[nums[i]], i]
        else:
            num_map[target-nums[i]] = i
print(twoSum([2, 7, 11, 15], 9))

Using enumerate:

def twoSum(nums, target):
    num_map = {}
    for index, num in enumerate(nums):
        if num in num_map:
            return [num_map[num], index]
        else:
            num_map[target-num] = index

2. Slicing of Iterable

An iterable object can be iterated over using a loop. slice function returns a slice object, that can extract a sublist from an iterable object. Since a string is also an iterable object. Slicing can also be used on strings. The syntax of the slice is slice(_start_, _stop_[, _step_]).

nums = [1, 2, 3, 4, 5, 6]
name = "123456"
sslice = slice(1, 6, 2)
print(nums[slice(1, 6)]) # [1, 2, 3, 4, 5, 6]
print(nums[sslice]) # [2, 4, 6]
print(name[sslice]) # 246
`

The above code can be simplified using square brackets. You can access sublist using square brackets.

Here start, stop and steps all are optional. The default value of start, stop and steps are 0, length of an object and 1 respectively.
You can also use negative values to change the flow. To understand the negative values, Let’s take an example palindrome-number problem. There are multiple ways to solve this problem. The quick way to solve it is, Convert an integer into a string and reverse the string and compare.

class Solution:
    def isPalindrome(self, x):
        num1 = str(x)
        num2 = num1[::-1]
        return num1 == num2
print(Solution().isPalindromewithReverse(121)) # True
print(Solution().isPalindromewithReverse(-121)) # False

Here in the above example, The reverse of the string num1 is assigned to num2.

Photo by Brett Jordan on Unsplash

3. Memoization

There are cases, Where you need to write recursion code. Memoization can really improve the performance of the code. Memoization is also useful to solve Dynamic Programming coding problems. Memoize can be tricky to implement. However, Using the decorator function it is too easy to implement memoize code. If you want to know more about decorators, You can read my article Validation in Python using Decorator.
To explain this topic, We will solve powx-n problem from Leetcode. In this problem, We have to implement a function that calculates x raised to the power n.

Code of Memoize Decorator:

def join(*args):
    return reduce(lambda a, b: f"{a}_{b}", args)
def memoize(func):
    memo = {}
    def helper(*args):
        key = join(*args)
        if key not in memo:
            memo[key] = func(*args)
        return memo[key]
    return helper

Now actual solution can be written as below

@memoize
def powHelper(x, n):
    if n == 0:
        return 1
    return powHelper(x*x, n/2) if n % 2 == 0 else x * powHelper(x*x, (n-1)/2)
class Solution:
    def myPow(self, x, n):
        m = n
        if n < 0:
            m *= -1
        res = powHelper(x, m)
        return res if n >= 0 else 1/res
print(Solution().myPow(2, 3))  # 8
print(Solution().myPow(2, 5))  # 32
print(Solution().myPow(2, -5))  # 0.03125

4. Varargs and Argument Unpacking

To understand Varargs, Let’s take an example of a sum function. The sum is a function, which returns the summation of variable number parameters passed to the function.

def sum(*args):
    res = 0
    for num in args:
        res += num
    return res
print(sum(1, 2, 3)) # 6
print(sum(1, 2, 3, 4, 5)) # 15

Here sum function can accept variable arguments args. Since args is a tuple is an iterable object(tuple), We can also use reduce from functools. To learn more about functional programming, Read Functional Programming in Python.

# Using reduce, simplified version
from functools import reduce
def sum(*args):
    return reduce(lambda a, b: a+b, args)
print(sum(1, 2, 3))
print(sum(1, 2, 3, 4, 5))

Note: In the above examples, varargs is a tuple. So we can unpack it.

def divide(*args):
    num, den = args
    return num/den
print(divide(10, 2))

You can also use named varargs. named varargs is a dictionary to accept multiple named arguments. Named varargs is very useful to take option dictionary.

def range(**args):
    start = args["start"] if "start" in args else 0
    end = args["end"] if "end" in args else 100
    res = []
    while start < end:
        res.append(start)
        start = start+1
    return res
print(range(start=10, end=20)) # [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
Photo by Arseny Togulev on Unsplash

5. Mapping of Iterator

Mapping of an iterable object is helpful to convert an iterable object. Mapping can be done using the map function.

numbers = [1, 2, 3, 4, 5]
result = map(lambda x: x*x, numbers) # return map object
print(list(result)) # need to convert to list
# Output: [1, 4, 9, 16, 25]

The above example uses a lambda function. If the function has multiple lines of statements, You can use a custom function.

numbers = [1, 2, 3, 4, 5]
def mapInt(num):
    return num*num if num % 2 == 0 else num*num*num
print(list(map(mapInt, numbers)))

Mapping can be done on any iterable object including sets, lists, tuples etc.

Note: There is a better way to map an iterable object. It is called List comprehensions. List comprehension is a concise and elegant way to map an iterable object.

Celsius = [39.2, 36.5, 37.3, 37.8]
Fahrenheit = [((float(9)/5)*x + 32) for x in Celsius]
print(Fahrenheit) # [102.56, 97.7, 99.14, 100.03999999999999]

You can also map to a dictionary, set and tuple. To learn more read datastructures

>>> a = {x for x in 'abracadabra' if x not in 'abc'}
>>> a
{'r', 'd'}

Conclusion

In this article, I have just mentioned a few tips and patterns. There are a lot of other cool concepts to be learned. To keep the article to a concise length, I have broken this article into multiple sub-article. I will publish those later. I have attached all reference link below.

References:

Did you find this article valuable?

Support Deepak Vishwakarma by becoming a sponsor. Any amount is appreciated!