Examples of Code Refactoring in Python

Examples of Code Refactoring in Python

What is Refactoring?

Code refactoring in Python is a process that can only be performed on existing code. Whether it is written by ourselves or by another developer is completely irrelevant, our only goal is to restructure it in a way that may increase performance, readability and scalability. Refactoring in python generally involves increasing readability as it’s already considered a slower and less performant language. This means we will try to break down processes into smaller functions, utilize meaningful variables and follow PEP8 (python styling guide) to determine proper line breaks, tabs, spacing, naming conventions, and control flow in a safe and effective way.

Code Refactoring Example 1

Given a list of fruits in singular, change it to plural.

Existing code


stuff = ["apple", "pear", "orange", "banana"]

for i in stuff:
    index = stuff.index(i)
    stuff[index] = i + "s"

Issues:

  1. Using ‘i’ for your loop variable is acceptable but make it more meaningful if possible. If I will represent some fruit, then its ok to name it ‘fruit’ as it provides more clarity.
  2. Naming a list of fruits “stuff” is not professional.
  3. Using a traditional “for” loop when we know we need index numbers is not efficient.
  4. “.index()” can be fickle, as it only ever finds the first element (thankfully we are changing each element as we loop).
  5. Using the “+” operator for concatenation is ok, but sometimes f -strings are better.
  6. List comprehension would be much better.
## Refactored code
fruits = [f"{fruit}s" for fruit in fruits]

In the example above, we reduced the code to one line using list comprehension, we fixed the variable names, removed the “.index()” function, and used an f string instead of the “+” operator. If you’re unfamiliar with list comprehensions, here is a quick explanation.

List comprehension

We can declare a list using square brackets “[]” and put a “for” loop and conditions inside. We must structure code in this particular order.

## Tell Python what you want for each loop: 
fruits = [f"{fruit}s”] 
## Define your for loop 
fruits = [f"{fruit}s" for fruit in fruits] 
## If you have a single if condition it must go at the end 
fruits = [f"{fruit}s" for fruit in fruits if len(fruits) > 4] 
## If you have an if and else statement they must come before the for loop (you can’t use elif) 
fruits = [f"{fruit}s" if len(fruits)> 4 else fruit * 2 for fruit in fruits]

Code Refactoring Example 2: Single Responsibility Principle

Given a simple game about walking in the woods. We run code based on the user’s input.

def game():
    x = input("you are walking in the woods, turn [1].left? | turn [2].right? | turn [3].back?: ")
    if x == "1":
        y = input("you spot a deer!! [1].run? | [2].pet the deer?: ")
        if y == "1":
            print("you ran from the deer")
        else:
            print("you pet the deer")    
    if x == "2":
        y = input("you spot a boar!! [1].run? | [2].pet the boar?: ")
        if y == "1":
            print("you ran from the boar but it chased you")
        else:
            print("you pet the boar and got injured")
    if x == "3":
        y = input("you spot a bigfoot!! [1].run? | [2].pet the bigfoot?: ")
        if y == "1":
            print("you ran from the bigfoot but he’s trying to find you")
        else:
            print("you got eaten by bigfoot!")

Issues:

    1. We are violating the principle of single responsibility which states a function should only have 1 responsibility.
    2. Poor variable usage.
    3. Poor use of flow control by using all “if” statements instead of if/elif/else (this can reduce performance).
    4. Invalid input is not handled.
    5. It is correct to print, but make sure the function to return anything (we don’t in this case) is not necessary.
### Refactored code
def deer_encounter():
    choice = input("you spot a deer!! [1].run? | [2].pet the deer?: ")
    if choice == "1":
        print("you ran from the deer")
    elif choice == "2":
        print("you pet the deer")
    else:
        print("invalid selection")
        deer_encounter()  # recursion can be useful to rerun if choice in invalid (beware of recursion

def boar_encounter():
    print("place all your boar logic in this function as you did with deer")

def bigfoot_encounter():
    print("place all your bigfoot logic in this function as you did with deer")

def game():
    actions = {
        "1": deer_encounter,  # here we are mapping the functions to a dictionary (do not call these functions here!)
        "2": boar_encounter,
        "3": bigfoot_encounter
    }
    user_direction = input("you are walking in the woods, turn [1].left? | turn [2].right? | turn [3].back?: ")
    if user_direction in ["1", "2", "3"]:  # this makes sure we don’t try to run an invalid number ie: > 3
        actions[user_direction]()  # pay attention here, we are mapping the user selection to the function and then calling it
    else:
        game()  # again be careful with recursion

Example 3: Limiting recursion to protect memory/stack overflow.


Def deer_encounter(attempts=0):
    if attempts < 20:
        choice = input(“you spot a deer!! [1].run? | [2].pet the deer?: “)
        if choice == “1”:
            print(“you ran from the deer”)
        elif choice == “2”:
            print(“you pet the deer”)
        else:
            print(“invalid selection”)
            deer_encounter(attempts + 1)  # here we make sure that there is a limit on recursion

In our refactored code we separated each process into its own function, this allows us to easily refactor that functions code, increase readability, and scalability. Notice how we map each function to a dictionary. If we had 25 choices on our journey through the woods, this would require us to write 25 “if/elif” statements.

Instead, we just map each function to a dictionary, and use the user’s selection as the key to access that function object, and we then call the function. Do not call these functions while you are mapping them. When we have multiple “if” statements, python will evaluate them all, and that means more than one statement can be true thus running the code belonging to multiple conditions. This can sometimes be desirable but in this case, we do not need to keep evaluating.

Using “elif” tells python to ignore the remaining conditions if one evaluates to True. Again, be very careful with recursion, it’s a powerful tool but each time you recursively call one it adds a new layer in the memory stack to keep track of which call you’re on.

Fortunately python has a limit of 1000 recursive calls before you get an error, but if you have a small quantity of memory on your device you can crash your program. If you feel it is safer, then you can use a “while” or “for” loop. Keep in mind that a “while” loop is technically infinite, so make sure you properly define an exit condition.

 

We will be happy to hear your thoughts

Leave a reply

Python and Excel Projects for Practice
Register New Account
Shopping cart