Comparando objetos en Python

Objetivo: aprender a implementar los operadores de comparación en nuestras clases.

En el artículo anterior mostramos los cimientos de la sobrecarga de operadores en Python. Aprendimos a dar significado a la suma de objetos redefiniendo un método mágico, __add__ (dos símbolos de subrayado al comienzo y otros dos al final), en cuyo interior describíamos el funcionamiento del “+”.

Hay, desde luego, más operadores que podemos sobrecargar y hoy nos ocuparemos de un conjunto específico de ellos: los de comparación.

¿Cuándo decimos que un objeto es mayor que otro? La respuesta, como programador, la tienes tú y sólo tú.

Imagina que defines una clase Persona entre cuyos atributos se encuentran su nombre, edad, altura y peso. Si pedro y luis (permíteme las minúsculas) son dos instancias de esa clase, ¿cuándo decimos que pedro es mayor que luis?

pedro > luis

Parece natural responder a esta comparación atendiendo a la edad de cada uno, pero no necesariamente tendría que ser así. Quizás prefieras basarte en su tamaño y optar por parámetros como la altura o el peso.

Comencemos creando una sencilla clase Persona:

class Persona():

    def __init__(self, nombre, edad, altura, peso):
        self.nombre = nombre
        self.edad = edad
        self.altura = altura
        self.peso = peso

Es preferible que la clase la crees en un fichero aparte, pues la tendremos que editar a menudo. Agrega también la creación de algunas instancias de la clase, así no habrá que volver a introducirlas cada vez que modifiquemos el código:

pedro = Persona("Pedro", 62, 1.65, 71)
luis = Persona("Luis", 40, 1.80, 82)
carmen = Persona("Carmen", 62, 1.70, 60)

Ejecuta el código completo para seguir a continuación desde el modo interactivo.

Obviamente, la comparación directa aún no tiene sentido:

>>> pedro > luis
Traceback (most recent call last):
  File "<pyshell#13>", line 1, in <module>
    pedro > luis
TypeError: unorderable types: Persona() > Persona()

Python sabe cómo ordenar valores numéricos, o incluso cadenas de caracteres u otras secuencias, pero no tiene ni idea de cómo apañárselas ante objetos de otra índole.

Efectuamos la comparación accediendo a los atributos de cada instancia:

>>> pedro.edad > luis.edad
True
>>> pedro.peso > luis.peso
False

Para que pedro > luis tenga sentido necesitamos ampliar la clase Persona definiendo un nuevo método mágico, __gt__, en el que incluiremos la funcionalidad deseada. Las letras gt se corresponden a greater than, “mayor que”, en inglés.

Si queremos que “mayor que” signifique “de más edad que”, la clase Persona habría de redefinirse del siguiente modo:

class Persona():

    def __init__(self, nombre, edad, altura, peso):
        self.nombre = nombre
        self.edad = edad
        self.altura = altura
        self.peso = peso

    def __gt__(self, persona):
        return self.edad > persona.edad 

Con esta nueva definición, la comparación directa ya es posible:

>>> pedro > luis
True

La clave de todo esto es entender que

pedro > luis

es equivalente a

pedro.__gt__(luis)

Es decir, el método __gt__ se aplica sobre el objeto a la izquierda del símbolo >, tomando como argumento el objeto de la derecha, tal como ya explicamos al sobrecargar la suma.

El código es bien simple:

    def __gt__(self, persona):
        return self.edad > persona.edad 

self es el objeto sobre el que actúa el método (pedro) y persona el que se facilita como argumento (luis). La función simplemente compara ambas edades, devolviendo el resultado de la comparación.

Habiendo definido ya un operador de comparación, nuestra clase quedaría un tanto coja si no implementáramos los restantes: <, >=, <=, == y !=.

He aquí los métodos mágicos asociados a cada operador de comparación:

>     __gt__
<     __lt__
>=    __ge__
<=    __le__
==    __eq__
!=    __ne__

De modo que esta sería la implementación completa de la comparación en la clase:

class Persona():

    def __init__(self, nombre, edad, altura, peso):
        self.nombre = nombre
        self.edad = edad
        self.altura = altura
        self.peso = peso

    def __gt__(self, persona):
        return self.edad > persona.edad

    def __lt__(self, persona):
        return self.edad < persona.edad

    def __ge__(self, persona):
        return self.edad >= persona.edad

    def __le__(self, persona):
        return self.edad <= persona.edad

    def __eq__(self, persona):
        return self.edad == persona.edad

    def __ne__(self, persona):
        return self.edad != persona.edad

Ejecuta el código completo de nuevo (incluyendo la creacion de las tres instancias) para poner en práctica la totalidad de las comparaciones:

>>> luis > carmen
False
>>> pedro < carmen
False
>>> carmen >= pedro
True
>>> luis <= carmen
True
>>> carmen == pedro
True
>>> carmen != luis
True

Python dispone de un truco que evita tener que sobrecargar los seis operadores. Dado que a > b también equivale, leído de derecha izquierda, a b < a, Python es suficientemente inteligente para interpretar que, si no incluyes una declaración específica del método __lt__, el <, “menor que”, no es más que un __gt__ intercambiando los argumentos. Se dice que los operadores > y < actúan “en espejo”. Lo mismo sucede con los pares >= y <=, así como con == y !=.

De modo que, tan sólo definiendo tres métodos, uno por cada par de operadores “espejo”, podríamos obtener la misma funcionalidad:

class Persona():

    def __init__(self, nombre, edad, altura, peso):
        self.nombre = nombre
        self.edad = edad
        self.altura = altura
        self.peso = peso

    def __gt__(self, persona):
        return self.edad > persona.edad

    def __ge__(self, persona):
        return self.edad >= persona.edad

    def __eq__(self, persona):
        return self.edad == persona.edad

Los operadores binarios (con dos operandos) no son los únicos susceptibles de ser sobrecargados en Python. Los unarios (como el signo negativo) o los extendidos (como el +=) también pueden servirnos para realizar curiosos trucos de magia. Pero esos los dejaremos para otro artículo…

Javier Montero Gabarró


http://elclubdelautodidacta.es/wp/2015/06/comparando-objetos-en-python/


El texto de este artículo se encuentra sometido a una licencia Creative Commons del tipo CC-BY-NC-ND (reconocimiento, no comercial, sin obra derivada, 3.0 unported)


El Club del Autodidacta


Consulta el índice completo de artículos relacionados con Python.

Safe Creative #1506254454136

Deja un comentario