Python

Calculator in Python for dummies


17th of December 2007

I need a mini calculator in my web app so that people can enter basic mathematical expressions instead of having to work it out themselfs and then enter the result in the input box. I want them to be able to enter "3*2" or "110/3" without having to do the math first. I want this to work like a pocket calculator such that 110/3 returns a 36.6666666667 and not 36 like pure Python arithmetic would. Here's the solution which works but works like Python:

 def safe_eval(expr, symbols={}):
    return eval(expr, dict(__builtins__=None), symbols)

 def calc(expr):
    return safe_eval(expr, vars(math))

 assert calc('3*2')==6
 assert calc('12.12 + 3.75 - 10*0.5')==10.87
 assert calc('110/3')==36

But to make it work like non-Python-geek users would expect it I ended up with the following solution which also adds a few more bells and whistles:

 import math
 import re
 integers_regex = re.compile(r'\b[\d\.]+\b')

 def calc(expr, advanced=False):
    def safe_eval(expr, symbols={}):
        return eval(expr, dict(__builtins__=None), symbols)
    def whole_number_to_float(match):
        group = match.group()
        if group.find('.') == -1:
            return group + '.0'
        return group
    expr = expr.replace('^','**')
    expr = integers_regex.sub(whole_number_to_float, expr)
    if advanced:
        return safe_eval(expr, vars(math))
    else:
        return safe_eval(expr)

 def test():
    print calc("147.43 - 40") # 107.43
    print calc('110/3') # 36.6666666667
    print calc('110/3.0') # 36.6666666667
    print calc('(10-(3+5))^2') # 4.0
    print calc('sys.exit(100)') # None
    print calc('a+b') # None
    print calc('(3+10))') # None
    print calc('del expr') # None
    print calc('cos(2*pi)') # None
    print calc('pow(3,2)', advanced=True) # 9.0
    print calc('cos(2*pi)', advanced=True) # 1.0

What this does is that it replaces whole numbers into floating point looking numbers before the expression is evaluated. It also replaces ** with ^ as an alias because I think most non-Python people expect 10^2 to be 100.

I haven't put this into production yet. I'm still playing around with it to get a feel for how it could work and what the implications might be. There is of course more work needed to wrap this with try-except statements so that dodgy attempts are captured correctly.



Comment

Show all 16 comments
 

Commenting is currently disabled in Mobile version