Skip to content

Python de Cero a Pro - Cap. 11 Clases y Objetos

¡Hola! En este capítulo, exploraremos un tema fundamental para organizar datos en Python: las colecciones. Las colecciones son estructuras que nos permiten agrupar múltiples valores de manera eficiente, lo que es esencial para construir programas complejos y bien estructurados.

Volver a Página Principal

Ver Codigo en GitLab


Python de Cero a Pro - Cap. 11 Clases 🚀

Section titled “Python de Cero a Pro - Cap. 11 Clases 🚀”

¡Bienvenidos al Capítulo 11 de “Python de Cero a Pro”! En esta entrega, profundizaremos en uno de los pilares de la programación orientada a objetos (POO) en Python: las clases. Las clases nos permiten estructurar nuestro código de una manera más organizada, modular y reutilizable, lo cual es fundamental para desarrollar aplicaciones robustas y fáciles de mantener. 🛠️


¿Qué son las Clases y Por Qué Son Importantes? 🤔

Section titled “¿Qué son las Clases y Por Qué Son Importantes? 🤔”

Imagina las clases como planos blueprints 📝 o moldes 🍪 para crear objetos. Un objeto es una instancia de una clase, y cada objeto tiene sus propias características (llamadas atributos 🏷️) y comportamientos (llamados métodos ⚙️).

Por ejemplo, si tenemos una clase Coche 🚗, los objetos creados a partir de ella (un “Ford Fiesta”, un “Tesla Model 3”) compartirán características generales (tienen ruedas, motor, color) pero con valores específicos para cada una (el Fiesta es rojo, el Tesla es blanco). Los comportamientos serían acciones como “arrancar” ▶️, “frenar” 🛑 o “acelerar” 💨.

Las clases nos permiten agrupar datos y las funciones que operan sobre esos datos, lo que mejora la organización 📂, la encapsulación 🔒 y la reutilización del código. ♻️


Nuestra Estructura de Proyecto Inicial 📁

Section titled “Nuestra Estructura de Proyecto Inicial 📁”

Antes de sumergirnos en la creación de nuestras nuevas clases, recordemos cómo estaba estructurado nuestro proyecto y cómo se manejaba la información de una persona de forma funcional:

tu_proyecto/
├── app.py
└── modules/
├── __init__.py
├── registro.py
└── parsers_date.py
└── models/
# app.py (Versión anterior)
#from modules.registro import Pedir_Nombre # Comentado, se usa import as
import modules.registro as Registro
from modules.parsers_date import parse_date
def main():
#counter = 0
name = Registro.Pedir_Nombre()
while not name:
print("No has ingresado un nombre válido.")
name = Registro.Pedir_Nombre()
#counter = counter + 1
#Imprimir_Nombre(name, counter)
birth = Registro.Pedir_Fecha_Nacimiento()
parser_date = parse_date(birth)
while parser_date is None:
print("No has ingresado una fecha de nacimiento válida.")
birth = Registro.Pedir_Fecha_Nacimiento()
parser_date = parse_date(birth) # Corregido: antes usaba Registro.parser_date
age = Registro.Calcular_Edad(parser_date)
Registro.Imprimir_Data(name, age)
if __name__ == "__main__":
main()

modules/registro.py (Versión anterior) ⏪

Section titled “modules/registro.py (Versión anterior) ⏪”
# modules/registro.py (Versión anterior)
from datetime import datetime
def Imprimir_Data(name, age):
print(f"Bienvenido {name}, Tu edad es: {age} años")
def Pedir_Nombre():
print("Bienvenido a nuestra app")
name = input("Cuál es tu nombre: ")
return name
def Pedir_Fecha_Nacimiento():
print("Gracias por Darnos tu Nombre.")
birth = input("Cuál es tu fecha de nacimiento: ")
return birth
def Calcular_Edad(birthday):
fecha_actual = datetime.now()
edad = fecha_actual.year - birthday.year - ((fecha_actual.month, fecha_actual.day)< (birthday.month, birthday.day))
return edad

modules/parsers_date.py (Versión anterior) ⏪

Section titled “modules/parsers_date.py (Versión anterior) ⏪”
# modules/parsers_date.py (Versión anterior)
from datetime import datetime
def parse_date(input_date):
formats_availaible = [
"%Y-%m-%d", #YYYY-MM-DD
"%d-%m-%Y", #DD-MM-YYYY
"%m/%d/%Y", #MM/DD/YYYY
"%Y/%m/%d" #YYYY/MM/DD
]
for formato in formats_availaible:
try:
date_parse = datetime.strptime(input_date, formato)
return date_parse
except ValueError:
pass
return None

Como puedes ver, la información de la persona (nombre, fecha de nacimiento, edad) se maneja a través de variables separadas y funciones que operan sobre ellas. Esto funciona, pero no agrupa lógicamente los datos con sus operaciones. 🧩


Nueva Estructura del Proyecto con Clases ✨

Section titled “Nueva Estructura del Proyecto con Clases ✨”

Para mejorar la organización y aplicar la POO, reestructuraremos nuestro proyecto de la siguiente manera:

tu_proyecto/
├── app.py
├── models/ <-- ¡NUEVO DIRECTORIO! 🆕
│ ├── __init__.py
│ └── persona.py <-- Aquí estará nuestra clase principal (modelo de datos) 👤
└── modules/
├── __init__.py
├── registro.py <-- ¡Ahora será una CLASE para manejar la entrada de datos! 📚
└── parsers_date.py <-- Contendrá la función de parseo de fechas 🗓️

En esta estructura:

  • models/persona.py: Contendrá la clase Persona, que encapsulará los datos de una persona (nombre, fecha de nacimiento, edad) y la lógica para calcular su edad. Esta es tu “clase de registro” para una entidad individual. 🧍‍♀️

  • modules/registro.py: Ahora contendrá la clase Registro, la cual agrupará las funciones para pedir el nombre y la fecha de nacimiento al usuario, y la lógica para calcular la edad. ✍️

  • modules/parsers_date.py: Mantendrá la función para parsear las fechas. ➡️📅

  • app.py: Será el punto de entrada principal y utilizará la clase Registro para obtener los datos y la clase Persona para crear y gestionar los datos de la persona. 🚀


Creando Nuestra Clase Persona en models/persona.py 🧑‍💻

Section titled “Creando Nuestra Clase Persona en models/persona.py 🧑‍💻”

Aquí definiremos la clase Persona que encapsulará la información de una persona.

models/persona.py
from datetime import datetime
# Importamos la función parse_date desde modules.parsers_date
from modules.parsers_date import parse_date
# Importamos la función Calcular_Edad desde modules.registro (ahora será un método de clase)
# Por ahora, la importamos directamente si registro.py aún no es una clase
# O, si registro.py ya es una clase, la importamos de la clase Registro
# Para este ejemplo, asumiremos que Calcular_Edad será un método estático o de clase en Registro.
# Por lo tanto, la importación se ajustará en el __init__ de Persona para llamar a Registro.Calcular_Edad
# Para evitar una dependencia circular directa en la importación de módulos,
# la lógica de cálculo de edad se puede mantener aquí o se puede refactorizar.
# Para simplicidad y siguiendo la estructura, la clase Persona usará directamente la función Calcular_Edad
# que estará en modules/registro.py (como método de clase o estático).
# Para la clase Persona, vamos a asumir que Calcular_Edad es una función independiente
# o un método estático/de clase que se puede llamar directamente.
# Para evitar dependencias circulares complejas en las importaciones,
# es mejor que Calcular_Edad sea una función en modules/registro.py
# o que se mueva a un módulo de utilidades más general.
# Para este ejercicio, la importaremos como si fuera una función del módulo registro.
from modules.registro import Calcular_Edad # Importación directa de la función
class Persona:
"""
Clase que representa a una persona con su nombre, fecha de nacimiento y edad.
Encapsula la información y la lógica relacionada con una persona.
"""
def __init__(self, nombre, fecha_nacimiento_str):
"""
Constructor de la clase Persona.
Se llama automáticamente al crear una nueva instancia de Persona.
Args:
nombre (str): El nombre de la persona.
fecha_nacimiento_str (str): La fecha de nacimiento de la persona en formato de cadena.
Será parseada internamente.
Raises:
ValueError: Si el nombre está vacío o el formato de la fecha de nacimiento es inválido.
"""
if not nombre:
raise ValueError("El nombre de la persona no puede estar vacío.")
self.nombre = nombre
self.fecha_nacimiento_str = fecha_nacimiento_str
# Usamos la función parse_date de parsers_date.py para convertir la cadena a datetime
self.fecha_nacimiento_dt = parse_date(fecha_nacimiento_str)
if self.fecha_nacimiento_dt is None:
raise ValueError(f"Formato de fecha de nacimiento inválido: '{fecha_nacimiento_str}'. "
"Asegúrate de usar YYYY-MM-DD, DD-MM-YYYY, MM/DD/YYYY o YYYY/MM/DD.")
# Usamos la función Calcular_Edad de registro.py para obtener la edad
self.edad = Calcular_Edad(self.fecha_nacimiento_dt)
def __str__(self):
"""
Método especial que define la representación en cadena de un objeto Persona.
Se invoca cuando usas print() o str() con un objeto Persona.
"""
return (f"Nombre: {self.nombre}\n"
f"Fecha de Nacimiento: {self.fecha_nacimiento_dt.strftime('%Y-%m-%d')}\n"
f"Edad: {self.edad} años")
def obtener_info_completa(self):
"""
Método para obtener toda la información de la persona en un diccionario.
Returns:
dict: Un diccionario con el nombre, fecha de nacimiento (como objeto datetime) y edad.
"""
return {
"nombre": self.nombre,
"fecha_nacimiento": self.fecha_nacimiento_dt,
"edad": self.edad
}
# Puedes añadir más métodos aquí, por ejemplo:
# def es_mayor_de_edad(self, edad_legal=18):
# return self.edad >= edad_legal

Explicación de models/persona.py:

  • __init__(self, nombre, fecha_nacimiento_str): Este es el constructor de la clase. 🏗️
    • Se ejecuta automáticamente cuando creamos un objeto Persona.
    • self es una referencia a la instancia actual del objeto que se está creando.
    • Toma el nombre y la fecha_nacimiento_str como argumentos.
    • Valida que el nombre no esté vacío. ✅
    • Utiliza parse_date (importado de modules.parsers_date) para convertir la cadena de fecha a un objeto datetime. Si el formato es inválido, lanza un ValueError. ❌
    • Utiliza Calcular_Edad (importado de modules.registro) para calcular la edad de la persona. 🎂
    • Almacena nombre, fecha_nacimiento_dt y edad como atributos de instancia (ej. self.nombre). 💾
  • __str__(self): Este es un método especial que define cómo se representa un objeto Persona cuando se convierte a una cadena (por ejemplo, cuando lo imprimes con print()). Esto reemplaza la necesidad de la función Imprimir_Data de registro.py para la clase Persona. 🖨️
  • obtener_info_completa(self): Un método adicional para obtener los datos de la persona en un formato de diccionario, útil si necesitas manipular la información de forma estructurada. 📊

Refactorizando modules/registro.py a una Clase Registro 🔄

Section titled “Refactorizando modules/registro.py a una Clase Registro 🔄”

Ahora, transformaremos el módulo registro.py en una clase Registro. Las funciones Pedir_Nombre, Pedir_Fecha_Nacimiento y Calcular_Edad se convertirán en métodos estáticos de esta clase. Los métodos estáticos no necesitan una instancia de la clase para ser llamados y no acceden a atributos de instancia (self). Son como funciones normales, pero agrupadas lógicamente dentro de una clase. 🧩

modules/registro.py
from datetime import datetime
class Registro:
"""
Clase que encapsula las funciones relacionadas con la entrada de datos de registro
y cálculos generales como la edad.
"""
@staticmethod
def Pedir_Nombre():
"""
Solicita al usuario que ingrese su nombre.
Es un método estático porque no necesita acceder a datos de una instancia de Registro.
"""
name = input("Cuál es tu nombre: ")
return name
@staticmethod
def Pedir_Fecha_Nacimiento():
"""
Solicita al usuario que ingrese su fecha de nacimiento.
Es un método estático por la misma razón que Pedir_Nombre.
"""
birth = input("Cuál es tu fecha de nacimiento (YYYY-MM-DD, DD-MM-YYYY, MM/DD/YYYY, YYYY/MM/DD): ")
return birth
@staticmethod
def Calcular_Edad(birthday):
"""
Calcula la edad de una persona basándose en su fecha de nacimiento.
Es un método estático porque solo opera con los argumentos que recibe,
sin depender del estado de una instancia de Registro.
Args:
birthday (datetime): Objeto datetime con la fecha de nacimiento.
Returns:
int: La edad calculada en años.
"""
fecha_actual = datetime.now()
edad = fecha_actual.year - birthday.year - ((fecha_actual.month, fecha_actual.day) < (birthday.month, birthday.day))
return edad
@staticmethod
def Imprimir_Data(name, age):
"""
Imprime un mensaje de bienvenida con el nombre y la edad.
(Esta función ahora puede ser reemplazada por el método __str__ de la clase Persona
cuando se trabaja con objetos Persona).
"""
print(f"Bienvenido {name}, Tu edad es: {age} años")

Explicación de modules/registro.py:

  • class Registro:: Definimos la nueva clase. 🆕
  • @staticmethod: Este decorador convierte los métodos en estáticos. Significa que puedes llamarlos directamente desde la clase (Registro.Pedir_Nombre()) sin necesidad de crear una instancia de Registro. 💡
  • Las funciones originales (Pedir_Nombre, Pedir_Fecha_Nacimiento, Calcular_Edad, Imprimir_Data) se han movido como métodos estáticos dentro de esta clase. 📦

modules/parsers_date.py (Sin Cambios) unchanged

Section titled “modules/parsers_date.py (Sin Cambios) unchanged”

Este módulo sigue siendo una utilidad independiente y no necesita cambios. 🧘‍♀️

# modules/parsers_date.py (Sin cambios)
from datetime import datetime
def parse_date(input_date):
"""
Intenta parsear una cadena de fecha en varios formatos.
Args:
input_date (str): La cadena de fecha a parsear.
Returns:
datetime or None: Un objeto datetime si el parseo es exitoso, None en caso contrario.
"""
formats_availaible = [
"%Y-%m-%d", #YYYY-MM-DD
"%d-%m-%Y", #DD-MM-YYYY
"%m/%d/%Y", #MM/DD/YYYY
"%Y/%m/%d" #YYYY/MM/DD
]
for formato in formats_availaible:
try:
date_parse = datetime.strptime(input_date, formato)
return date_parse
except ValueError:
pass # Ignorar el error y probar el siguiente formato
return None # Si ningún formato coincide

Refactorizando app.py para Usar las Nuevas Clases 🏗️

Section titled “Refactorizando app.py para Usar las Nuevas Clases 🏗️”

Finalmente, modificaremos app.py para que utilice la clase Registro para obtener los datos de entrada y la clase Persona para encapsular la información de la persona.

# app.py (Versión con las clases Registro y Persona)
# Importamos la clase Registro desde modules.registro
from modules.registro import Registro
# Importamos la clase Persona desde models.persona
from models.persona import Persona
def main():
"""
Función principal de la aplicación para registrar y mostrar información de una persona.
Ahora utiliza las clases Registro y Persona para encapsular la lógica.
"""
print("Bienvenido a nuestra app de registro de personas. 👋")
# 1. Pedir el nombre usando el método estático de la clase Registro
nombre_persona = Registro.Pedir_Nombre()
while not nombre_persona:
print("No has ingresado un nombre válido. Por favor, inténtalo de nuevo. 🚫")
nombre_persona = Registro.Pedir_Nombre()
# 2. Pedir la fecha de nacimiento usando el método estático de la clase Registro
fecha_nacimiento_str = Registro.Pedir_Fecha_Nacimiento()
# 3. Validar y crear una instancia de la clase Persona
try:
# La clase Persona se encarga de parsear y calcular la edad internamente
persona_registrada = Persona(nombre_persona, fecha_nacimiento_str)
print("\n--- Información de la Persona Registrada --- 📋")
# Al imprimir el objeto persona_registrada, se invoca automáticamente
# el método __str__ de la clase Persona.
print(persona_registrada)
print("------------------------------------------")
# Ejemplo de cómo acceder a los atributos de la instancia directamente
print(f"Accediendo directamente al nombre: {persona_registrada.nombre} ✨")
print(f"Accediendo directamente a la edad: {persona_registrada.edad} años 🎂")
# Ejemplo de uso de otro método de la clase
# info_dict = persona_registrada.obtener_info_completa()
# print(f"Información en diccionario: {info_dict}")
except ValueError as e:
# Captura errores si la fecha de nacimiento es inválida al intentar crear Persona
print(f"Error al registrar la persona: {e} 🚨")
print("Por favor, reinicia la aplicación e intenta con un formato de fecha válido. 🔄")
# Este bloque asegura que la función main() se ejecute solo cuando el script app.py
# se está ejecutando directamente (no cuando es importado como un módulo).
if __name__ == "__main__":
main()

Cambios en app.py:

  • from modules.registro import Registro: Importamos la nueva clase Registro. 📥
  • from models.persona import Persona: Importamos la clase Persona. 📥
  • Las llamadas a Pedir_Nombre() y Pedir_Fecha_Nacimiento() ahora se hacen a través de la clase Registro (ej. Registro.Pedir_Nombre()). 📞
  • La creación del objeto Persona se mantiene, ya que la clase Persona ahora es responsable de validar la fecha y calcular la edad internamente. 🧠
  • El manejo de errores para la creación de Persona se ha simplificado, ya que la clase Persona se encarga de las validaciones de datos de entrada. ✅

Cómo Ejecutar la Aplicación Refactorizada ▶️

Section titled “Cómo Ejecutar la Aplicación Refactorizada ▶️”
  1. Asegúrate de que tu estructura de carpetas sea la siguiente:

    tu_proyecto/
    ├── app.py
    ├── models/
    │ ├── __init__.py (Archivo vacío)
    │ └── persona.py
    └── modules/
    ├── __init__.py (Archivo vacío)
    ├── registro.py
    └── parsers_date.py

    (Recuerda crear los archivos __init__.py vacíos dentro de las carpetas models/ y modules/ para que Python las reconozca como paquetes. 📦)

  2. Guarda el código de cada archivo en su ubicación correspondiente. 💾

  3. Abre tu terminal y navega hasta la carpeta tu_proyecto/. 💻

  4. Ejecuta el archivo app.py:

    Terminal window
    python app.py

Ahora, cuando ejecutes la aplicación, verás que la interacción es similar, pero internamente, la información de la persona se está manejando de una manera mucho más organizada y orientada a objetos gracias a nuestras nuevas clases Registro y Persona. 🥳


En este capítulo, hemos dado un paso crucial hacia la programación orientada a objetos en Python. Aprendimos a definir clases, a usar el constructor __init__, a manejar atributos de instancia y a crear métodos. Al encapsular la información de una persona en la clase Persona y las funciones de entrada en la clase Registro, hemos hecho nuestro código más legible, modular y preparado para futuras expansiones. 📈

En los próximos capítulos, exploraremos más a fondo los conceptos de POO, como la herencia, el polimorfismo y la composición, que te permitirán construir aplicaciones aún más sofisticadas. ¡Sigue practicando y experimentando con las clases! 💪📚