¡Este es un texto traducido automáticamente que puede contener errores!
En Python (también aplica a la mayoría de los otros lenguajes) es posible crear objetos propios, con valores, reglas y funciones propias. Esto se denomina clases (classes en inglés). Utilizamos las clases para agrupar datos relacionados y funcionalidades en una unidad, por ejemplo, una clase Orden que tiene datos como orden_id, nombre_cliente, productos y funciones como agregar_producto(), calcular_total(), etc.
Diccionario (JSON) vs Clases (Objetos)
JSON (JavaScript Object Notation) es un formato para almacenar y transferir datos independientemente del lenguaje de programación, mientras que una clase es una estructura en un lenguaje de programación específico.
Cuando vamos a enviar datos a través de la red, o almacenarlos en un archivo, solemos utilizar JSON (o tablas en bases de datos).
Cuando vamos a trabajar con datos estructurados en el código, utilizamos clases.
En este módulo veremos cómo podemos usar clases para validar datos.
Vídeo: Cómo las clases recopilan datos y reglas
El vídeo explica qué es una clase y cómo se pueden usar las clases para estructurar y validar datos.
Lo más fácil es utilizar @dataclass del módulo dataclasses. Esto nos permite evitar escribir mucho código repetitivo para crear una clase. (Por ejemplo, las funciones integradas __init__ y __repr__ (representación)).
Ejemplo de una clase sin usar el decorador dataclass:
class Car:
def __init__(self, make: str, model: str, year: int):
self.make = make
self.model = model
self.year = year
def __repr__(self):
return f"{self.year} {self.make} {self.model}"
my_car = Car("Toyota", "Corolla", 2020)
print(my_car) # Salida: 2020 Toyota Corolla
Ejemplo con el decorador dataclass, que logra lo mismo que arriba (pero con menos código):
from dataclasses import dataclass
@dataclass
class Car:
make: str
model: str
year: int
my_car = Car("Toyota", "Corolla", 2020)
print(my_car) # Salida: Car(make='Toyota', model='Corolla', year=2020)
Tarea 1 - Crea una clase
Crea una clase llamada Person. Esta clase debe tener las siguientes propiedades (atributos):
name: El nombre de la personaeye_color: El color de ojos de la personaphone_number: El número de teléfono de la personaemail: La dirección de correo electrónico de la persona
Instancia (utiliza) un objeto de la clase Person con valores válidos para todas las propiedades, como se muestra en el ejemplo siguiente.
@dataclass
class Person:
... # Tu código aquí
bob_kaare = Person(name="Bob Kåre",
eye_color="blue",
phone_number="12345678",
email="bob_kaare@example.com")
print(bob_kaare)
Solución: Una dataclass para Persona
Aquí hay una posible solución:
from dataclasses import dataclass
@dataclass
class Person:
name: str
eye_color: str
phone_number: str
email: str
Tarea 2 - Validación en la clase
En el ejemplo anterior, no hemos añadido ninguna validación. Esto significa que podemos crear una Persona con valores inválidos, como:
@dataclass
class Person:
... # Tu código aquí
invalid_person = Person(name="",
eye_color="yes",
phone_number="12345",
email="not-an-email")
print(invalid_person)
# Salida: Person(name='', eye_color='yes', phone_number='12345', email='not-an-email')
Esto es (potencialmente) problemático y puede generar rápidamente deuda técnica a futuro. Afortunadamente existen formas simples de agregar validación en clases.
Comenzaremos viendo la validación de correos electrónicos. Existen bibliotecas integradas en Python que pueden ayudarnos con esto, pero como lo estamos haciendo para aprender, crearemos nuestra propia validación simple mediante una nueva clase para “Email” y explorando una función __post_init__ (solo para dataclasses).
Nota
También podemos hacer esto en la propia clase Person, pero suele ser mejor crear clases propias para cosas que pueden reutilizarse.
Ejemplo de una función __post_init__
from dataclasses import dataclass
@dataclass
class Email:
address: str
def __post_init__(self):
print(f"Validando correo electrónico: {self.address}")
# Tu código aquí
Para una validación de correo electrónico sencilla, por ejemplo, podemos comprobar que el correo electrónico contiene tanto el carácter @ como el carácter .. Eventualmente, puedes comprobar que el correo electrónico coincida con un patrón regex. (Más avanzado, ¡pero busca en internet!)
Solución: Código para validación simple de correo electrónico
Aquí hay una posible solución; usamos excepciones para que el programa falle si el correo es inválido. Esto detendrá el programa de inmediato y mostrará un mensaje de error.
from dataclasses import dataclass
@dataclass
class Email:
address: str
def __post_init__(self):
if "@" not in self.address:
raise ValueError(f"Falta la @ en la dirección de correo: {self.address}")
if "." not in self.address.split("@")[1]:
raise ValueError(f"Falta el . en el dominio de la dirección de correo: {self.address}")
if " " in self.address:
raise ValueError(f"La dirección de correo no puede contener espacios: {self.address}")
# Prueba el código
test = Email("hola@example.com") # Válido
try:
test = Email("holaexample.com")
except ValueError as e:
print(e) # Inválido, falta la @
Tarea 3 - Validación de número de teléfono
Crea una clase similar a la que hiciste para los correos electrónicos, pero ahora para números de teléfono
+ ¡Desafío con la validación de teléfono!
¿Puedes arreglar la validación para números de teléfono para que acepte tanto letras (str) como números (int)? Por ejemplo, 12345678 y "12345678" deben ser ambos válidos.
Intenta también agregar códigos de país como un atributo (valor dentro de la clase). Por ejemplo, 47 para Noruega, 46 para Suecia
Tarea 4 - Utilizar la validación en la clase Persona
Ahora que hemos creado la validación para el correo electrónico y el número de teléfono, podemos utilizar estas en la clase Persona.
@dataclass
class Person:
name: str
eye_color: str
phone_number: PhoneNumber # Usa la clase PhoneNumber
email: Email # Usa la clase Email
¡Surge un nuevo desafío!
Ahora que hemos modificado la clase Person para usar las clases PhoneNumber y Email, también debemos cambiar cómo instanciamos (creamos) una Person. Ahora primero debemos crear un objeto PhoneNumber y uno de Email, antes de poder crear una Person.
bob_kaare = Person(name="Bob Kåre",
eye_color="blue",
phone_number=PhoneNumber("12345678"), # Nota el cambio aquí
email=Email("bob_kaare@example.com")) # Nota el cambio aquí
print(bob_kaare)
# Observación: también se necesita un cambio en la forma en que extraemos los valores
print(bob_kaare.email.address)
print(bob_kaare.phone_number.number) # .country_code(?)
Tarea 5 - Propiedades en clases (Opcional)
Cuando usamos objetos para representar valores como correo electrónico y número de teléfono, necesitamos especificar el subvalor (por ejemplo, address para correo electrónico y number para número de teléfono) cada vez que queremos obtener el valor. Esto puede volverse un poco engorroso a la larga. Afortunadamente, existe una solución a esto, utilizando el decorador @property en una clase, lo que nos permite obtener el valor directamente del objeto, sin tener que especificar el subvalor.
Esto plantea, sin embargo, otro desafío, y es que necesitamos la función __init__ en la clase Person. Esto se debe a que no podemos usar el mismo nombre para una propiedad y un atributo en una dataclass.
from dataclasses import dataclass
@dataclass
class EksempelVerdi:
attributt: str
@dataclass
class Person:
name: str
_verdi: EksempelVerdi # Variable interna (comienza con _ para indicar que es "privada")
def __init__(self, name: str, verdi: EksempelVerdi):
self.name = name
self._verdi = verdi
@property
def verdi(self):
return self._verdi.attributt # Obtiene el valor subyacente directamente
# Prueba el código
person = Person(name="Alice", verdi=EksempelVerdi("Noe tekst"))
print(person.verdi) # Salida: Noe tekst
Nota
Las propiedades son únicas ya que no necesitan parámetros y no requieren paréntesis para ejecutarse. En el ejemplo, extraemos person.valor sin paréntesis (no person.valor()) , aunque técnicamente es una función.
Ejemplo alternativo que acepta tanto str como ValorEjemplo
@dataclass
class Persona:
nombre: str
_valor: str
def __init__(self, nombre: str, valor: str | ValorEjemplo):
self.nombre = nombre
if isinstance(valor, ValorEjemplo):
self._valor = valor
elif isinstance(valor, str):
self._valor = ValorEjemplo(valor)
else:
raise TypeError("valor debe ser de tipo str o ValorEjemplo")
@property
def valor(self) -> str:
return self._valor.atributo # Obtiene el subvalor directamente
# Prueba del código
persona = Persona(nombre="Alice", valor="Algun texto")
print(persona.valor) # Salida: Algunos texto
Tarea 6 - Propiedades con lógica (Opcional)
Actualiza Person agregando un nuevo atributo llamado birthday (fecha de nacimiento). Este debe ser de tipo datetime.date (de la biblioteca datetime).
A continuación, debes crear las siguientes propiedades:
- Crea una propiedad
ageque calcule la edad de la persona basada enbirthdayy la fecha actual. - Crea una propiedad
is_adultque devuelveTruesi la persona tiene 18 años o más, de lo contrarioFalse.
Solución: Edad y adulto como properties
Aquí hay una posible solución:
from dataclasses import dataclass
from datetime import date
@dataclass
class Person:
name: str
birthday: date
@property
def age(self) -> int:
"""Calcula la edad basada en la fecha de nacimiento y la fecha actual"""
today = date.today()
age = today.year - self.birthday.year
# Ajustar hacia abajo con 1 si la persona no ha cumplido años este año todavía
if (today.month, today.day) < (self.birthday.month, self.birthday.day):
age -= 1
return age
@property
def is_adult(self) -> bool:
"""Devuelve True si la persona tiene 18 años o más"""
return self.age >= 18
# Prueba el código
person = Person(name="Alice", birthday=date(2005, 5, 15))
print(person.age) # Por ejemplo. 18 si la fecha actual es después del 15 de mayo de 2023
print(person.is_adult) # True
¡Otro desafío más!
¿Puedes hacer que la instancia de Person acepte tanto un objeto datetime.date como una cadena con el formato "DD-MM-AAAA" para birthday? (Pista: usa datetime.strptime para convertir la cadena en un objeto datetime.date).
