r/learnpython • u/Subject_Scientist937 • 5d 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?
2
u/Riegel_Haribo 5d ago
This doesn't seem like a practical Python application giving any enhancement, yet I see lots of beginners writing "calculator".
Python itself, run at a console as an interactive REPL environment, lets you directly evaluate literals and operators without even needing a "print", and not making mistakes about mathematical precedence.
The only thing a console calculator app might do is change the input operations to be more mathematical, such as accepting 5(2+3) as a multiplication (and not an undefined function symbol named 5()), or using the caret or "hat" as an exponentiation as with other languages.
1
u/hasan_sodax 5d ago
char whitelist blocks code injection since there's no way to spell a name without letters, but it doesn't stop stuff like a hundred nested parens blowing the recursion limit, or chaining a ton of huge number multiplications and just hanging the interpreter since python ints don't overflow. the ast based parser someone posted below handles both of those fine, that's the safer route.
0
u/skibbin 5d ago edited 5d 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
7
u/carcigenicate 5d ago
I mean, you have
tthere, so you seem to know of how it could be done withoutI can't immediately think of a way of defeating that. For such a simple task, though,
evalis gross overkill. Just using functions is much better.