Nos contratan para diseñar una clase para evaluar la relación
calidad-precio de diversos hoteles. Nos dicen que los atributos que se
cargarán de los hoteles son: nombre, ubicación, puntaje obtenido por
votación, y precio, y que además de guardar hoteles y mostrarlos,
debemos poder compararlos en términos de sus valores de relación
calidad-precio, de modo tal que x < y
signifique que el hotel x
es
peor en cuanto a la relación calidad-precio que el hotel y
, y que
dos hoteles son iguales si tienen la misma relación calidad-precio. La
relación calidad-precio de un hotel la definen nuestros clientes como
(puntaje^2) ∗ 10 / precio
.
Además, y como resultado de todo esto, tendremos que ser capaces de ordenar de menor a mayor una lista de hoteles, usando el orden que nos acaban de definir.
Averiguamos un poco más respecto de los atributos de los hoteles:
- El nombre y la ubicación deben ser cadenas no vacías.
- El puntaje debe ser un número (sin restricciones sobre su valor)
- El precio debe ser un número distinto de cero.
Empezamos diseñar a la clase:
El método __init__
:
- Creará objetos de la clase Hotel con los atributos que se indicaron (nombre, ubicación, puntaje, precio).
- Los valores por omisión para la construcción son: puntaje en
0
, precio enfloat("inf")
(infinito), nombre y ubicación en*
(el precio muy alto sirve para que si no se informa el precio de un hotel, se asuma el mayor valor posible. - Necesitamos validar que puntaje y precio sean números (utilizaremos
la función
es_numero
que ya se usó en el caso de los puntos). Cuando un precio viene en cero se reemplaza su valor porfloat("inf")
(de modo de asegurar que el precio nunca quede en cero). - Necesitamos validar que nombre y ubicación sean cadenas no vacías
(para lo cual tenemos que construir una función
es_cadena_no_vacia
). - Cuando los datos no satisfagan los requisitos se levantará una
excepción
TypeError
.
Contará con un método __str__
para mostrar a los hoteles mediante
una cadena del estilo:
"Hotel City de Mercedes - Puntaje: 3.25 - Precio: 78 pesos".
Respecto a la relación de orden entre hoteles, la clase deberá poder contar con los métodos necesarios para realizar esas comparaciones y para ordenar una lista de hoteles.
Casi todas las tareas, podemos realizarlas con los temas vistos para la
creación de la clase Punto
. Para el último ítem deberemos introducir
nuevos métodos especiales.
Ejercicio 14.1. Escribir la función es_cadena_no_vacia(valor)
que
decide si un valor cualquiera es una cadena no vacía o no, e incluirla
en el módulo validaciones.
El fragmento inicial de la clase programada en Python queda así:
class Hotel(object):
""" Hotel: sus atributos son: nombre, ubicacion, puntaje y precio."""
def __init__(self, nombre = '*', ubicacion = '*',
puntaje = 0, precio = float("inf")):
""" nombre y ubicacion deben ser cadenas no vacías,
puntaje y precio son números.
Si el precio es 0 se reemplaza por infinito. """
if es_cadena_no_vacia (nombre):
self.nombre = nombre
else:
raise TypeError ("El nombre debe ser una cadena no vacía")
if es_cadena_no_vacia (ubicacion):
self.ubicacion = ubicacion
else:
raise TypeError ("La ubicación debe ser una cadena no vacía")
if es_numero(puntaje):
self.puntaje = puntaje
else:
raise TypeError ("El puntaje debe ser un número")
if es_numero(precio):
if precio != 0:
self.precio = precio
else:
self.precio = float("inf")
else:
raise TypeError("El precio debe ser un número")
def __str__(self):
""" Muestra el hotel según lo requerido. """
return self.nombre + " de "+ self.ubicacion+ \
" - Puntaje: "+ str(self.puntaje) + " - Precio: "+ \
str(self.precio)+ " pesos."
Con este código tenemos ya la posibilidad de construir hoteles, con los atributos de los tipos correspondientes, y de mostrar los hoteles según nos lo han solicitado.
>>> h = Hotel("Hotel City", "Mercedes", 3.25, 78)
>>> print h
Hotel City de Mercedes - Puntaje: 3.25 - Precio: 78 pesos.
14.5.1. Métodos para comparar objetos
Para resolver las comparaciones entre hoteles, será necesario definir algunos métodos especiales que permiten comparar objetos.
En particular, cuando se quiere que los objetos puedan ser ordenados,
el método que se debe definir es __cmp__
, que debe devolver:
- Un valor entero menor a cero, si el primer parámetro es menor al segundo.
- Un valor entero mayor a cero, si el primer parámetro es mayor que el segundo.
- Cero, si ambos parámetros son iguales.
Para crear el método __cmp__
, definiremos primero un método auxiliar
ratio(self)
que calcula la relación calidad-precio de una instancia de
Hotel
según la fórmula indicada:
def ratio(self):
""" Calcula la relación calidad-precio de un hotel de acuerdo
a la fórmula que nos dio el cliente. """
return ((self.puntaje**2)*10.)/self.precio
A partir de este método es muy fácil crear un método __cmp__
que
cumpla con la especificación previa.
def __cmp__(self, otro):
diferencia = self.ratio() - otro.ratio()
if diferencia < 0:
return -1
elif diferencia > 0:
return 1
else:
return 0
Una vez que está definida esta función podremos realizar todo tipo de comparaciones entre los hoteles:
>>> h = Hotel("Hotel City", "Mercedes", 3.25, 78)
>>> i = Hotel("Hotel Mascardi", "Bariloche", 6, 150)
>>> i < h
False
>>> i == h
False
>>> i > h
True
14.5.2. Ordenar de menor a mayor listas de hoteles
En una unidad anterior vimos que se puede ordenar una lista usando el
método sort
:
>>> l1 = [10, -5, 8, 12, 0]
>>> l1.sort()
>>> l1
[-5, 0, 8, 10, 12]
De la misma forma, una vez que hemos definido el método __cmp__
,
podemos ordenar listas de hoteles, ya que internamente el método sort
comparará los hoteles mediante el método de comparación que hemos definido:
>>> h1=Hotel("Hotel 1* normal", "MDQ", 1, 10)
>>> h2=Hotel("Hotel 2* normal", "MDQ", 2, 40)
>>> h3=Hotel("Hotel 3* carisimo", "MDQ", 3, 130)
>>> h4=Hotel("Hotel vale la pena" ,"MDQ", 4, 130)
>>> lista = [ h1, h2, h3, h4 ]
>>> lista.sort()
>>> for hotel in lista:
... print hotel
...
Hotel 3* carisimo de MDQ - Puntaje: 3 - Precio: 130 pesos.
Hotel 1* normal de MDQ - Puntaje: 1 - Precio: 10 pesos.
Hotel 2* normal de MDQ - Puntaje: 2 - Precio: 40 pesos.
Hotel vale la pena de MDQ - Puntaje: 4 - Precio: 130 pesos.
Podemos verificar cuál fue el criterio de ordenamiento invocando al
método ratio
en cada caso:
>>> h1.ratio()
1.0
>>> h2.ratio()
1.0
>>> h3.ratio()
0.69230769230769229
>>> h4.ratio()
1.2307692307692308
Y vemos que efectivamente:
- "Hotel 3* carisimo", con la menor relación calidad-precio aparece primero.
- "Hotel 1* normal" y "Hotel 2* normal" con la misma relación calidad-precio (igual a 1.0 en ambos casos) aparecen en segundo y tercer lugar en la lista.
- "Hotel vale la pena" con la mayor relación calidad-precio aparece en cuarto lugar en la lista.
Hemos por lo tanto ordenado la lista de acuerdo al criterio solicitado.
14.5.3. Otras formas de comparación
Si además de querer listar los hoteles por su relación calidad-precio
también se quiere poder listarlos según su puntaje, o según su
precio, no se lo puede hacer mediante el método __cmp__
.
Para situaciones como esta, sort
puede recibir, opcionalmente, otro
parámetro que es la función de comparación a utilizar. Esta función
deberá cumplir con el mismo formato que el método __cmp__
, pero puede ser
una función cualquiera, ya sea un método de una clase o una función externa.
Además, para simplificar la escritura de este tipo de funciones,
podemos utilizar la función de Python cmp
, que si le pasamos dos
númeroes, devuelve los valores de la forma que necesitamos.
def cmpPrecio(self, otro):
""" Compara dos hoteles por su precio. """
return cmp(self.precio, otro.precio)
def cmpPuntaje(self, otro):
""" Compara dos hoteles por su puntaje. """
return cmp(self.puntaje, otro.puntaje)
Así, para ordenar según el precio, deberemos hacerlo de la siguiente forma:
>>> h1 = Hotel("Hotel Guadalajara", "Pinamar", 2, 55)
>>> h2 = Hotel("Hostería París", "Rosario", 1, 35)
>>> h3 = Hotel("Apart-Hotel Estocolmo", "Esquel", 3, 105)
>>> h4 = Hotel("Posada El Cairo", "Salta", 2.5, 15)
>>> lista = [ h1, h2, h3, h4 ]
>>> lista.sort(cmp=Hotel.cmpPrecio)
>>> for hotel in lista:
... print hotel
...
Posada El Cairo de Salta - Puntaje: 2.5 - Precio: 15 pesos.
Hostería París de Rosario - Puntaje: 1 - Precio: 35 pesos.
Hotel Guadalajara de Pinamar - Puntaje: 2 - Precio: 55 pesos.
Apart-Hotel Estocolmo de Esquel - Puntaje: 3 - Precio: 105 pesos.
14.5.4. Comparación sólo por igualdad o desigualdad
Existen clases, como la clase Punto vista anteriormente, que no se
pueden ordenar, ya que no se puede decir si dos puntos son menores o
mayores, con lo cual no se puede implementar un método __cmp__
.
Pero en estas clases, en general, será posible comparar si dos objetos son o no iguales, es decir si tienen o no el mismo valor, aún si se trata de objetos distintos.
>>> p = Punto(3,4)
>>> q = Punto(3,4)
>>> p == q
False
En este caso, por más que los puntos tengan el mismo valor, al no
estar definido ningún método de comparación Python no sabe cómo
comparar los valores, y lo que compara son las variables. p
y q
son
variables distintas, por más que tengan los mismos valores.
Para obtener el comportamiento esperado en estos casos, se redefinen
los métodos __eq__
(correspondiente al operador ==
) y __ne__
(correspondiente a !=
o <>
).
De forma que para poder comparar si dos puntos son o no iguales,
deberemos agregar los siguientes dos métodos a la clase Punto
:
def __eq (self, otro):
""" Devuelve si dos puntos son iguales. """
return self.x == otro.x and self.y == otro.y
def __ne__(self, otro):
""" Devuelve si dos puntos son distintos. """
return not self == otro
Una vez agregados estos métodos ya se puede comparar los puntos por su igualdad o desigualdad:
>>> p = Punto(3,4)
>>> q = Punto(3,4)
>>> p == q
True
>>> p != q
False
>>> r = Punto(2,3)
>>> p == r
False
>>> p != r
True