En este artículo, va a construir una aplicación de tablas de multiplicar, utilizando el poder de la Programación Orientada a Objetos (POO) en Python
Practicará los principales conceptos de la P.O.O .y cómo utilizarlos en una aplicación totalmente funcional
Python es un lenguaje de programación multiparadigma, lo que significa que nosotros como desarrolladores podemos elegir la mejor opción para cada situación y problema. Cuando hablamos de Programación Orientada a Objetos, nos referimos a uno de los paradigmas más utilizados para construir aplicaciones escalables, en las últimas décadas
Los fundamentos de la POO
Vamos a echar un vistazo rápido al concepto más importante de la P.O.O. en Python, las clases
Una clase es una plantilla en la que definimos la estructura y el comportamiento, de los objetos. Esa plantilla nos permite crear Instancias, que no son otra cosa que objetos individuales hechos siguiendo la composición de la clase
Una simple clase libro, con los atributos de título y color, se definiría de la siguiente manera
class Libro:
def __init__(self, título, color):
self.title = título
self.color = color
Si queremos crear instancias de la clase libro, debemos llamar a la clase y pasarle argumentos
# Objetos instancia de la clase Libro
libro_azul = Libro("El niño azul", "Azul")
libro_verde = Libro("La historia de la rana", "Verde")
Una buena representación de nuestro programa actual sería
Lo sorprendente es que cuando comprobamos el tipo de las instancias de libro_azul y libro_verde, obtenemos "Libro"
# Imprimiendo el tipo de los libros
print(type(blue_book))
# <clase '__main__.Libro'>
print(type(green_book))
#
<clase '__main__.Libro'>
Después de tener claros estos conceptos, podemos empezar a construir el proyecto 😃
Declaración del proyecto
Mientras trabajamos como desarrolladores/programadores, la mayor parte del tiempo no lo pasamos escribiendo código, según thenewstack sólo pasamos un tercio de nuestro tiempo escribiendo o refactorizando código
Los otros dos tercios los pasamos leyendo el código de otros y analizando el problema en el que estamos trabajando
Así que para este proyecto, generaré un enunciado del problema y analizaremos cómo crear nuestra aplicación a partir de él. Como resultado, estamos realizando el proceso completo, desde pensar en la solución hasta aplicarla con código
Un profesor de primaria quiere un juego para poner a prueba las habilidades de multiplicación de los alumnos de 8 a 10 años.
El juego debe tener un sistema de vidas y otro de puntos, en el que el alumno empiece con 3 vidas y deba alcanzar una determinada cantidad de puntos para ganar. El programa debe mostrar un mensaje de "perder" si el alumno agota todas sus vidas.
El juego debe tener dos modos, multiplicaciones aleatorias y multiplicaciones de tabla.
El primero debe dar al alumno una multiplicación aleatoria del 1 al 10, y éste debe responder correctamente para ganar un punto. Si no lo hace, el alumno pierde un vivo y el juego continúa. El alumno sólo gana cuando alcanza 5 puntos.
El segundo modo debe mostrar una tabla de multiplicar del 1 al 10, en la que el alumno debe introducir el resultado de la multiplicación correspondiente. Si el alumno falla 3 veces pierde, pero si completa dos tablas, el juego termina
Sé que los requisitos pueden ser un poco mayores, pero le prometo que los resolveremos en este artículo 😁
Divide y vencerás
La habilidad más importante en programación es la resolución de problemas. Esto se debe a que usted necesita tener un plan antes de empezar a hackear el código
Siempre sugiero tomar el problema más grande y dividirlo en otros más pequeños que puedan ser a la vez, fácil y eficientemente resueltos
Así que si necesita crear un juego, empiece por dividirlo en las partes más importantes. Estos subproblemas serán mucho más fáciles de resolver
Sólo entonces podrá tener claro cómo ejecutar e integrar todo con código
Hagamos un gráfico de cómo sería el juego
Este gráfico establece las relaciones entre los objetos de nuestra aplicación. Como puede ver, los dos objetos principales son Multiplicación aleatoria y Multiplicación por tablas. Y lo único que comparten son los atributos Puntos y Vidas
Teniendo en cuenta toda esta información, pasemos al código
Creación de la clase de juego Padre
Cuando trabajamos con programación orientada a objetos, buscamos la forma más limpia de evitar la repetición de código. A esto se le llama SECO (no te repitas) (no te repitas)
Nota: Este objetivo no está relacionado con escribir el menor número de líneas de código (la calidad del código no debe medirse por ese aspecto) sino con abstraer la lógica más utilizado
De acuerdo con la idea anterior, la clase padre de nuestra aplicación debe establecer la estructura y el comportamiento deseado de las otras dos clases
Veamos cómo se haría
class BaseGame:
# Longitud que centra el mensaje
message_lenght = 60
description = ""
def __init__(self, points_to_win, n_lives=3):
"""Clase base del juego
Args:
points_to_win (int): los puntos que necesitará el juego para ser terminado
n_lives (int): El número de vidas que tiene el alumno. Por defecto es 3.
"""
self.points_to_win = points_to_win
self.points = 0
self.vidas = n_vidas
def get_numeric_input(self, message=""):
while True:
# Obtiene la entrada del usuario
user_input = input(message)
# Si la entrada es numérica, la devuelve
# Si no lo es, imprime un mensaje y repite
if user_input.isnumeric():
return int(user_input)
else:
print("La entrada debe ser un número")
continue
def print_welcome_message(self):
print("JUEGO DE MULTIPLICACIÓN DE PITONES".center(self.longitud_del_mensaje))
def print_lose_message(self):
print("LO SIENTO HA PERDIDO TODAS SUS VIDAS".center(self.longitud_del_mensaje))
def print_win_message(self):
print(f "ENHORABUENA HAYA ALCANZADO {self.puntos}".center(self.longitud_del_mensaje))
def print_current_lives(self):
print(f "Actualmente tiene {self.vidas\n")
def print_current_score(self):
print(f"\nTu puntuación es {self.points}")
def print_description(self):
print("\n\n" self.description.center(self.message_lenght) "\n")
# Método básico de ejecución
def run(self):
self.print_welcome_message()
self.print_description()
Vaya, parece una clase enorme. Permítame explicarla en profundidad
En primer lugar, entendemos los atributos de la clase y el constructor
Básicamente, los atributos de clase, son variables creadas dentro de la clase, pero fuera del constructor o de cualquier método
Mientras que los atributos de instancia son variables creadas solo dentro del constructor
La principal diferencia entre, estos dos es el alcance. es decir, los atributos de clase son accesibles tanto, desde un objeto instancia como desde la clase. Por otro lado, los atributos de instancia sólo son accesibles desde un objeto instancia
game = BaseGame(5)
# Acceso a la longitud del mensaje de la clase desde la clase
print(game.message_lenght) # 60
# Acceso a la longitud del mensaje desde la clase
print(BaseGame.message_lenght) # 60
# Acceso a los puntos desde la instancia
print(game.points) # 0
#
Acceso a los puntos desde la clase
print(BaseGame.points) # Error de atributo
Otro artículo puede profundizar en este tema. Manténgase en contacto para leerlo
La funciónget_numeric_input
se utiliza para evitar que el usuario proporcione cualquier entrada que no sea numérica. Como puede observar, este método está diseñado para preguntar al usuario hasta obtener una entrada numérica. Lo utilizaremos más adelante en las clases del niño
Los métodos print nos permiten ahorrarnos la repetición de imprimir lo mismo cada vez que ocurre un evento en el juego
Por último, pero no menos importante, el método ejecute es sólo una envoltura que las clases Multiplicación aleatoria y Multiplicación de tablas utilizarán para interactuar con el usuario y hacer que todo sea funcional
Creación de las clases del niño
Una vez que hemos creado esa clase padre, que establece la estructura y parte de la funcionalidad de nuestra aplicación, es hora de construir las clases del modo de juego propiamente dicho, utilizando el poder de la herencia
Clase de multiplicación aleatoria
Esta clase ejecutará el "primer modo" de nuestro juego. Va a utilizar el módulo aleatorio , por supuesto, que nos dará la capacidad de pedir al usuario operaciones aleatorias del 1 al 10. Aquí tiene un excelente artículo sobre el módulo random (y otros módulos importantes) 😉
import random # Módulo para
operaciones aleatorias
class RandomMultiplication(BaseGame):
description = "En este juego debes responder correctamente a la multiplicación aleatoria\nGanas si alcanzas 5 puntos, o pierdes si pierdes todas tus vidas"
def __init__(self):
# El número de puntos necesarios para ganar son 5
# Pasa 5 "puntos_para_ganar" como argumento
super().__init__(5)
def get_random_numbers(self):
first_number = random.randint(1, 10)
segundo_número = random.randint(1, 10)
return primer_número, segundo_número
def run(self):
# Llama a la clase superior para imprimir los mensajes de bienvenida
super().run()
while self.lives > 0 and self.points_to_win > self.points:
# Obtiene dos números aleatorios
number1, number2 = self.get_random_numbers()
operation = f"{número1} x {número2}: "
# Pide al usuario que responda a esa operación
# Evita errores de valor
user_answer = self.get_numeric_input(message=operation)
if user_answer == number1 * number2:
print("\nSu respuesta es correcta\n")
# Añade un punto
self.puntos = 1
else:
print("\nLo siento, su respuesta es incorrecta\n")
# Resta un vivo
self.vidas -= 1
self.print_current_score()
self.print_current_lives()
# Sólo se ejecuta cuando el juego ha terminado
# Y ninguna de las condiciones es cierta
else:
# Imprime el mensaje final
if self.points >= self.points_to_win:
self.print_win_message()
else:
self.print_lose_message()
He aquí otra clase masiva 😅. Pero como he dicho antes, no es el número de líneas que ocupa, es lo legible y eficiente que es. Y lo mejor de Python es que permite a los desarrolladores hacer código limpio y legible como si estuvieran hablando inglés normal
Esta clase tiene una cosa que puede confundirle, pero se lo explicaré de la forma más sencilla posible
# Clase padre
def __init__(self, puntos_para_ganar, n_vidas=3):
“..
.
# Clase hija
def __init__(self):
# El número de puntos necesarios para ganar es 5
# Pase 5 "puntos_para_ganar" como argumento
super().__init__(5)
El constructor de la clase hija está llamando a la función super que, al mismo tiempo hace referencia a la clase padre (BaseGame). Básicamente le está diciendo a Python
Rellene
¡el atributo "puntos_para_ganar" de la clase padre con 5!
No es necesario poner auto, dentro de la parte super().__init__()
sólo porque estamos llamando a super dentro del constructor, y resultaría redundante
También estamos utilizando la función super en el método de ejecución, y veremos lo que ocurre en ese trozo de código
# Método básico run
# Método padre
def run(self):
self.print_welcome_message()
self.print_description()
def run(self):
# Llama a la clase superior para imprimir los mensajes de bienvenida
super().run()
....
Como puede observar, el método run de la clase superior imprime el mensaje de bienvenida y la descripción. Pero es una buena idea mantener esa funcionalidad y además añadir otras adicionales en las clases hijas. De acuerdo con esto, utilizamos super para ejecutar todo el código del método padre antes de ejecutar la siguiente pieza
La otra parte de la función ejecutar es bastante sencilla. Pide al usuario un número con el mensaje de la operación que debe responder. Luego se compara el resultado con la multiplicación real y si son iguales, se añade un punto, si no lo son se quita 1 vida
Vale la pena decir que estamos utilizando bucles while-else. Esto excede el alcance de este artículo pero publicaré uno sobre ello en unos días
Por último, get_random_numbers, utiliza la función random.randint, que devuelve un número entero aleatorio dentro del rango especificado. A continuación, devuelve una tupla de dos enteros aleatorios
Clase de multiplicación aleatoria
El "segundo modo", debe mostrar el juego en formato de tabla de multiplicar, y asegurarse de que el usuario responde correctamente al menos a 2 tablas
Para ello, utilizaremos de nuevo el poder de super y modificaremos el atributo de la clase padre puntos_para_ganar a 2
class TablaMultiplicacion(BaseGame):
description = "En este juego debe resolver correctamente la tabla de multiplicar completa\NGana si resuelve 2 tablas"
def __init__(self):
# Necesita completar 2 tablas para ganar
super().__init__(2)
def run(self):
# Imprime mensajes de bienvenida
super().run()
while self.lives > 0 and self.points_to_win > self.puntos:
# Obtiene dos números aleatorios
number = random.randint(1, 10)
for i in range(1, 11):
if self.lives <= 0:
# Asegura que el juego no pueda continuar
# si el usuario agota las vidas
self.points = 0
break
operation = f"{number} x {i}: "
user_answer = self.get_numeric_input(message=operation)
if user_answer == number * i:
print("¡Genial! Su respuesta es correcta")
else:
print("Lo sentimos, su respuesta no es correcta")
self.lives -= 1
self.puntos = 1
# Sólo se ejecuta cuando el juego ha terminado
# Y ninguna de las condiciones es cierta
else:
# Imprime el mensaje final
if self.points >= self.points_to_win:
self.print_win_message()
else:
self.print_lose_message()
Como puede darse cuenta sólo estamos modificando el método de ejecución de esta clase. Esa es la magia de la herencia, escribimos una vez la lógica que usamos en varios sitios, y nos olvidamos de ella 😅
En el método de ejecución, estamos utilizando un bucle for para obtener los números del 1 al 10 y construimos la operación que se muestra al usuario
Una vez más, si se agotan las vidas o se alcanzan los puntos necesarios para ganar, el bucle while se romperá y se mostrará el mensaje de ganar o perder.
SÍ, hemos creado los dos modos del juego, pero hasta ahora si ejecutamos el programa no ocurrirá nada
Así que finalicemos el programa implementando la elección del modo, e instanciando las clases dependiendo de esa elección.
Implementación de la elección
El usuario podrá elegir a qué modo quiere jugar. Así que veamos cómo implementarlo
if __name__ == "__main__":
print("Seleccione el modo de juego")
choice = input("[1],[2]: ")
if choice == "1":
game = RandomMultiplication()
elif choice == "2":
game = TableMultiplication()
else:
print("Por favor, seleccione un modo de juego válido")
exit()
game.run()
En primer lugar, pedimos al usuario que elija entre los modos 1 ó 2. Si la entrada no es válida, el script deja de ejecutarse. Si el usuario selecciona el primer modo, el programa ejecutará el modo de juego Multiplicación aleatoria , y si selecciona el segundo, se ejecutará el modo Multiplicación de tablas
Así es como quedaría
Conclusión
Enhorabuena, acaba de construir una aplicación Python con Programación Orientada a Objetos
Todo el código está disponible en el repositorio de Github
En este artículo usted aprendió a
- Usar constructores de clases Python
- Crear una app funcional con POO
- Utilizar la función super en las clases Python
- Aplicar los conceptos básicos de herencia
- Implementar atributos de clase e instancia
Feliz codificación 👨💻
A continuación, explore algunos de los mejores IDE de Python para mejorar su productividad.