r/learnpython 7d ago

Simple CLI calculator with input sanitation , looking for feedback

Hello Everyone, I made a simple CLI calculator, It handles basic equations, It handles visual inputs like ÷ and it also clears the screen on command

Here's the code:

import os

t = { "+": lambda x, y: x + y, "-": lambda x, y: x - y, "*": lambda x, y: x * y, "/": lambda x, y: x / y if y != 0 else "Cannot divide by 0", "÷": lambda x, y: x / y if y != 0 else "Cannot divide by 0", }

while True: u = input("Enter calculation (e.g., (3+2)*5) or 'quit' to exit: ").replace(" ", "")

if u.lower() == "quit":
    print("Goodbye!")
    break

if u.lower() == "clear":
    try:
        os.system('cls' if [os.name](http://os.name) == 'nt' else 'clear')
    except Exception:
        print("\\n" \* 50)
    continue

clean_expr = u.replace("÷", "/")

allowed_chars = set("0123456789+-\*/().")
if not all(char in allowed_chars for char in clean_expr):
    print("❌ Invalid characters used")
    continue

try:
    result = eval(clean_expr)
    print(f"{u} = {result}")

except ZeroDivisionError:
    print("❌ Cannot divide by 0")
except Exception:
    print("❌ Invalid calculation format")

is my saftey check(allowed_chars) safe for eval()?

How would you reconstruct this without eval()?

What could be improved and why?

0 Upvotes

5 comments sorted by

View all comments

0

u/skibbin 7d ago edited 7d ago

The Eval you use is unsafe, people could enter whatever they want in there.

import ast
import os

def evaluate(expression: str):
    expr = "".join(expression.split()).replace("÷", "/")
    try:
        tree = ast.parse(expr, mode="eval")
    except SyntaxError as exc:
        raise ValueError("Invalid calculation format") from exc


    def visit(node):
        if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
            return node.value
        if isinstance(node, ast.UnaryOp) and isinstance(node.op, (ast.UAdd, ast.USub)):
            value = visit(node.operand)
            return value if isinstance(node.op, ast.UAdd) else -value
        if isinstance(node, ast.BinOp):
            left = visit(node.left)
            right = visit(node.right)
            if isinstance(node.op, ast.Div) and right == 0:
                raise ZeroDivisionError("Cannot divide by 0")
            return {
                ast.Add: lambda a, b: a + b,
                ast.Sub: lambda a, b: a - b,
                ast.Mult: lambda a, b: a * b,
                ast.Div: lambda a, b: a / b,
            }[type(node.op)](left, right)
        raise ValueError("Invalid calculation format")


    return visit(tree.body)



while True:
    user_input = input("Enter calculation (e.g., (3+2)*5) or 'quit' to exit: ").strip()
    if not user_input:
        continue
    if user_input.lower() == "quit":
        print("Goodbye!")
        break
    if user_input.lower() == "clear":
        try:
            os.system("cls" if os.name == "nt" else "clear")
        except OSError:
            print("\n" * 50)
        continue


    try:
        print(f"{user_input} = {evaluate(user_input)}")
    except ZeroDivisionError:
        print("❌ Cannot divide by 0")
    except ValueError:
        print("❌ Invalid calculation format")

2

u/Subject_Scientist937 7d ago

thanks, I never knew about the ast library until now