Reputation: 77
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
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
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
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
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