MousE0910
MousE0910

Reputation: 77

Python dictionary switch goes through other functions instead of just the one called

I'm making an extremely simple calculator style app where a control function is called with 5 parameters. The first one is a string that sets the mode for the calculator and the other 4 are numbers being used. I've used a dictionary to facilitate the switch between various modes.

However, it seems the application is checking all functions with the numbers instead of only the one I want. For example, if I use negative numbers in addition, I get math domain error in the square root function (that obviously cannot use negative numbers). Using normal positive numbers works without problems.

Thank you for any help or information about why this is happening and how to fix it.

Here is my primitive code:

import math

def control(a, x, y, z, k):
    return {
        'ADDITION': addition(x, y),
        'SUBTRACTION': subtraction(x, y),
        'MULTIPLICATION': multiplication(x, y),
        'DIVISION': division(x, y),
        'MOD': modulo(x, y),
        'SECONDPOWER': secondPower(x),
        'POWER': power(x, y),
        'SECONDRADIX': secondRadix(x),
        'MAGIC': magic(x, y, z, k)
    }[a]

def addition(x, y):
    return float(x) + float(y)

def subtraction(x, y):
    return float(x) - float(y)

def multiplication(x, y):
    return float(x) * float(y)

def division(x, y):
    return float(x) / float(y)

def modulo(x, y):
    return float(x) % float(y)

def secondPower(x):
    return math.pow(float(x),2.0)

def power(x, y):
    return math.pow(float(x),float(y))

def secondRadix(x):
    return math.sqrt(float(x))

def magic(x, y, z, k):
    l = float(x) + float(k)
    m = float(y) + float(z)
    return (l / m) + 1.0

a = input()
x = input()
y = input()
z = input()
k = input()

try:
    control(a, x, y, z, k)
except ValueError:
    print("This operation is not supported for given input parameters")

out = control(a, x, y, z, k)
print(out)

And this is the log:

C:\<path>\Python\Python35-32\python.exe C:/<path>/calculator.py
ADDITION
-6.0
5
3
2
This operation is not supported for given input parameters
Traceback (most recent call last):
  File "C:/<path>/calculator.py", line 67, in <module>
    out = control(a, x, y, z, k)
  File "C:/<path>/calculator.py", line 13, in control
    'SECONDRADIX': secondRadix(x),
  File "C:/<path>/calculator.py", line 47, in secondRadix
    return math.sqrt(float(x))
ValueError: math domain error

Process finished with exit code 1

Upvotes: 2

Views: 611

Answers (4)

Steven Rumbalski
Steven Rumbalski

Reputation: 45542

You need to delay evaluating your functions:

from functools import partial

def control(a, x, y, z, k):
    return {
        'ADDITION':       partial(addition, x, y),
        'SUBTRACTION':    partial(subtraction, x, y),
        'MULTIPLICATION': partial(multiplication, x, y),
        'DIVISION':       partial(division, x, y),
        'MOD':            partial(modulo, x, y),
        'SECONDPOWER':    partial(secondPower, x),
        'POWER':          partial(power, x, y),
        'SECONDRADIX':    partial(secondRadix, x),
        'MAGIC':          partial(magic, x, y, z, k)
    }[a]()

It is inefficient to create curried functions on each call to control so here is a second approach that creates your dictionary only once:

def control(a, x, y, z, k):
    return control.funcs[a](x, y, z, k)
control.funcs = {
    'ADDITION':       (lambda x, y, z, k: addition(x, y)),
    'SUBTRACTION':    (lambda x, y, z, k: subtraction(x, y)),
    'MULTIPLICATION': (lambda x, y, z, k: multiplication(x, y)),
    'DIVISION':       (lambda x, y, z, k: division(x, y)),
    'MOD':            (lambda x, y, z, k: modulo(x, y)),
    'SECONDPOWER':    (lambda x, y, z, k: secondPower(x)),
    'POWER':          (lambda x, y, z, k: power(x, y)),
    'SECONDRADIX':    (lambda x, y, z, k: secondRadix(x)),
    'MAGIC':          (lambda x, y, z, k: magic(x, y, z, k))
}

At this point it may be simpler do away with the control function and just use the dictionary directly.

Upvotes: 4

reticentroot
reticentroot

Reputation: 3682

The easiest thing to do is rewrite the code, it is more code, but it is easier to maintain and you won't run all the methods, only the one you want since it seems you were just returning the single key anyway. If you want to run more then one operation, then pass in the operations as a string e.g, a = input() --> "ADDITION SUBTRACTION", then split(" ") to create a list. Iterate over that list and pass a into the method control.

import math

def control(a, x, y, z, k):
    if a == 'ADDITION':
        return addition(x, y)
    elif a == 'SUBTRACTION':
        return subtraction(x, y)
    elif a == 'MULTIPLICATION':
        return multiplication(x, y)
    elif a == 'DIVISION':
        return division(x, y),
    elif a == 'MOD':
        return  modulo(x, y)
    elif a == 'SECONDPOWER':
        return secondPower(x)
    elif a == 'POWER':
        return power(x, y)
    elif a == 'SECONDRADIX':
        return secondRadix(x)
    elif a == 'MAGIC':
        return magic(x, y, z, k)

Upvotes: 1

AChampion
AChampion

Reputation: 30258

I'm not a great fan of big if-elif-elif-else but understand the need to avoid constructing the dictionary every time.
Expanding on @C.B.'s answer - you can still use a dictionary dispatch mechanism but just store the function and number of arguments in the dictionary.
This can now be created once outside of the function, e.g.:

dispatch = {
    'ADDITION': (addition, 2),
    'SUBTRACTION': (subtraction, 2),
    'MULTIPLICATION': (multiplication, 2),
    'DIVISION': (division, 2),
    'MOD': (modulo, 2),
    'SECONDPOWER': (secondPower, 1),
    'POWER': (power, 2),
    'SECONDRADIX': (secondRadix, 1),
    'MAGIC': (magic, 4)
}

def control(a, *args):
    func, count = dispatch[a] 
    return func(*args[:count])

Upvotes: 0

C.B.
C.B.

Reputation: 8326

When you pass all the arguments to control, each individual function is evaluated since you have parens appended to it. You end up trying to calculate math.sqrt(float(-6)) which is an imaginary number. In order to avoid this, you would have to design control with a generic *args such as

def control(a, *args):
    return {
        'ADDITION': addition,
        'SUBTRACTION': subtraction,
        'MULTIPLICATION': multiplication,
        'DIVISION': division,
        'MOD': modulo,
        'SECONDPOWER': secondPower,
        'POWER': power,
        'SECONDRADIX': secondRadix,
        'MAGIC': magic
    }[a](*args)

Which allows you to retrieve the one function you are interested in and then call it with your arguments.

Upvotes: 0

Related Questions