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.
Síguenos con el tutorial
Section titled “Síguenos con el tutorial”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) ⏪
Section titled “app.py (Versión anterior) ⏪”# app.py (Versión anterior)
#from modules.registro import Pedir_Nombre # Comentado, se usa import asimport modules.registro as Registrofrom 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 clasePersona
, 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 claseRegistro
, 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 claseRegistro
para obtener los datos y la clasePersona
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.
from datetime import datetime# Importamos la función parse_date desde modules.parsers_datefrom 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 lafecha_nacimiento_str
como argumentos. - Valida que el
nombre
no esté vacío. ✅ - Utiliza
parse_date
(importado demodules.parsers_date
) para convertir la cadena de fecha a un objetodatetime
. Si el formato es inválido, lanza unValueError
. ❌ - Utiliza
Calcular_Edad
(importado demodules.registro
) para calcular la edad de la persona. 🎂 - Almacena
nombre
,fecha_nacimiento_dt
yedad
como atributos de instancia (ej.self.nombre
). 💾
- Se ejecuta automáticamente cuando creamos un objeto
__str__(self)
: Este es un método especial que define cómo se representa un objetoPersona
cuando se convierte a una cadena (por ejemplo, cuando lo imprimes conprint()
). Esto reemplaza la necesidad de la funciónImprimir_Data
deregistro.py
para la clasePersona
. 🖨️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. 🧩
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 deRegistro
. 💡- 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.registrofrom modules.registro import Registro# Importamos la clase Persona desde models.personafrom 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 claseRegistro
. 📥from models.persona import Persona
: Importamos la clasePersona
. 📥- Las llamadas a
Pedir_Nombre()
yPedir_Fecha_Nacimiento()
ahora se hacen a través de la claseRegistro
(ej.Registro.Pedir_Nombre()
). 📞 - La creación del objeto
Persona
se mantiene, ya que la clasePersona
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 clasePersona
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 ▶️”-
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 carpetasmodels/
ymodules/
para que Python las reconozca como paquetes. 📦) -
Guarda el código de cada archivo en su ubicación correspondiente. 💾
-
Abre tu terminal y navega hasta la carpeta
tu_proyecto/
. 💻 -
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
. 🥳
Conclusión 🎉
Section titled “Conclusión 🎉”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! 💪📚
Referencias Complementarias
Section titled “Referencias Complementarias”- Documentación Oficial de Python sobre Clases: La referencia definitiva para profundizar en todos los aspectos de las clases en Python.
- Real Python - Clases y POO: Un tutorial muy completo y fácil de seguir con ejemplos adicionales.
- PEP 8 — Guía de Estilo para Código Python: Este documento es la guía de estilo de facto para el código de Python. Es esencial para escribir código legible y profesional.
- QuePasaLinux: No olvides revisar mi canal para más contenido y ejemplos prácticos sobre estos temas.