Los decoradores @property en Python convierten métodos en atributos accesibles con la anotación de punto, logrando interfaces limpias, validación y cálculo automático detrás de escena. Con un getter convertido en propiedad y un setter controlado, tu clase gana encapsulación efectiva sin cambiar cómo lees o escribes valores.
¿Qué es property y por qué hace tu código más pythonico?
Usar property permite exponer datos como atributos, pero con la lógica de métodos. Así, el acceso es natural y no requiere paréntesis, mientras mantienes el control interno.
Convierte un método en atributo de solo lectura con @property.
Controla la escritura con @nombre.setter y valida entradas.
Accede con la anotación de punto, sin paréntesis.
¿Cómo convertir un getter en propiedad con @property?
Primero, elimina el método get_... y declara la propiedad. Luego úsala como un atributo.
classLibro:def__init__(self, titulo, autor, ISBN): self.titulo = titulo
self.autor = autor
self.ISBN = ISBN
self._veces_prestado =0# parámetro interno@propertydefveces_prestado(self):return self._veces_prestado
En el flujo de impresión, en lugar de retornar solo el título desde libros_disponibles, retorna el objeto libro y accede así:
# en mainfor libro in libros_disponibles():print(libro.titulo, libro.veces_prestado)# sin paréntesis
¿Cómo controlar la escritura con setter y validación?
Con el setter defines cómo se asigna y validas valores. Si no cumple, genera un error claro.
classLibro:# ...@veces_prestado.setterdefveces_prestado(self, valor):if valor >0: self._veces_prestado = valor
else:raise ValueError("El valor de veces prestado debe ser mayor a cero")
Prueba en main asignando un valor negativo y ejecuta:
python main.py
Verás un ValueError con el mensaje de validación. Esto ayuda a detectar un bug antes de que llegue a usuarios reales.
¿Cómo usar properties para computar y mostrar datos?
Además de exponer valores, puedes crear propiedades que computan resultados a partir del estado interno. Por ejemplo, una propiedad es_popular basada en datos internos se accede como atributo y no necesita setter.
Propiedades computadas como es_popular evitan estados inconsistentes.
No declaras setter si no quieres permitir escritura.
Para mostrar información compuesta, crea una propiedad que devuelva una cadena con título, autor e ISBN.
classLibro:# ...@propertydefdescripcion_completa(self):returnf"{self.titulo} por {self.autor}{self.ISBN}"
Úsala en la salida en main con la anotación de punto:
print(libro.descripcion_completa)
¿Cómo se integra en main y biblioteca?
La integración es directa si retornas objetos libro en lugar de cadenas.
Modifica libros_disponibles para retornar la lista de objetos Libro.
Itera y accede a libro.titulo, libro.veces_prestado y libro.descripcion_completa.
Evita paréntesis: las propiedades se leen como atributos.
Ejecuta con python main.py para validar resultados.
¿Qué reto práctico puedes implementar ahora?
Pon en práctica las propiedades para reforzar encapsulación y claridad.
Agrega una propiedad nombre completo a la clase usuario que combine nombre y cédula.
Crea una propiedad en biblioteca para libros disponibles que retorne la lista de todos los libros disponibles.
Comparte tu implementación y comenta cómo validaste las asignaciones.
¿Tienes otra idea de propiedad útil en tu modelo de dominio? Cuéntala en los comentarios y qué validaciones agregarías.
multa_acumulada (decimal) — calculada por dias_atraso * tarifa.
Biblioteca
libros (list[Libro]) — setter valida lista e instancias.
libros_disponibles (list[Libro], calculada) — filter por disponible == True.
usuarios (list[Usuario]) — validar instancias.
ocupacion (float, calculada) — porcentaje de libros prestados.
top_prestados (list[Libro], calculada) — N libros más prestados.
reservas_pendientes_count (int, calculada).
Autor / Editorial
nombre (str)
obras_count (int, calculada)
slug (str) — para URLs, validación de caracteres.
Notas de validación generales
Usar property + setter con TypeError/ValueError.
Normalizar strings (.strip(), minúsculas/uppercase según caso).
Validar tipos en listas (iterar y isinstance).
Fechas: no permitir fechas futuras donde no correspondan.
Valores numéricos: >= 0.
RESPUESTA DEL RETO
usuarios.py
classUsuario:def__init__(self, nombre, cedula): self.nombre = nombre
self.cedula = cedula
@propertydefnombre(self):return self._nombre
@nombre.setterdefnombre(self, value):ifnotisinstance(value,str)ornot value.strip():raise ValueError("El nombre debe ser una cadena no vacía.") self._nombre = value.strip()@propertydefcedula(self):return self._cedula
@cedula.setterdefcedula(self, value):ifnotisinstance(value,str)ornot value.isdigit():raise ValueError("La cédula debe ser una cadena de dígitos.") self._cedula = value
@propertydefnombre_completo(self):returnf"{self.nombre} ({self.cedula})"
biblioteca.py
classBiblioteca:def__init__(self, nombre): self.nombre = nombre
self._libros =[] self.usuarios =[]@propertydeflibros(self):return self._libros
@libros.setterdeflibros(self, value):ifnotisinstance(value,list):raise TypeError("libros debe ser una lista de instancias de Libro.")# validación opcional por elemento (import dentro para evitar ciclos)try:from libros import Libro
for item in value:ifnotisinstance(item, Libro):raise TypeError("Todos los elementos de libros deben ser instancias de Libro.")except ImportError:# si no existe el módulo libros, solo validar tipo de listapass self._libros = value
@propertydeflibros_disponibles(self):# asume que cada libro tiene un atributo 'disponible' (bool)return[lib for lib in self._libros ifgetattr(lib,"disponible",True)]
Dejo aca el reto
el print para probarlo
Y la modificación a biblioteca
Que solo fue quitar los parentesis tanbueb de libros_disponibles
Muy buen trabajo, el nombre de Users deberia ser en minuscula, puesto que usamos la primera mayuscula para referirnos a las class.
Para el reto
usuarios.py
@propertydefnombre_completo(self):returnf"{self.nombre}, con cedula {self.cedula}"
biblioteca.py
@propertydeflibros_disponibles_property(self): resultado=""for libro in self.libros:if libro.disponible:#resultado+=str(libro)+"\n" resultado+=libro.descripcion_completa+"\n"return resultado
Una recomendación:
Entiendo que puede ser por el sistema de comentarios pero hay que tener cuidado con la identación, el @property debe quedar al mismo nivel que el def.
si quieres ajusta el código y vuelvo a revisarlo.
Ya corregi la indentacion del @property, en mi codigo si esta indentado correctamente, nada mas que al copiar y pegar no copie los tabuladores correspondientes
Falta un else en la parte del setter del propery de veces_prestado, si se queda como se muestra en el video al momento de colocar un valor positivo a veces_prestado aun asi manda el error. Debe quedar asi
@veces_prestado.setter#para indicar que es un atributo que puede ser llenadodefveces_prestado(self,veces_prestado):#print(veces,veces>0)if veces_prestado>0: self.__vecesprestado=veces_prestado
else:#este es el que faltaraise ValueError("El valor de veces prestado debe ser mayor a 0")
Buen catch! que bien que lo hayas corregido, gracias por el aporte.