El Día 010 del reto 100 Días de Python construye una calculadora de línea de comandos para sumar, restar, multiplicar y dividir. También permite encadenar operaciones, usando el resultado de un cálculo como punto de partida del siguiente.
La idea más interesante de este proyecto es usar un diccionario para asociar los símbolos de los operadores directamente con funciones, lo que evita una larga cadena de instrucciones if/elif para elegir qué cálculo ejecutar. Es un patrón que vale la pena entender bien, ya que escala de forma limpia cuando agregas más operaciones.
Esta guía recorre el código fuente completo del Día 010, explica el patrón del diccionario de operadores y la lógica de encadenamiento, y señala dos vacíos reales en el manejo de errores del programa.
Descripción del Proyecto
Cada operación aritmética tiene su propia función. Un diccionario asocia el símbolo de un operador con la función correspondiente:
operations = {
"+": addition,
"-": subtraction,
"*": multiplication,
"/": division,
}
Esto permite que la calculadora elija y llame una operación sin una larga cadena de condicionales. La función get_number() pide un dato una y otra vez hasta que puede convertir la respuesta a float. Después de calcular un resultado, el usuario puede continuar con esa respuesta o iniciar un cálculo nuevo.
Código Fuente
El proyecto del Día 010 está en day010/main.py, dentro del repositorio 100daysofpython:
from art import LOGO
print(LOGO)
print("*******************************************")
print("Welcome to The Calculator!")
print("*******************************************")
def addition(n1, n2):
return n1 + n2
def subtraction(n1, n2):
return n1 - n2
def multiplication(n1, n2):
return n1 * n2
def division(n1, n2):
return n1 / n2
operations = {
"+": addition,
"-": subtraction,
"*": multiplication,
"/": division
}
def get_number(prompt):
while True:
try:
return float(input(prompt))
except ValueError:
print("Invalid input! Please enter a number.")
def calculator():
num1 = get_number("What is the first number?: ")
should_accumulate = True
while should_accumulate:
for symbol in operations:
print(symbol)
while True:
operation_symbol = input("Pick an operation: ")
if operation_symbol in operations:
break
print("Invalid operator! Please choose +, -, * or /")
num2 = get_number("What is the next number?: ")
answer = operations[operation_symbol](num1, num2)
print(f"{num1} {operation_symbol} {num2} = {answer}")
choice = input(f"Type 'y' to continue calculating with {answer}, or type 'n' to start a new calculation: ")
if choice == "y":
num1 = answer
else:
should_accumulate = False
print("\n" * 20)
calculator()
calculator()
Cómo Funciona el Programa
Asociar Operadores con Funciones
operations = {
"+": addition,
"-": subtraction,
"*": multiplication,
"/": division
}
En Python, las funciones son valores, igual que los números o las cadenas de texto: se pueden guardar en un diccionario, pasar de un lado a otro y llamar después. Este diccionario asocia cada símbolo de operador directamente con la función que lo implementa, así que buscar y llamar la operación correcta toma una sola línea:
answer = operations[operation_symbol](num1, num2)
operations[operation_symbol] obtiene la función correspondiente (por ejemplo, addition), y el (num1, num2) final la llama de inmediato. Agregar un operador nuevo más adelante, como % para el módulo, solo requiere escribir una función nueva y añadir una entrada al diccionario, sin cambiar la lógica de búsqueda.
Validar Datos Numéricos con una Función Reutilizable
def get_number(prompt):
while True:
try:
return float(input(prompt))
except ValueError:
print("Invalid input! Please enter a number.")
En lugar de duplicar el mismo ciclo de validación para el primer y el segundo número, get_number() es una función pequeña y reutilizable que sigue preguntando hasta recibir un dato válido y entonces hace return en cuanto lo logra. Es una forma limpia de evitar repetir dos veces el mismo patrón try/except.
Encadenar Cálculos
if choice == "y":
num1 = answer
Cuando el usuario decide seguir calculando, el answer anterior se convierte en el nuevo num1 y el ciclo continúa. Esto permite que algo como 10 + 5 y luego * 2 sobre el resultado funcione como dos pasos interactivos separados en lugar de una sola expresión.
Dos Vacíos Reales en el Manejo de Errores
Sin Protección Contra la División entre Cero
def division(n1, n2):
return n1 / n2
A diferencia de get_number(), que valida su dato antes de devolverlo, division() no hace ninguna verificación. Si el usuario elige / e ingresa 0 como segundo número, Python lanza un ZeroDivisionError que nada en este programa captura: la calculadora se cierra con un error en lugar de mostrar un mensaje amigable.
El Reinicio Usa Recursión en Lugar de un Ciclo
else:
should_accumulate = False
print("\n" * 20)
calculator()
Cuando el usuario quiere iniciar un cálculo nuevo, el programa vuelve a llamar a calculator() desde dentro de sí mismo: esto es recursión. Funciona bien en el uso normal, pero cada vez que alguien inicia un cálculo nuevo, Python agrega otro marco a la pila de llamadas, y esos marcos no se liberan hasta que todas las llamadas anidadas terminan. Si reinicias suficientes veces en una misma sesión (mucho más allá del uso normal, pero no imposible en una prueba automatizada), el límite de recursión predeterminado de Python acabaría lanzando un RecursionError. Un ciclo while True que envuelva todo el cuerpo de la función lograría el mismo comportamiento de reinicio sin ese riesgo.
Qué Enseña Este Proyecto
El Día 010 combina:
- Funciones como valores, guardadas y buscadas en un diccionario
- Funciones reutilizables de validación de datos
- Ciclos para encadenar cálculos usando un resultado acumulado
- La recursión como una forma (imperfecta) de reiniciar el flujo de un programa
- Dónde tiene vacíos el manejo de errores de un programa y por qué importa
El proyecto es intencionalmente simple y no implementa la precedencia matemática: cada operación se realiza en el orden en que el usuario la selecciona, por lo que 10 + 5 * 2 ingresado en dos pasos separados calcula (10 + 5) * 2, no 10 + (5 * 2).
Ejecutar el Proyecto
git clone https://github.com/victorhugo81/100daysofpython.git
cd 100daysofpython/day010
python main.py
Posibles Siguientes Pasos
Cuando entiendas el código existente, intenta ampliarlo:
- Agrega un
try/except ZeroDivisionErroralrededor de la llamada a la división, para que dividir entre cero muestre un mensaje amigable en lugar de cerrarse con un error - Reemplaza el reinicio recursivo por un ciclo
while Truealrededor de toda la calculadora, evitando cualquier preocupación por la profundidad de recursión - Agrega más operadores, como la potenciación (
**) o el módulo (%), para ver cuán poco código requiere el patrón del diccionario por cada uno nuevo - Registra y muestra un historial de todos los cálculos realizados en la sesión
Conclusión
El Día 010 del reto 100 Días de Python muestra cómo asociar operadores con funciones en un diccionario mantiene limpia y fácil de ampliar la lógica de una calculadora, mientras que encadenar resultados mediante un ciclo permite hacer cálculos de varios pasos. También es un caso de estudio útil sobre un manejo de errores incompleto: la falta de verificación de la división entre cero y el reinicio recursivo son fáciles de pasar por alto en una primera lectura, y por eso es tan importante recorrer cada rama de la lógica de un programa.
Consulta el código fuente del Día 010 en GitHub y sigue explorando el índice de proyectos del repositorio mientras construyes más programas en Python, un día y un proyecto pequeño a la vez.
