Pieces of Py #2: Use a dictionary instead of multiple if-statements

Posted on Thu 01 August 2019 in Python • 4 min read

One thing I learned during my first #100DaysOfCode was to use a dict instead of numerous if-statements. In my opinion it can make the code much more readable among other things. Here I'll try to make an example where this concept could fit in.

In this example we create a very simple calculator, that takes three parameters. As you can see we are using standard if-statements here. It could have been if-elses, but I think that makes it even harder to read.

def calculate(operator: str, value1: int, value2: int):
    """
    The calcluate function takes three arguments and returns the 
    result of the asked for calculation.

    operator: a string that determines what kind of calculation to do.
    value1: an integer of the first value to be calculated
    value2: an integer of the second value to be calculated

    """

    if operator == 'add':
        return value1 + value2
    if operator == 'sub':
        return value1 - value2
    if operator == 'mul':
        return value1 * value2
    if operator == 'div':
        return value1 / value2

    return None

Below you see the results of calling this function with it's different operators. I know, there is no error handling in this function whatsoever, but I try to keep it simple here and stay on topic.

calculate('add', 1, 2)

3

calculate('sub', 1, 2)

-1

calculate('mul', 1, 2)

2

calculate('div', 1, 2)

0.5

Using a dict instead of if-statements

Let's rewrite the calculate function, get rid of the if-statements and create a dict with out operators as keys, and a function (lamda in this case) as the value. If there had been more complex fuctions, I could have lifted out those to its own functions, and just put the function names as values in the dict.

But in this case let's go with some lambdas.

def calculate(operator: str, value1: int, value2: int):

    operate = {
        'add': lambda x, y: x + y,
        'sub': lambda x, y: x - y,
        'mul': lambda x, y: x * y,
        'div': lambda x, y: x / y,
    }

    # We use the get method on our operate dict to get the desired function.
    # The my_operator variable becomes the function we want to use. 
    # If the operator string we get as input isn't found in the dict,
    # the my_operator in this case gets 'None'

    my_operator =  operate.get(operator, None)

    return my_operator(value1, value2)

Calling our new function below, returns the same results as before.

calculate('add', 1, 2)

3

calculate('sub', 1, 2)

-1

calculate('mul', 1, 2)

2

calculate('div', 1, 2)

0.5

Of course this is a very simple example. If we for example call the function with a operator that doesn't exist, we get an error.

calculate('muldiv', 1, 2)
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-45-aad5c90e123c> in <module>
----> 1 calculate('muldiv', 1, 2)


<ipython-input-40-e74565bc1ae5> in calculate(operator, value1, value2)
     15     my_operator =  operate.get(operator, None)
     16 
---> 17     return my_operator(value1, value2)


TypeError: 'NoneType' object is not callable

This was a simple example to replace muliple if-statements with a dict, with keys and functions as values.

Using a list of numbers as input

One thing that hit me when I did this in an exercise, was that the calculate function I did was very limiting. It could only calculate two values. I needed the input to be a list of numbers. How would I accomplish that and still use lambdas? (Of course I could've easily, broken out each lambda to it's own separate function)

It turns out there was something in the functools library that I could use in this case - functools.reduce.

A quote from the functools.reduce documentation:

Apply function of two arguments cumulatively to the items of sequence, from left to right, so as to reduce the sequence to a single value. For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates ((((1+2)+3)+4)+5)

With functools.reduce I could use the lambda function as the first parameter, and send in a list of number as the second parameter.

import functools

def calculator(operation: str, numbers: list):

    operations = {
        'add': functools.reduce(lambda x, y: x + y, numbers),
        'sub': functools.reduce(lambda x, y: x - y, numbers),
        'mul': functools.reduce(lambda x, y: x * y, numbers),
        'div': functools.reduce(lambda x, y: x / y, numbers),
    }

    return operations.get(operation)
calculator('add', [1, 2, 3, 4])

10

calculator('sub', [1, 2, 3, 4])

-8

calculator('mul', [1, 2, 3, 4])

24

calculator('div', [1, 2, 3, 4])

0.041666666666666664

Conclusion

In this article I've given an easy example on how to use a dictionary instead of multiple if-statements to sort of emulate a switch/case statement. Since Python dictionaries can take almost anything as values for different keys, we used a function as the value which we then used to calculate numbers.

Resources

Let me know on Twitter if I can improve this article, or if you have other resources to help out with understanding this topic.