David Perek
David Perek

Reputation: 141

Using Python Interactive as a Programming Calculator

I have used AnalogX PCalc for years as my go to calculator for back of the envelope interactive calculations where integer math is important and flexible base input and output is important. For example, when working with fixed point numbers, often neither a decimal or hex view of numbers at once is helpful to wrap your head around issues. It also has nifty C math functions built in, as well as string functions that return integers. Downsides are obvious in that it is limited in function set and also Windows only.

I was thinking that Python interactive would be a much better calculator, and it generally already is, if you only care about one base, and don't care about integer math shenanigans. But for those two constraints at least, it is not so great. So at a minimum, would it be possible to get Python interactive to print integers in multiple bases, or another base beyond base 10 without prepending "hex()" every time?

Upvotes: 0

Views: 597

Answers (1)

farzad
farzad

Reputation: 8855

Python standard library provides code and codeop modules so the functionality of REPL can be used in other programs as well.

For example here is a sample Python file, extending the standard library classes to provide a new interactive console that converts integer results to other bases (currently base 2 and 16 are supported, but adding other bases should be easy). A couple of extra lines are used to support both Python 2 and 3.

#!/usr/bin/env python
from __future__ import print_function

import sys
import code

try:
    from StringIO import StringIO
except ImportError:
    from io import StringIO


class NumericConsole(code.InteractiveConsole):
    def __init__(self, *args, **kwargs):
        code.InteractiveConsole.__init__(self, *args, **kwargs)
        self.base = 10

    def runcode(self, code_to_run):
        return_val, output = self._call_and_capture_output(code.InteractiveConsole.runcode, self, code_to_run)
        try:
            output = self._to_target_base(output) + '\n'
        except ValueError:
            pass
        print(output, end='')
        return return_val

    def _to_target_base(self, value):
        # this can be extended to support more bases other than 2 or 16
        as_int = int(value.strip())
        number_to_base_funcs = {16: hex, 2: bin}
        return number_to_base_funcs.get(self.base, str)(as_int)

    def _call_and_capture_output(self, func, *args, **kwargs):
        output_buffer = StringIO()
        stdout = sys.stdout
        try:
            sys.stdout = output_buffer
            func_return = func(*args, **kwargs)
        finally:
            sys.stdout = stdout
        return (func_return, output_buffer.getvalue())


def interact(base=10):
    console = NumericConsole()
    console.base = base
    console.interact()


if __name__ == '__main__':
    base = len(sys.argv) > 1 and int(sys.argv[1]) or 10
    interact(base)

Now you can run this script and pass the desired base as the first CLI argument:

$ python <this_script> 16

And if the results of any expression is an integer, it will print it in hex format.

Of course more bases can be added (assuming you'll have a function in there to convert the decimal value to that base), and there are prettier ways to pass in CLI arguments.

Upvotes: 1

Related Questions