Reverse Polish Notation calculator in Python

After reading the definition, I made a simple Reverse Polish Notation (RPN) calculator in Python.

Originally it had just 4 operators (using import operator and a lookup table) and only did integers. After looking at some example calculations, I amended it to work on floats and added a raising to powers. Then I saw some more advanced operations here and added math functions.

Owing to the large amount of math functions available, is there an easier way to include them without having to have math.<function> in the operators lookup?

# Reverse Polish Notation calculator
# based on http://en.wikipedia.org/wiki/Reverse_Polish_notation

import math
import operator
'-':operator.sub,
'*':operator.mul,
'/':operator.div,
'^':operator.pow,
'sin':math.sin,
'tan':math.tan,
'cos':math.cos,
'pi':math.pi}

def is_number(s):
try:
float(s)
return True
except ValueError:
pass

def calculate(equation):
stack = []
result = 0
for i in equation:
if is_number(i):
stack.insert(0,i)
else:
if len(stack) < 2:
print 'Error: insufficient values in expression'
break
else:
print 'stack: %s' % stack
if len(i) == 1:
n1 = float(stack.pop(1))
n2 = float(stack.pop(0))
result = ops[i](n1,n2)
stack.insert(0,str(result))
else:
n1 = float(stack.pop(0))
stack.insert(0,str(result))
return result

def main():
running = True
while running:
equation = raw_input('enter the equation: ').split(' ')
again = raw_input('\nEnter another? ')[0].upper()
if again != 'Y':
running = False

if __name__ == '__main__':
main()


Simple/straightforward test:

enter the equation: 6 4 5 + * 25 2 3 + / -
stack: ['5', '4', '6']
stack: ['9.0', '6']
stack: ['3', '2', '25', '54.0']
stack: ['5.0', '25', '54.0']
stack: ['5.0', '54.0']
RESULT: 49.000000


Math functions test:

enter the equation: 5 8 2 15 * sin * + 2 45 tan + /
stack: ['15', '2', '8', '5']
stack: ['30.0', '8', '5']
stack: ['0.5', '8', '5']
stack: ['4.0', '5']
stack: ['45', '2', '9.0']
stack: ['1.0', '2', '9.0']
stack: ['3.0', '9.0']
RESULT: 3.000000


• In Python, list is designed to grow efficiently from the end. Implementing a stack using append and pop at the end is therefore a better approach.
• I would expect is_number to return False instead of the implicit None which usually stands for a missing value.
• An explicit math. in front of functions makes clear where they come from. If you want to support more functions you have other things to worry about. For example, the forced conversion from degrees to radians only makes sense with certain functions.
• You need to store explicitly the number of arguments each operator expects. Your current solution is to infer that from the length of the operator's name. That is not an extensible approach.

You could try something like this:

import math
x = 5
methodToCall = getattr(math, 'sin')
result = methodToCall(x)


So you basically evaluate the string and get the corresponding method.