Crear una calculadora en Python es un proyecto ideal para practicar entrada de datos, funciones, condiciones, bucles y manejo de errores. La primera versión puede ejecutar cuatro operaciones, pero una estructura limpia permite añadir historial, potencias, porcentajes y una interfaz gráfica sin reescribir todo el programa.
Para reforzar conceptos, consulta las guías sobre entrada y salida, funciones, excepciones, bucles y operadores. Como fuentes externas, revisa la documentación de funciones integradas y la documentación oficial de Decimal.
Separar las operaciones
def sumar(a, b):
return a + b
def restar(a, b):
return a - b
def multiplicar(a, b):
return a * b
def dividir(a, b):
if b == 0:
raise ZeroDivisionError("No se puede dividir por cero")
return a / bCada función hace una sola cosa, puede probarse de manera aislada y no depende de input() ni de print().
Mapa de operaciones
OPERACIONES = {
"+": sumar,
"-": restar,
"*": multiplicar,
"/": dividir,
}Un diccionario evita una cadena larga de if. También permite mostrar opciones y añadir nuevas funciones de forma consistente.
Leer números de forma segura
def leer_numero(mensaje):
while True:
texto = input(mensaje).strip().replace(",", ".")
try:
return float(texto)
except ValueError:
print("Escribe un número válido.")La sustitución de coma ayuda a usuarios acostumbrados a otra notación, pero una aplicación financiera debe utilizar reglas regionales explícitas y Decimal.
Elegir la operación
def leer_operacion():
while True:
simbolo = input("Operación (+, -, *, /): ").strip()
if simbolo in OPERACIONES:
return simbolo
print("Operación no disponible.")Validar antes de ejecutar produce mensajes más claros que esperar un error de clave.
Programa principal
def calcular():
a = leer_numero("Primer número: ")
simbolo = leer_operacion()
b = leer_numero("Segundo número: ")
funcion = OPERACIONES[simbolo]
resultado = funcion(a, b)
print(f"Resultado: {resultado}")
if __name__ == "__main__":
calcular()La comprobación de __name__ permite importar el archivo en pruebas sin iniciar la interacción.
Manejo de división por cero
try:
resultado = funcion(a, b)
except ZeroDivisionError as error:
print(error)
else:
print(f"Resultado: {resultado}")No devuelvas cero como respuesta a una división inválida, porque parecería un resultado matemático correcto.
Repetir cálculos
while True:
calcular()
continuar = input("¿Otro cálculo? (s/n): ").strip().lower()
if continuar != "s":
breakLa condición de salida debe ser evidente. Evita bucles infinitos con múltiples break difíciles de seguir.
Añadir potencias y resto
def potencia(a, b):
return a ** b
def resto(a, b):
if b == 0:
raise ZeroDivisionError("El divisor no puede ser cero")
return a % b
OPERACIONES["**"] = potencia
OPERACIONES["%"] = restoEl símbolo de porcentaje puede significar resto o porcentaje según el diseño. Explica claramente la convención.
Cálculos monetarios con Decimal
from decimal import Decimal, InvalidOperation
def leer_decimal(mensaje):
while True:
try:
return Decimal(input(mensaje).strip().replace(",", "."))
except InvalidOperation:
print("Cantidad inválida.")Los floats representan números binarios aproximados. Decimal es más apropiado cuando necesitas reglas decimales exactas y redondeo controlado.
Formatear la salida
print(f"Resultado: {resultado:.2f}")No redondees internamente después de cada paso salvo que el dominio lo exija. Conserva precisión y formatea al mostrar.
Historial
historial = []
historial.append({
"a": a,
"operacion": simbolo,
"b": b,
"resultado": resultado,
})El historial puede mostrarse al final o guardarse en JSON. No uses eval() para reconstruir expresiones de un archivo.
Por qué no usar eval()
eval() ejecuta código. Si una entrada procede del usuario, podría leer archivos, importar módulos o realizar acciones no deseadas. Una tabla explícita de operaciones es más segura, verificable y fácil de extender.
Pruebas unitarias
def test_sumar():
assert sumar(2, 3) == 5
def test_dividir():
assert dividir(10, 2) == 5Prueba negativos, cero, números decimales y operandos grandes. Para división por cero, confirma que se lance la excepción esperada.
Interfaz gráfica
Una versión con Tkinter debe conservar las funciones matemáticas separadas. Los botones solo recopilan datos, llaman a la lógica y muestran el resultado. Esta separación evita que el código de interfaz se convierta en una única función difícil de probar.
Memoria de resultado
Una mejora útil es permitir que el siguiente cálculo utilice el resultado anterior. Guarda el valor en una variable controlada y ofrece una opción explícita para reutilizarlo. No reemplaces silenciosamente el primer operando, porque el usuario podría no entender qué número se está usando. También puedes implementar comandos para limpiar memoria, mostrar historial y repetir la última operación.
Diseño de un menú extensible
Cuando existan muchas funciones, muestra nombre, símbolo y descripción desde una única estructura. Así la interfaz y la ejecución utilizan la misma fuente de verdad. Cada operación puede incluir la función, el número de operandos y una validación específica. Este patrón evita que el menú anuncie opciones que el motor todavía no soporta.
Errores frecuentes
Los fallos habituales son mezclar toda la lógica con input(), no validar texto, usar eval(), ocultar la división por cero, comparar floats exactamente y añadir operaciones mediante condiciones duplicadas. También es común olvidar que input() siempre devuelve una cadena.
Conclusión
Una calculadora pequeña puede enseñar arquitectura útil para proyectos mayores. Separa operaciones, valida entradas, utiliza un mapa de funciones y maneja errores específicos. Después añade historial, Decimal, pruebas e interfaz sin alterar el núcleo matemático. Esa evolución demuestra el valor de diseñar funciones simples desde el inicio.






