Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

Essentially what I've done here is written an Interpreter that takes in Python math, such as this, 23 ** 374748, and prints out the result. This program relies on the fact that in Python 2.7 input() is "broken". This means it can take in Python math.

# Main parser class
class Parser(object):
    def __init__(self):
        self.PROMPT = "MaTh @ ==> "
        self.ERR_MSG = "Error: Invalid math!"

    # Parse inputted math
    def parse_math(self):
        while True:
            try:
                math = input(self.PROMPT)
                print "{}\n".format(math)
            except:
                print self.ERR_MSG


# Run the program
if __name__ == "__main__":
    Parser().parse_math()

Can I improve anything here? Is it possible for this to be Python3 compatible?

share|improve this question
1  
You can approximate the behaviour of Python 2 input() by using eval(input()) in Python 3. –  Greg Hewgill Aug 10 at 20:34
3  
From this PyCon video: "if it has two methods, one of which is __init__, it shouldn't be a class." You could write a single standalone parse_math function that did exactly the same. –  Jaime Aug 10 at 21:36

3 Answers 3

up vote 2 down vote accepted

For 2.x and 3.x compatibility, I would explicitly use eval(raw_input(...)); you can then use:

import sys

if sys.version_info[0] > 2:
    raw_input = input

This makes more sense if you're writing for 2.x first, and is slightly easier than shoehorning eval onto 3.x's input everywhere you need it (which also makes life more difficult everywhere you don't).

Also for compatibility, print is a function in 3.x. You can import from __future__, or just use

print("{}\n".format(math))

in both.


You could make the constants class attributes:

class Parser(object):

    PROMPT = "..."
    ERR_MSG = "..."

    def parse_math(self):
        ...

These are still accessible via self, so your other code doesn't change. Note that now there are no instance attributes, you don't need __init__. In fact, you could take this further and make parse_math a class method, then you don't need to create an instance to call it.


Try giving the user a way to escape the loop. Perhaps:

while True:
    math = raw_input(self.PROMPT)
    if math.lower() == "done":
        break
    try:
        math = eval(math)
    except (NameError, SyntaxError):
        print(self.ERR_MSG)
    else:
        print("{}\n".format(math))

Note here also

  • Splitting input and evaluation to allow pre-evaluation checking;
  • Catching specific errors (see e.g. here); and
  • Using the else to minimise the code in the try block.

Finally, your code has no documentation. This is simple enough to not really need it, but it's a habit worth getting into. See e.g. PEP-257.

share|improve this answer

Security

This program is capable of doing a lot more than just interpreting math. Consider what will happen with…

MaTh @ ==> list(open('/etc/passwd'))

MaTh @ ==> __import__('os').unlink(__import__('sys').argv[0])

In summary, any kind of eval() is dangerous, and you should be extremely cautious about feeding arbitrary user input to be evaluated (which is what input() does).

Misnomer

Your MathParser is much more than a parser — it's a full read-eval-print loop.

Infinite loop

There is no clean way to exit the program, not even ControlC or EOF.

share|improve this answer

How to get out?

It would be good to have an intuitive way to exit the interpreter. With Control-D for example. It triggers an EOFError, so you could do that like this:

try:
    math = input(self.PROMPT)
    print("{}\n".format(math))
except EOFError:
    print('')
    break
except:
    print(self.ERR_MSG)

Before breaking out of the loop, I added a print('') to print a blank line, otherwise the shell will return control to the user in the middle of the prompt line. This way you could do things like:

python math_interpreter.py <<< '3 + 4'
# or
echo '3 + 4' | python math_interpreter.py

To work in both Python 2.7+ and 3.0, I changed the print something statements to Python 3 style print(something).

Use class attributes

Instead of setting PROMPT and ERR_MSG in __init__, you could make them class attributes:

class Parser(object):
    PROMPT = "MaTh @ ==> "
    ERR_MSG = "Error: Invalid math!\n"

Python 3 support

As @Greg said, you can use eval(input()) in Python 3 to approximate the behavior of input() in Python 2. To make the program work with both Python versions, you need to detect the version and switch the implementations accordingly. Maybe with something like this:

import sys
if sys.version_info[0] < 3:
    def input_and_eval(prompt):
        return input(prompt)
else:
    def input_and_eval(prompt):
        return eval(input(prompt))

You can use this input_and_eval function, which wraps the version specific prompting and evaluation.

Suggested implementation

#!/usr/bin/env python

import sys
if sys.version_info[0] < 3:
    def input_and_eval(prompt):
        return input(prompt)
else:
    def input_and_eval(prompt):
        return eval(input(prompt))


class Parser(object):
    PROMPT = "MaTh @ ==> "
    ERR_MSG = "Error: Invalid math!\n"

    def parse_math(self):
        while True:
            try:
                math = input_and_eval(self.PROMPT)
                print("{}\n".format(math))
            except EOFError:
                print('')
                break
            except:
                print(self.ERR_MSG)


if __name__ == "__main__":
    Parser().parse_math()
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.