This calculator reads a line such as 7 * 6, works out the answer and asks for the next line, until you type q. It checks every line and gives a clear message when the line is wrong.
The program
import operator OPERATIONS = { "+": operator.add, "-": operator.sub, "*": operator.mul, "/": operator.truediv, "**": operator.pow, } def calculate(line): parts = line.split() if len(parts) != 3: return "Type a number, an operator and a number, like 7 * 6" left, symbol, right = parts if symbol not in OPERATIONS: return f"Unknown operator {symbol!r}. Use one of: + - * / **" try: a = float(left) b = float(right) except ValueError: return "Both sides must be numbers" if symbol == "/" and b == 0: return "Cannot divide by zero" try: result = OPERATIONS[symbol](a, b) except OverflowError: return "That answer is too large to show" except ZeroDivisionError: return "Cannot divide by zero" return f"{result:g}" while True: line = input("calc> ") if line.strip() in ("q", "quit"): break print(calculate(line))
Typed in this example: 12 + 30, 7 * 6, 10 / 4, 5 / 0, 2 ^ 8, ten + 1, 2 ** 10, 10 ** 400, 0 ** -1, q. Press Run to type your own.
calc> 12 + 30 42 calc> 7 * 6 42 calc> 10 / 4 2.5 calc> 5 / 0 Cannot divide by zero calc> 2 ^ 8 Unknown operator '^'. Use one of: + - * / ** calc> ten + 1 Both sides must be numbers calc> 2 ** 10 1024 calc> 10 ** 400 That answer is too large to show calc> 0 ** -1 Cannot divide by zero calc> q
How it works
OPERATIONSis a dictionary that maps each symbol to a function.operator.add(a, b)does the same asa + b. Looking up the symbol replaces a long chain ofif/elif.line.split()splits the line on spaces. It must give exactly three parts, so the user has to type spaces around the operator.float()raises aValueErrorfor text liketen, and thetry/exceptturns that into a message.:gprints a number without a useless.0:42.0prints as42, and2.5stays2.5. It also rounds to 6 significant digits, so1234567 + 1prints as1.23457e+06. Use:.12gif you need more digits.2 ^ 8gives "Unknown operator". In Python,^is not a power. It is bitwise XOR, so the calculator rejects it and lists**instead.10 ** 400is too large for afloat, so Python raises anOverflowError.0 ** -1means1 / 0, so it raises aZeroDivisionErroreven though there is no/in the line. The secondtry/exceptturns both into messages.
Why not eval()?
Many calculator examples pass the whole line to eval(). That runs the text as Python code, so a user could type __import__('os').remove('notes.txt') and delete a file. Never call eval() on text that a user typed.
Another way: full expressions
To accept expressions like (2 + 3) * 4, with brackets and the normal order of operations, you can let Python's own parser read the text and then allow only numbers and the four operators. The ast module gives you the parsed expression as a tree:
import ast import operator ALLOWED = { ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv, ast.USub: operator.neg, } def evaluate(node): if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)): return node.value if isinstance(node, ast.BinOp) and type(node.op) in ALLOWED: return ALLOWED[type(node.op)](evaluate(node.left), evaluate(node.right)) if isinstance(node, ast.UnaryOp) and type(node.op) in ALLOWED: return ALLOWED[type(node.op)](evaluate(node.operand)) raise ValueError("only numbers and + - * / are allowed") def calculate(text): return evaluate(ast.parse(text, mode="eval").body) for expression in ["2 + 3 * 4", "(2 + 3) * 4", "100 / 8", "-4 * -2.5"]: print(expression, "=", calculate(expression)) try: calculate("__import__('os').getcwd()") except ValueError as error: print("Rejected:", error)
2 + 3 * 4 = 14 (2 + 3) * 4 = 20 100 / 8 = 12.5 -4 * -2.5 = 10.0 Rejected: only numbers and + - * / are allowed
evaluate() calls itself for the left and right side of each operator. Anything that is not a number or an allowed operator, such as a function call, raises a ValueError. The power operator is left out on purpose: 9 ** 9 ** 9 would keep the computer busy for a very long time.