100 Días de Código en Python — Día 17: Crear Clases Propias y el Proyecto Quiz
Nivel: Grado décimo
Área: Tecnología e Informática
Modalidad: Tutorial práctico
Fuente: Curso "100 Days of Code: The Complete Python Pro Bootcamp" (Angela Yu, App Brewery). Contenido del Día 17 (nivel intermedio).
17 min de lectura
13 secciones
Este tutorial adapta pedagógicamente el Día 17. Las secciones 📝 Ejercicio original del curso provienen de los archivos del curso. Los retos 🟢🟡🟠🔴, el proyecto extra, el proyecto integrador y el banco de preguntas en español son creaciones de esta adaptación. Las correcciones al material original aparecen como 🛠️ Nota técnica.
(Fuentes: 116 - Day 17 Goals- what we will make by the end of the day.md, 126 - Run for that Bus!.md)
En el Día 16 usamos clases hechas por otros (Turtle, PrettyTable y la máquina de café). Hoy aprenderemos a crear nuestras propias clases, y con ellas construiremos un cuestionario de Verdadero o Falso que muestra preguntas, revisa las respuestas y lleva el puntaje, programado completamente con objetos.
Mensaje de cierre del curso: "¡corre para alcanzar el bus!". La instructora cuenta que de niña corría para alcanzar el bus del colegio aunque no supiera si llegaría. A veces llegaba y a veces no, pero siempre lo intentaba. "Corre hacia ese bus, porque la mitad de las veces lo alcanzarás."
Prerequisito general: Día 16 (objetos, atributos, métodos y notación de punto), funciones con parámetros y return (Días 8 y 10), listas de diccionarios (Día 9).
classUser:pass
user_1 = User()# construir un objeto a partir de la clase
class + el nombre + dos puntos:. Todo lo que va dentro de la clase lleva sangría.
Una clase (o función) vacía produce un error de sangría. La palabra clave pass significa "no hago nada por ahora; sigue".
🛠️ Nota técnica: la transcripción dice que la declaración termina con "un punto y coma". Es un error de traducción: se trata de dos puntos (:). En Python, el punto y coma (;) casi nunca se usa.
Convenciones de nombres:
Estilo
Forma
En Python se usa para…
PascalCase
NombreDeClase
Clases
camelCase
nombreDeVariable
Casi no se usa en Python (es común en JavaScript o Java)
snake_case
nombre_de_variable
Variables, funciones, métodos, atributos
El nombre PascalCase viene de Blaise Pascal: como en el nombre de una persona, cada palabra empieza con mayúscula.
🟢 Reto 1: crea las clases vacías Estudiante, Curso y Materia y construye un objeto de cada una.
🟡 Reto 2: escribe cada identificador con la convención correcta: "número de lista", "calcular promedio", "boletín académico", "nota final" (clase o variable, según el caso).
🟠 Reto 3: imprime un objeto de tu clase vacía. ¿Qué ves? Compáralo con print(timmy) del Día 16.
🔴 Reto 4: busca en la guía de estilo PEP 8 (se abre en otra pestaña) la sección de convenciones de nombres y resume en 5 líneas qué dice sobre las clases, las funciones y las constantes (Día 12).
Forma 1: agregar atributos "desde fuera" (funciona, pero es propensa a errores):
Python
123456
user_1 = User()
user_1.id="001"
user_1.username ="angela"
user_2 = User()
user_2.id="002"
user_2.usernme ="jack"# ¡error de tipeo! Crea un atributo distinto y Python no avisa
Forma 2: el constructor __init__ (recomendada):
Python
12345678910
classUser:def__init__(self, user_id, username):print("Nuevo usuario creado...")
self.id= user_id
self.username = username
self.followers =0# valor por defecto: no se recibe como parámetro
user_1 = User("001","angela")# 1 línea en lugar de 3print(user_1.username)# angelaprint(user_1.followers)# 0
__init__ (con dos guiones bajos a cada lado) es un método especial que Python ejecuta automáticamente cada vez que se construye un objeto. Se le llama constructor, y el proceso se llama inicializar.
self es el objeto que se está construyendo. self.id = user_id significa "el atributo id de este objeto toma el valor recibido".
El parámetro (user_id) y el atributo (self.id) pueden llamarse distinto, aunque la convención es que se llamen igual.
Valores por defecto: un atributo como followers empieza en 0 para todos los usuarios. No tiene sentido pedirlo al construir el objeto.
🟢 Reto 1: clase Estudiante con nombre y curso recibidos como parámetros, y notas = [] por defecto.
🟡 Reto 2: clase Carro con marca y asientos (el curso usa seats = 5). Crea dos carros y verifica que sus atributos son independientes.
🟠 Reto 3: demuestra con código el problema de la "Forma 1": crea un atributo con un error de tipeo y explica por qué Python no avisa.
🔴 Reto 4: investiga cómo dar un valor por defecto a un parámetro del constructor (def __init__(self, nombre, curso="10A"):). ¿En qué se diferencia de un atributo con valor por defecto?
Ejemplo del curso: seguir usuarios en una red social:
Python
12345678910111213141516
classUser:def__init__(self, user_id, username):
self.id= user_id
self.username = username
self.followers =0
self.following =0deffollow(self, user):
user.followers +=1# el OTRO usuario gana un seguidor
self.following +=1# YO sigo a una persona más
user_1 = User("001","angela")
user_2 = User("002","jack")
user_1.follow(user_2)print(user_1.followers, user_1.following)# 0 1print(user_2.followers, user_2.following)# 1 0
(Resultados verificados.)
Sobre self: "Nunca verás self cuando usas objetos, pero lo verás mucho cuando escribes tus clases." Al llamar user_1.follow(user_2), Python pasa user_1 como self automáticamente.
Olvidar self en la definición:def follow(user): produce TypeError: follow() takes 1 positional argument but 2 were given.
Olvidar self. al usar un atributo:followers += 1 dentro del método produce un UnboundLocalError o un NameError, porque crea o busca una variable local, no el atributo (Día 12).
Confundir el objeto con la clase:User.follow(user_2) no funciona como se espera, porque falta el objeto que sigue.
🟢 Reto 1: agrega a Estudiante el método agregar_nota(nota) y el método promedio(), que devuelva el promedio (Día 10).
🟡 Reto 2: agrega a User el método unfollow(user). ¿Qué validación necesita?
🟠 Reto 3: crea una clase CuentaAhorro con los métodos depositar, retirar (sin permitir saldo negativo) y consultar.
🔴 Reto 4: en tu User, ¿qué pasa si un usuario se sigue a sí mismo, o sigue dos veces al mismo usuario? Diseña atributos (por ejemplo, una lista seguidos) y validaciones para evitarlo.
06
4. 🚀 Proyecto del día: el Juego de Preguntas (Quiz)#
(Fuentes: 120 a 124, partes 1 a 5 del proyecto oficial del curso original)
Construir un cuestionario de Verdadero o Falso que muestre las preguntas numeradas, revise cada respuesta, informe la respuesta correcta y el puntaje acumulado, y al final muestre el puntaje final.
question_data: una lista de diccionarios con "text" y "answer"
Los datos
question_model.py
La clase Question
Modelar una pregunta
quiz_brain.py
La clase QuizBrain
La lógica del cuestionario
main.py
La unión de todo
Programa principal
El código inicial del curso (quiz-game-start, con 12 preguntas en inglés) no está incluido entre los materiales suministrados. Este tutorial usa un banco de preguntas en español creado para esta adaptación:
Python
123456
question_data =[{"text":"En Python, el primer índice de una lista es 0.","answer":"True"},{"text":"El operador % de Python calcula porcentajes.","answer":"False"},{"text":"len('Python') es igual a 6.","answer":"True"},{"text":"Un byte está formado por 8 bits.","answer":"True"},]
Texto
1234567891011121314
classDiagram
class Question {
text
answer
}
class QuizBrain {
question_number = 0
score = 0
question_list
still_has_questions()
next_question()
check_answer(user_answer, correct_answer)
}
QuizBrain --> Question : usa una lista de
Enunciado: en question_model.py, crea Question con un __init__ que reciba el texto y la respuesta y los guarde en los atributos text y answer.
Prueba:new_q = Question("2 + 3 = 5", "True") y luego print(new_q.text).
¿Cómo recorres question_data? ¿Qué clave usas para obtener el texto y cuál para la respuesta?
¿Por qué conviene tener objetos en lugar de diccionarios? (El curso lo explica: con claves de texto es fácil cometer errores de tipeo; con atributos, el editor autocompleta y avisa.)
Estructura de la solución: una lista vacía → un for → construir Question(texto, respuesta) → append. Con los datos del curso quedan 12 objetos.
Consejo de formato del curso: en PyCharm, Code → Auto-Indent Lines organiza la sangría de los datos, y las líneas de más de 120 caracteres se pueden dividir presionando Enter dentro del string.
Crea QuizBrain con question_number = 0 (valor por defecto) y question_list, que se recibe al construir el objeto.
Crea next_question(): toma la pregunta en la posición question_number, suma 1 al número y pide la respuesta con el formato P.1: texto (True/False):.
Detalle que resuelve el curso:question_number empieza en 0 para acceder al índice 0 de la lista, pero el usuario debe ver "P.1". La solución es sumar 1 justo después de tomar la pregunta y antes de mostrarla.
Enunciado: crea still_has_questions(), que devuelve True mientras queden preguntas, y úsalo en main.py con while quiz.still_has_questions():.
Simplificación que muestra el curso:
Python
12345678
# Versión largaif self.question_number <len(self.question_list):returnTrueelse:returnFalse# Versión corta: la comparación YA ES True o Falsereturn self.question_number <len(self.question_list)
Crea check_answer(user_answer, correct_answer). Compara en minúsculas (.lower()), suma un punto si acierta, muestra "¡Correcto!" o "Incorrecto" y, en ambos casos, la respuesta correcta y el puntaje score/question_number.
Al terminar, main.py muestra "Completaste el cuestionario" y el puntaje final.
Correcto, Correcto, Incorrecto, Incorrecto → puntaje final 2/4
🛠️ Nota técnica (verificada): la prueba 4 muestra un problema de la versión en español: un estudiante que responde "verdadero" recibe "Incorrecto", porque la respuesta guardada es "True". Mejoras: (a) aceptar v/f y verdadero/falso traduciéndolos antes de comparar, o (b) guardar las respuestas como True/False (booleanos) y convertir la entrada. Además, una entrada distinta de verdadero o falso debería volver a preguntarse, no contar como error (kit del Día 13).
El curso cambia todas las preguntas por otras de Open Trivia DB (se abre en otra pestaña), una base de datos gratuita con miles de preguntas de trivia aportadas y verificadas por su comunidad. Allí se genera una URL con el número de preguntas, la categoría, la dificultad y el tipo (verdadero o falso), y el resultado viene en formato JSON (JavaScript Object Notation), que se parece mucho a un diccionario de Python.
Lo que cambió en el código: solo dos claves en main.py, porque los nuevos datos usan "question" y "correct_answer" en lugar de "text" y "answer". QuizBrain no se tocó.
"Esto es modularidad en su máxima expresión." Podrías cambiar los datos a otro idioma, tema o formato, y a QuizBrain no le importaría: solo sabe llevar el número de pregunta, mostrar la siguiente y revisar la respuesta. Un compañero podría trabajar en QuizBrain mientras otro consigue los datos, sin estorbarse.
🛠️ Nota técnica: las preguntas de Open Trivia DB suelen incluir entidades HTML, como " en lugar de " y ' en lugar de '. Si se pegan tal cual, el cuestionario las muestra así. Solución:import html y html.unescape(texto). Verificado: html.unescape("The "Windows" OS's logo") → The "Windows" OS's logo. Además, las preguntas están en inglés.
Enunciado: genera 10 preguntas de Verdadero o Falso en Open Trivia DB (el curso usa la categoría de computación, dificultad fácil), reemplaza question_data con la lista "results" y ajusta main.py para que funcione sin tocar QuizBrain.
🟢 Reto 1: genera preguntas de otra categoría y verifica que el cuestionario funciona cambiando solo los datos.
🟡 Reto 2: aplica html.unescape al construir cada Question.
🟠 Reto 3: agrega a Question el atributo category, llénalo con los datos de Open Trivia DB y muéstralo antes de cada pregunta. ¿Tuviste que tocar QuizBrain?
🔴 Reto 4: escribe un párrafo que explique, con este proyecto como ejemplo, dos ventajas de la POO frente a la programación procedimental del Día 15.
08
6. 🌟 Proyecto extra: Banco de Preguntas del Colegio#
Cada estudiante escribe 5 preguntas de Verdadero o Falso de una materia (Ciencias, Sociales, Matemáticas…), verificando las respuestas en una fuente confiable, y las agrega a un data.py común.
Agregar a cada pregunta las claves "materia" y "autor".
Ampliar Question con esos atributos.
Al inicio, el usuario elige la materia (o "todas") y solo se construyen los objetos Question de esa materia.
Sin modificar la lógica de QuizBrain, salvo lo estrictamente necesario. Documenta qué cambiaste y por qué.
Cada pregunta la revisa otro estudiante, que confirma la respuesta con una fuente. Registra en un comentario quién la revisó.
09
7. 🏆 Proyecto integrador del Día 17: Tarjetas de Estudio de Selección Múltiple#
Proyecto integrador creado para esta adaptación. Integra la creación de clases (Día 17), el diseño con objetos (Día 16), random (Día 4), listas y diccionarios (Día 9), validación (Día 13) y ámbito (Día 12).
El Verdadero o Falso tiene un 50 % de probabilidad de acertar al azar. Para estudiar de verdad, los estudiantes quieren preguntas de selección múltiple y un informe de las preguntas en las que fallaron.
Actividad 1: preguntas generadas por IA. Pide a una IA 10 preguntas de Verdadero o Falso sobre la historia de Colombia. Luego verifica cada respuesta en una fuente confiable. ¿Cuántas eran incorrectas o ambiguas? (Las IA pueden producir afirmaciones falsas con total seguridad.) Solo las verificadas entran a tu data.py.
Actividad 2: revisión de diseño. Muestra tu diagrama de clases del integrador a una IA y pregúntale si las responsabilidades están bien repartidas. Evalúa críticamente sus sugerencias: ¿mejoran tu diseño o solo lo complican?
Clases propias Estudiante y User con __init__, atributos por defecto y métodos (Temas 2 y 3).
Quiz completo en 4 archivos, con las pruebas y la mejora para aceptar respuestas en español.
Versión del Quiz con datos de Open Trivia DB y html.unescape.
Proyecto extra (Banco del Colegio, con revisión por pares) o integrador (Selección Múltiple, con su diagrama de clases).
Cuaderno de errores:TypeError por faltar argumentos en __init__, TypeError por olvidar self en un método, IndexError por <= en still_has_questions y un atributo con error de tipeo creado "desde fuera".