Tips and Tricks for Clean, Readable Python Code
Tips and Tricks for Clean, Readable Python Code,

Tips and Tricks for Clean, Readable Python Code

Built-in Python features, like decorators and list comprehensions, enable developers to convert messy code into clear and readable solutions without having to recreate functionality.

Learning to code is quite a challenging journey. On one hand, you have the technical aspects of coding, while on the other, there’s the focus on crafting that code with elegance. Personally, I found the latter to be the most difficult. I could successfully tackle problems in a brute-force manner, but when it came to producing a sophisticated solution, I would inevitably opt for nested loops every time. However, that approach has its drawbacks, as effective code should adhere to the principles of DRY (Don’t Repeat Yourself), be memory-efficient, and be understandable to others.

Fortunately, Google was a valuable resource for me, and I gradually discovered tools that enabled me to create cleaner solutions more easily without having to start from scratch. Below are some of Python’s built-in features that enhance both code clarity and simplicity.

*args and **kwargs

*args and **kwargs enhance the versatility of functions. By utilizing *args as a parameter, a function can accept any number of arguments. If *args were not included, developers would need to handle integer and string arguments individually.

With *args:
def add(*args):
    results = []
    for n in args:
        results.append(n)

    return results

print(add())
print(add(1,"Cat"))
print(add(1,"Lion",3))


# []
# [1, 'Cat']
# [1, 'Lion', 3]

And without it just throws an error…

**kwargs

**kwargs functions similarly to *args, but it is used for key-value pairs. You can utilize **kwargs in a function that either doesn’t have a fixed number of parameters or can accept an indefinite number of key-value pairs.

def dictionary_builder(**kwargs):
    for x,y in kwargs.items():
        print(f'key: {x}, value: {y}')

dictionary_builder(topic = 'Python')
dictionary_builder(city = 'Jalgaon', state = 'Maharashtra')
dictionary_builder(month = 'July', day = 7, year = 2024)

# key: topic, value: Python
# key: city, value: Jalgaon
# key: state, value: Maharashtra
# key: month, value: July
# key: day, value: 7
# key: year, value: 2024

List Comprehension

List comprehensions are a fantastic way for developers to whip up lists in just a single line! If we were to build a list of numbers without using a list comprehension, we could do it with the following code:

numbers = []

for i in range (1, 7):
    numbers.append(i)

A list comprehension turns that code into a single line. The basic syntax is:

[expression for item in iterable] and in simple code, it looks like this:

numbers= [i for i in range(5)]

List comprehensions can also include filtering functionality.

even_numbers = [i for i in range(1,5) if i % 2 == 0]
print(even_numbers) # [2, 4]

Did you know that there’s more than just lists when it comes to creating data structures? You can also make a dictionary in a similar way, using the same creation pattern! The basic syntax for dictionaries looks like this:

{key_expression: value_expression for item in iterable}.

We can multiply each number in our numbers list by 10 to a by_tens dictionary.

numbers = [1, 2, 3, 4, 5, 6]
by_tens = {num: num*10 for num in numbers}
print(by_tens) #{1: 10, 2: 20, 3: 30, 4: 40, 5: 50, 6: 60}

We can also do this with a set. The basic syntax is

{expression for item in iterable}. The code looks like this:

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_set = {num for num in numbers if num % 2 == 0}
print(even_set)# {2, 4, 6, 8, 10}

zip()

zip() function is a fantastic way to work with multiple lists at the same time, allowing you to easily create tuples from their corresponding elements. It really transforms what could be a lengthy process into a quick and tidy one-liner, making your code feel cleaner and more efficient!

zip() The code goes through the length of the smallest list, so if the lists you’re working with happen to be of different lengths, the resulting tuples will match the size of the shortest list. Below is a lovely example that clearly demonstrates both of these important points:

pets = ["lion", "Bear", "Donkey", "Rabbit", "Pig"]
names = ["Maxie", "Spot", "Dusty", "Swift", "Speedy"]
ages = [6, 5, 5, 16]
 
# Create a zipped object combining the pets, names, and ages lists
result = zip(pets, names, ages)
 
# Print the zipped object
print("Zipped result as a list:")
for i in list(result):
  print(i)
  
# Zipped result as a list:
# ('lion', 'Maxie', 6)
# ('Bear', 'Spot', 5)
# ('Donkey', 'Dusty', 5)
# ('Rabbit', 'Swift', 16)

Merging Dictionaries

You can merge dictionaries using the update() functionality or the dictionary unpacking syntax (**).

land_pets = {'dog': 'Rocky', 'cat': 'Luna', 'horse': 'Atlas'}
water_pets = {'fish': 'Neptune', 'turtle': 'Coral'}
all_pets = {**land_pets, **water_pets}

print(all_pets)

# Output:{'dog': 'Rocky', 'cat': 'Luna', 'horse': 'Atlas', 'fish': 'Neptune', 'turtle': 'Coral'}

or you could use the update function to add the water_pets object to the land_pets object: land_pets.update(water_pets).

Chaining Comparison Operators

Chaining comparison operators enables the combination of several comparisons within a single expression. This chaining eliminates the necessity for the explicit `and` operator. Additionally, it enhances both the readability and efficiency of the code by decreasing the number of individual comparisons.

The example below compares the variable miles to determine if the distance is in range. The code looks like this without chaining comparison operators:

def distance_checker(miles):
    if miles > 0 and miles <10:
        print("in range")
    else:
        print("out of range")

distance_checker(3) # in range


When the comparison is expressed as a compound condition, it looks like this:

def distance_checker(miles):
    if  0 < miles <10:
        print("in range")
    else:
        print("out of range")

distance_checker(13) # out of range

Ternary Operator

Ternary operators allow developers to write if conditionals in a single line. The basic syntax is:

result = true_value if condition else false_value

If the condition evaluates to True the expression returns the true value. If the condition evaluates to False, the expression returns the false value. Here’s an example without the turnery operator

x = 5

if x % 2 == 0:
    result = "even"   
else:
    result = "odd"

print(result) #odd

Versus the if conditional with the ternary operator:

x = 5

result = "even" if x % 2 == 0 else "odd"
print(result)

Decorators

Decorators are wonderful tools that allow us to enhance our functions without altering their original code. Essentially, a decorator is a charming little function that receives the original function as its input, spruces it up with new features, and then graciously hands back the updated function for us to enjoy.

Let’s start with a basic division function.

def division(x,y):
    print(x/y)
    
division(10,5) # 2

division(9, 3) # 3

Let’s imagine for a moment that this function always needs to divide the larger number by the smaller one. There are plenty of reasons why changing the source code might not be the best option, and that’s where a decorator can really shine! If you’ve got a bit of familiarity with closures, this might feel quite familiar to you. For those who are new to the concept, think of a decorator as a delightful function that builds, modifies, and returns another function. The basic structure of the decorator will look something like this:

#decorator function definition, function passed in as an argument
def decorated_division(func):

    #inner function where the modifications will take place, has the same arguments as the function it's modifying
    def inner(a,b):

    #return the inner function   
    return inner

Inside the inner function is where we’ll check to see if the arguments are in the correct order and make the necessary swaps (another Python trick) if not.

def decorated_division(func):

    def inner(a,b):
        if a < b:
            a,b = b,a
            
        return func(a,b)

    return inner

The inner function looks and behaves like any basic function does.

Now there are a few different ways we can use the decorator function to modify the division function. The first way to do this is to use @decorator and it looks like this:

@decorated_division
def division(x,y):
    print(x/y)
    
division(5, 10) # 2

Another way to do this is to assign the function as a variable:

new_division = decorated_division(division)
new_division(5,10) # 2

Happy Coding!

Those helpful tips are bound to take your code from simple to truly elegant. The more you dive into Python, the more enjoyable and effortless it becomes!

Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *