4

I just would like to know what is the best approach to writing a function that performs different calculations depending on a parameter.

As an extremely simple example, let's say that I want a function that either multiply or divide two numbers depending on the value of the parameter that should only take two values, 'multiply' or 'divide'. What I would do is something like this:

def simple_operation(a, b, operation):
    if operation == 'divide':
        return a / b
    elif operation == 'multiply':
        return a * b


print(simple_operation(3, 9, 'multiply'))

In my particular case, I want to calculate the equilibrium constant of a reaction as a function of temperature and there are different ways to do that. For example, using van 't Hoff equation or calculating the formation properties at a specific temperature. Each way (both have pros and cons) would need a fair amount of lines so I don't know what's the best approach, I feel like there might be a better way than using if-statements with a lot of code for each case. I would like to know how experienced programmers approach this kind of situations.

JaffXXI
  • 75
  • 7
  • Possible duplicate of [How to pass an operator to a python function?](https://stackoverflow.com/questions/18591778/how-to-pass-an-operator-to-a-python-function) – ruohola May 03 '19 at 07:21
  • 1
    Your approach is fine. If the operation itself is complex and requires a lot of code then create one or more extra functions and call those to keep the simple operation function 'simple'. – dwjbosman May 03 '19 at 07:26
  • Of course, using if-statements with only a function call for each case would be probably better. – Stop harming Monica May 03 '19 at 07:39

2 Answers2

5

Use a dict:

def simple_operation(a, b, operation):
    operations = {
        'divide'  : lambda a, b: a / b,
        'multiply': lambda a, b: a * b,
    }
    return operations.get(operation)(a, b)

You can add a default function for unknown operations:

def simple_operation(a, b, operation):
        def err(*_):
            raise ValueError("Operation not accepted")
        operations = {
            'divide'  : lambda a, b: a / b,
            'multiply': lambda a, b: a * b,
        }
        return operations.get(operation, err)(a, b)

You can reference anything in the dict, it may be good to use plain funtions instead of lambdas or the operator module:

import operator
def simple_operation(a, b, operation):
        def err(*_):
            raise ValueError("Operation not accepted")
        operations = {
            'divide'  : operator.truediv,
            'multiply': operator.mul,
        }
        return operations.get(operation, err)(a, b)
Netwave
  • 40,134
  • 6
  • 50
  • 93
  • 1
    Nice solution, but might be cleaner to name functions instead of lambdas if the calculations are lengthy. – glhr May 03 '19 at 07:23
  • @glhr, the lambdas are for the example, but anything can be referenced. – Netwave May 03 '19 at 07:24
  • Might be good to illustrate that in case OP doesn't know how to name a function from a dictionary. – glhr May 03 '19 at 07:25
  • Thank you very much for all your replies. Although, It seems like it'd be best to combine them all. As glhr mentioned my actual calculations are much more complicated than just dividing or multiplying, and dwjbosman said I could create other functions to keep the main function simple. I will try the dictionary approach as suggested by Netwave but referencing functions created by me. That's possible, right? – JaffXXI May 03 '19 at 07:46
  • @JaffXXI, yes, this here is just an example, as said in the last paragraph you can reference any function inside, I just decided to use those in the examples. – Netwave May 03 '19 at 07:47
  • Yes @JaffXXI you can build your own complex function on top of simple operators and refer to those in the dictionary, and ask them in the user input like I did in my answer below! – Devesh Kumar Singh May 03 '19 at 07:49
  • Would these functions need to be created inside the main function? In the example, inside the ```simple_operation``` function? – JaffXXI May 03 '19 at 08:14
  • @JaffXXI, no, they can be in any scope accessible by the `dict` – Netwave May 03 '19 at 08:15
  • 1
    I added a complex function in my example for you @JaffXXI ! Please check! Also considering accepting either mine or Netwave's answer based on whichever answer helped you the most :) – Devesh Kumar Singh May 03 '19 at 08:18
2

You can use operator module to pass the function you want to operate on You can have a custom dictionary mapping strings to relevant functions of operator module, and you can also get rid of the simple_operation wrapper with this

from operator import *

#Dictionary to map user input to operation
op_map = {'divide': truediv, 'multiply': mul, 'addition': add}

#Get user input and call functions directly
print(op_map['divide'](9, 3))
#3.0
print(op_map['multiply'](9, 3))
#27
print(op_map['addition'](9, 3))
#12

As an extra step I went ahead and wrote the user input part as well

from operator import *

#Dictionary to map user input to operation
op_map = {'divide': truediv, 'multiply': mul, 'addition': add}

operation = input('Enter what operation you want to perform, available are divide, multiply and addition>>')
if operation not in op_map.keys():
    print('You entered an invalid operation')
    exit()
else:
    op1 = int(input('Provide the first operand>>'))
    op2 = int(input('Provide the second  operand>>'))
    res = op_map[operation](op1, op2)
    print('Result of operation {} between {} and {} is {}'.format(operation, op1, op2, res))

The output might look like

Enter what operation you want to perform, available are divide, multiply and addition>>addition
Provide the first operand>>3
Provide the second   operand>>9
Result of operation addition between 3 and 9 is 12

Enter what operation you want to perform, available are divide, multiply and addition>>divide
Provide the first operand>>27
Provide the second   operand>>3
Result of operation divide between 27 and 3 is 9.0

Also as the OP mentions one way of using a complex function might be as below

#A complex function
def complex(a,b,c,d):

    return (a/b)+(c*d)

#Dictionary to map user input to operation
op_map = {'complex': complex}

print(op_map['complex'](9, 3, 6, 4))
#27.0
Devesh Kumar Singh
  • 20,259
  • 5
  • 21
  • 40