Learning Python

Xavier Morel xavier.morel at masklinn.net
Sun Feb 5 13:46:25 EST 2006


Byte wrote:
>> parse the expression, extract the operands and the operation, apply the
> operation to the operands
> 
> How? Give me some example code, but please keep it simple.
> 
>> are you trying to code a calculator?
> 
> Not intending to, just trying to learn Python. Suppose what i'm trying
> to code is a but like a CLI calculator, but i'm just writing it for my
> own educational beifits.
> 

Side note: have you read Dive into Python (http://diveintopython.org/) 
yet? If you haven't -- and are done reading the whole python tutorial -- 
you should, it's a very good and practical read.

Now about the calculator, let's do a simple RPN notation (they're much 
simpler to compute than the "regular" notation, see 
http://en.wikipedia.org/wiki/RPN for some informations about RPN)

First, we need an input

-->
expression = raw_input("> ")
<--

Now we need to handle this input. An RPN expression is a space-separated 
list of tokens, these tokens being either operands (numbers) or 
operations. This means that we merely need to split our expression at 
the spaces

-->
expression = raw_input("> ")
tokens = expression.split() # split a string using spaces as default 
split delimiter
<--

Now we have a list of tokens.

RPN calculations involve a stack, to keep the current operands. Python's 
`list` has everything you need to emulate a stack

-->
expression = raw_input("> ")
tokens = expression.split() # split a string using spaces as default 
split delimiter
stack = list()
<--

Now, we have to handle each token.
Either a token is a number (an operand), in which case we have to 
convert it to an integer (it's currently stored as a string), or it's an 
operation, in which case we have to apply the operation on the last two 
tokens of the computation stack and store the result back on the stack.

This means that we have to store addition, substraction, multiplication 
and division. Luckily, there is the python module `operator` that stores 
an accessor to these operations (add, sub, mul, div). We could store 
them in a dictionary with an operation token as index: we feed the 
operation token to the dict, it outputs the operation function.

-->
import operator

operations = {
     "+": operator.add,
     "-": operator.sub,
     "*": operator.mul,
     "/": operator.div
}

expression = raw_input("> ")
tokens = expression.split() # split a string using spaces as default 
split delimiter
stack = list()
<--

Good, now all we need to know is to feed each token to the `operations` 
dict, let's try with the first token of the RPN expression "1 2 + 4 * 3 +"

 >>> operations['1']

Traceback (most recent call last):
   File "<pyshell#12>", line 1, in -toplevel-
     operations['1']
KeyError: '1'

Well, there is no "1" key in our dictionary, so we get an error. Kind of 
obvious.
The Python dictionaries also have a "get" method that takes a key and a 
value, and they return the value (default) if the key doesn't exist, how 
would that one fare?

 >>> operations.get('1',None)
 >>> operations.get('2',None)
 >>> operations.get('+',None)
<built-in function add>
 >>>

Ok, it's return the operation function if it knows the token we feed it, 
and "None" if it doesn't.
Now all we have to do is check if it returns None, and if it does we 
know it's an operand and not an operation.

First we need to loop on the list of tokens:

-->
import operator

operations = {
     "+": operator.add,
     "-": operator.sub,
     "*": operator.mul,
     "/": operator.div
}

expression = raw_input("> ")
tokens = expression.split() # split a string using spaces as default 
split delimiter
stack = list()

for token in tokens:
     operation = operations.get(token, None)
     if operation: # If operation is not "None", therefore if the token 
was an operation token
         pass # do nothing for now
     else: # if operation is "None" == if the token is an operand token
         pass # do nothing either
<--

Ok, what are we supposed to do if the token is an operand? Convert the 
operand (token) to an integer (the token is a string), and then add it 
to our computational stack.
The last part is done via the `append` methods of  Python's list.

If the token is an operation, we have to apply the operation to the last 
two operands of the stack, and push the result back on the stack
To get the last operation of a Python list, we use the `pop`method, it 
defaults to returning the last value of a list.

-->
import operator

operations = {
     "+": operator.add,
     "-": operator.sub,
     "*": operator.mul,
     "/": operator.div
}

expression = raw_input("> ")
tokens = expression.split() # split a string using spaces as default 
split delimiter
stack = list()

for token in tokens:
     operation = operations.get(token, None)
     if operation: # If operation is not "None", therefore if the token 
was an operation token
         result = operation(stack.pop(), stack.pop()) # apply the 
operation on the last two items of the stack
	stack.append(result) # put the result back on the stack
     else: # if operation is "None" == if the token is an operand token
         stack.append(int(token)) # Convert the token to an integer and 
push it on the stack
<--

Ok, let's test that shall we?
What happens if we feed it "1 2 + 4 * 3 +", this should return 15 = 
(1+2)*4+3.
The result is the only item that should be left on our stack

-->
 >>> operations = {
     "+": operator.add,
     "-": operator.sub,
     "*": operator.mul,
     "/": operator.div
}
 >>> expression = raw_input("> ")
 > 1 2 + 4 * 3 +
 >>> tokens = expression.split() # split a string using spaces as 
default split delimiter
 >>> stack = list()
 >>> for token in tokens:
	operation = operations.get(token, None)
	if operation: # If operation is not "None", therefore if the token was 
an operation token
		result = operation(stack.pop(), stack.pop()) # apply the operation on 
the last two items of the stack
		stack.append(result) # put the result back on the stack
	else: # if operation is "None" == if the token is an operand token
		stack.append(int(token)) # Convert the token to an integer and push it 
on the stack

		
 >>> stack
[15]
 >>>
<--
Gread. Now let's try it again with the expression "1 4 -". This should 
return -3.

-->
 >>> # snip
 >>> stack
[3]
<--
Duh, the result is wrong.

This is because when we do operation(stack.pop(), stack.pop()), we use 
the last value of the stack as the first operand of our operation (here, 
our expression resolves to "operator.sub(4, 1)" which is equivalent to 
"4-1", which is wrong).
Let's get out operands in the good order by replacing that line with

-->
operand2 = stack.pop()
operand1 = stack.pop()
result = operation(operand1, operand2)
<--

And now it works, we have a basic RPN calculator.

Homework: truth is that this calculator kind of sucks: it's very easy to 
break it.

Try using "-" as your input expression. Or "1 +", or "1 56 + +". What 
happens?
Try to modify the calculator to avoid these errors, or handle them 
gracefully.

Likewise, entering the expression "5 0 /" results in a Division By Zero 
error. Try to handle it to display a friendly message instead of having 
the calculator blow.

You have to rewrite everything every time you want to compute a new 
expression. Modify the calculator to always ask for new expression until 
a specific command is entered (such as "exit")

The calculator breaks (again) if you use a token that is neither an 
integer nor an operation (e.g. "1 foo +", or even "foo")
Modify it to handle that kind of false input without exploding in your 
hands.

The output is ugly (a list of only one item), try to improve it.

Computing the expression "5 3 /" (5/3) returns "1". Why? How can you 
improve it?

Additionally, you currently cannot use floating point numbers (e.g. "1.5 
1.3 +") in your expressions (guess what the calculator does?), see if 
you could modify the calculator to handle floating point numbers.

Last, but not the least, try to create a Polish Notation calculator 
(prefix notation, it's like RPN but the operation is in front of the 
operand) and a "maths notation" calculator.



More information about the Python-list mailing list