Recipe3.14.Using Python as a Simple Adding Machine


Recipe 3.14. Using Python as a Simple Adding Machine

Credit: Brett Cannon

Problem

You want to use Python as a simple adding machine, with accurate decimal (not binary floating-point!) computations and a "tape" that shows the numbers in an uncluttered columnar view.

Solution

To perform the computations, we can rely on the decimal module. We accept input lines, each made up of a number followed by an arithmetic operator, an empty line to request the current total, and q to terminate the program:

import decimal, re, operator parse_input = re.compile(r'''(?x)  # allow comments and whitespace in the RE               (\d+\.?\d*)          # number with optional decimal part               \s*                  # optional whitespace               ([-+/*])             # operator               $''')                # end-of-string oper = { '+': operator.add, '-': operator.sub,          '*': operator.mul, '/': operator.truediv,        } total = decimal.Decimal('0') def print_total( ):     print '==  ==  =\n', total print """Welcome to Adding Machine:  Enter a number and operator,  an empty line to see the current subtotal, or q to quit: """ while True:     try:         tape_line = raw_input( ).strip( )     except EOFError:         tape_line = 'q'     if not tape_line:         print_total( )         continue     elif tape_line == 'q':         print_total( )         break     try:         num_text, op = parse_input.match(tape_line).groups( )     except AttributeError:         print 'Invalid entry: %r' % tape_line         print 'Enter number and operator, empty line for total, q to quit'         continue     total = oper[op](total, decimal.Decimal(num_text))

Discussion

Python's interactive interpreter is often a useful calculator, but a simpler "adding machine" also has its uses. For example, an expression such as 2345634+2894756-2345823 is not easy to read, so checking that you're entering the right numbers for a computation is not all that simple. An adding machine's tape shows numbers in a simple, uncluttered columnar view, making it easier to double check what you have entered. Moreover, the decimal module performs computations in the normal, decimal-based way we need in real life, rather than in the floating-point arithmetic preferred by scientists, engineers, and today's computers.

When you run the script in this recipe from a normal command shell (this script is not meant to be run from within a Python interactive interpreter!), the script prompts you once, and then just sits there, waiting for input. Type a number (one or more digits, then optionally a decimal point, then optionally more digits), followed by an operator (/, *, -, or + the four operator characters you find on the numeric keypad on your keyboard), and then press return. The script applies the number to the running total using the operator. To output the current total, just enter a blank line. To quit, enter the letter q and press return. This simple interface matches the input/output conventions of a typical simple adding machine, removing the need to have some other form of output.

The decimal package is part of Python's standard library since version 2.4. If you're still using Python 2.3, visit http://www.taniquetil.com.ar/facundo/bdvfiles/get_decimal.html and download and install the package in whatever form is most convenient for you. decimal allows high-precision decimal arithmetic, which is more convenient for many uses (such as any computation involving money) than the binary floating-point computations that are faster on today's computers and which Python uses by default. No more lost pennies due to hard-to-understand issues with binary floating point! As demonstrated in Recipe 3.13, you can even change the rounding rules from the default of ROUND_HALF_EVEN, if you really need to.

This recipe's script is meant to be very simple, so many improvements are possible. A useful enhancement would be to keep the "tape" on disk for later checking. You can do that easily, by adding, just before the loop, a statement to open some appropriate text file for append:

tapefile = open('tapefile.txt', 'a')

and, just after the try/except statement that obtains a value for tape_line, a statement to write that value to the file:

tapefile.write(tape_line+'\n')

If you do want to make these additions, you will probably also want to enrich function print_total so that it writes to the "tape" file as well as to the command window, therefore, change the function to:

def print_total( ):     print '==  ==  =\n', total     tapefile.write('==  ==  =\n' + str(total) + '\n')

The write method of a file object accepts a string as its argument and does not implicitly terminate the line as the print statement does, so we need to explicitly call the str built-in function and explicitly add '\n' as needed. Alternatively, the second statement in this version of print_total could be coded in a way closer to the first one:

    print >>tapefile, '==  ==  =\n', total

Some people really dislike this print >>somefile, syntax, but it can come in handy in cases such as this one.

More ambitious improvements would be to remove the need to press Return after each operator (that would require performing unbuffered input and dealing with one character at a time, rather than using the handy but line-oriented built-in function raw_input as the recipe doessee Recipe 2.23 for a cross-platform way to get unbuffered input), to add a clear function (or clarify to users that inputting 0* will zero out the "tape"), and even to add a GUI that looks like an adding machine. However, I'm leaving any such improvements as exercises for the reader.

One important point about the recipe's implementation is the oper dictionary, which uses operator characters (/, *, -, +) as keys and the appropriate arithmetic functions from the built-in module operator, as corresponding values. The same effect could be obtained, more verbosely, by a "tree" of if/elif, such as:

if op == '+':     total = total + decimal.Decimal(num_text) elif op == '-':     total = total - decimal.Decimal(num_text) elif op == '*':     <line_annotation>... and so on ...</line_annotation>

However, Python dictionaries are very idiomatic and handy for such uses, and they lead to less repetitious and thus more maintainable code.

See Also

decimal is documented in the Python 2.4 Library Reference, and is available for download to use with 2.3 at http://www.taniquetil.com.ar/facundo/bdvfiles/get_decimal.html; you can read the decimal PEP 327 at http://www.python.org/peps/pep-0327.html.



Python Cookbook
Python Cookbook
ISBN: 0596007973
EAN: 2147483647
Year: 2004
Pages: 420

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net