¡Este es un texto traducido automáticamente que puede contener errores!
En Python (esto también se aplica a la mayoría de los otros lenguajes) es posible crear objetos propios, con valores, reglas y funciones propias. Esto se llama clases (classes en inglés). Usamos clases para recopilar datos y funciones relacionados 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.
Dictionary (JSON) vs Klasser (Objekter)
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 necesitamos enviar datos a través de la red, o almacenarlos en un archivo, a menudo usamos JSON (o tablas en bases de datos).
Cuando necesitamos trabajar con datos estructurados en el código, usamos clases.
En este módulo, veremos cómo podemos usar clases para validar datos.
Lo más fácil es usar @dataclass de la biblioteca dataclasses. Esto nos permite evitar escribir mucho código repetitivo para crear una clase. (Como, 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 decorador de dataclass, que logra lo mismo que lo anterior (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 en el ejemplo a continuación.
@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 Persona:
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)
# Output: Person(name='', eye_color='yes', phone_number='12345', email='not-an-email')
Esto es (potencialmente) problemático, y puede fácilmente generar deuda técnica en el futuro. Afortunadamente, existen formas sencillas de añadir validación a las clases.
Vamos a empezar viendo la validación de correo electrónico. Existen bibliotecas integradas en Python que pueden ayudarnos con esto, pero como lo hacemos para aprender, vamos a crear nuestra propia validación sencilla, creando una nueva clase para “Email” y examinando una función __post_init__ (solo para dataclass).
Merk
Nosotros podemos también hacer esto en la propia clase Persona, pero a menudo es mejor crear clases separadas para cosas que puedan 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 “fallar” si el correo electrónico no es válido, esto detendrá el programa inmediatamente 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 @ en la dirección de correo electrónico: {self.address}")
if "." not in self.address.split("@")[1]:
raise ValueError(f"Falta . en el dominio de la dirección de correo electrónico: {self.address}")
if " " in self.address:
raise ValueError(f"La dirección de correo electrónico 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 @
Tarea 3 - Validación de números 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éfonos!
¿Puedes solucionar 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 (subvalor 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
¡Nuevo desafío surge!
Ahora que hemos cambiado la clase Person para usar las clases PhoneNumber y Email, también debemos cambiar la forma en que instanciamos (creamos) una Person. Ahora debemos primero crear un objeto PhoneNumber y un objeto 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)
# Obs: un cambio debe hacerse en la forma en que extraemos los valores también
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 subvalor directamente
# Prueba el código
person = Person(name="Alice", verdi=EksempelVerdi("Noe tekst"))
print(person.verdi) # Salida: Noe tekst
Merk
Las propiedades son únicas en el sentido de que no necesitan parámetros y no necesitan paréntesis para ejecutarse. En el ejemplo, extraemos person.verdi sin paréntesis (no person.verdi()), aunque técnicamente sea una función.
Ejemplo alternativo que acepta tanto str como EksempelVerdi
@dataclass
class Person:
name: str
_verdi: str
def __init__(self, name: str, verdi: str | EksempelVerdi):
self.name = name
if isinstance(verdi, EksempelVerdi):
self._verdi = verdi
elif isinstance(verdi, str):
self._verdi = EksempelVerdi(verdi)
else:
raise TypeError("verdi må være av type str eller EksempelVerdi")
@property
def verdi(self) -> str:
return self._verdi.attributt # Henter ut underverdien direkte
# Test koden
person = Person(name="Alice", verdi="Noe tekst")
print(person.verdi) # Output: Noe tekst
Tarea 6 - Propiedades con lógica (Opcional)
Actualiza Person añadiendo un nuevo atributo llamado birthday (cumpleaños). Este debe ser de tipo datetime.date (del la biblioteca datetime).
Luego, crea las siguientes propiedades:
- Crea una propiedad
ageque calcule la edad de la persona basándose enbirthdayy la fecha actual. - Crea una propiedad
is_adultque retorneTruesi la persona tiene 18 años o más, de lo contrarioFalse.
Solución: Edad y adulto como propiedades
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 basándose en la fecha de nacimiento y la fecha actual"""
today = date.today()
age = today.year - self.birthday.year
# Ajusta hacia abajo en 1 si la persona aún no ha tenido su cumpleaños este año
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 posterior al 15 de mayo de 2023
print(person.is_adult) # True
¡Otro desafío!
¿Puedes hacer que la instanciación de Person acepte tanto datetime.date como un texto en el formato "DD-MM-AAAA" para birthday? (Pista: usa datetime.strptime para convertir la cadena a un datetime.date)
