Ver índice de contenidos del libro

15.1. Polimorfismo

El concepto de polimorfismo (del griego muchas formas) implica que si en una porción de código se invoca un determinado método de un objeto, podrán obtenerse distintos resultados según la clase del objeto. Esto se debe a que distintos objetos pueden tener un método con un mismo nombre, pero que realice distintas operaciones.

En las unidades anteriores, varias veces utilizamos las posibilidades provistas por el polimorfismo, sin haberle puesto este nombre.

Se vio, por ejemplo, que es posible recorrer cualquier tipo de secuencia (ya sea una lista, una tupla, un diccionario, un archivo o cualquier otro tipo de secuencia) utilizando la misma estructura de código (for elemento in secuencia).

De la misma forma, hemos utilizado funciones que podían trabajar con los distintos tipos numéricos sin hacer distinción sobre de qué tipo de número se trataba (entero, real, largo o complejo).

Por otro lado, en la unidad anterior se vio también que al construir una clase, es posible incluir el método __str__ para que cuando se quiera imprimir el objeto se lo haga de la forma deseada; así como una gran variedad de otros métodos especiales, que permiten que operadores comunes sean utilizados sobre distintos tipos de objetos.

15.1.1. Interfaz

Llamamos interfaz a un conjunto de funciones, métodos o atributos con nombres específicos. Una interfaz es un contrato entre el programador que realiza una clase y el que la utiliza, puede consistir en uno solo o varios métodos o atributos.

Por ejemplo, para que un objeto se pueda comparar con otros, debe cumplir con la interfaz comparable, que en Python implica incluir el método __cmp__ visto en la unidad anterior.

La idea de polimorfismo se basa, entonces, en utilizar distintos tipos de datos a través de una interfaz común.

15.1.2. Redefinición de métodos

Llamamos redefinición a la acción de definir un método con el mismo nombre en distintas clases, de forma tal que provea una interfaz.

Un bloque de código será polimórfico cuando dentro de ese código se realicen llamadas a métodos que puedan estar redefinidos en distintas clases.

Tomemos por ejemplo el caso ya mencionado en el que se recorre una secuencia (lista, tupla, archivo, etc) mediante una misma estructura de código. Esto es posible gracias a la redefinición del método especial __iter__, que devuelve un iterador. Un bloque que utiliza una secuencia en forma genérica es, entonces, un bloque polimórfico.

Nota En Python al no ser necesario especificar explícitamente el tipo de los parámetros que recibe una función, las funciones son naturalmente polimórficas.

En otros lenguajes, puede darse que sólo algunas funciones específicas sean polimórficas (como en C++, por ejemplo), o que sea extremadamente difícil obtener un comportamiento polimórfico (como es el caso de C).

En la vida real, cuando analizamos las funciones de respiración, reproducción o alimentación, de los seres vivos vemos que siempre se repite el mismo patrón: si bien la acción en todos los casos es la misma, puede suceder que haya diferencias en la implementación en cada tipo, ya que no es lo mismo la respiración de una mojarrita que la de un malvón, no es lo mismo la reproducción de una ameba que la de un elefante.

De la misma forma, al implementar nuestras clases, debemos proveer distintas implementaciones de los métodos que se llaman igual, para que puedan comportarse polimórficamente, como ser las redefiniciones de los métodos __str__ o __cmp__ vistas en la unidad anterior.

En particular en Python, la sobrecarga de operadores, mencionada anteriormente, es un proceso que se realiza mediante la redefinición de algunos métodos especiales. En otros lenguajes se utilizan técnicas distintas para obtener el mismo resultado.

Nota El término sobrecarga viene de un posible uso de polimorfismo que está presente en algunos lenguajes orientados a objetos: la posibilidad de tener, dentro de una misma clase, dos métodos que se llamen igual pero reciban parámetros de distintos tipos. Es decir, que el método al que hay que llamar se decide por el tipo del parámetro, no por el tipo del objeto que lo contiene.

En Python no tenemos sobrecarga de métodos, ya que al no definir los tipos de los parámetros en el encabezado, no sería posible distinguir a qué método hay que llamar. Sin embargo, se puede decir que sí tenemos sobrecarga de operadores, ya que al encontrar un operador, Python llamará a distintos métodos según el tipo de las variables que se quiera sumar, restar, multiplicar, etc.

15.1.3. Un ejemplo de polimorfismo

En la unidad anterior se vio la clase Punto que representa a un punto en el plano. Es posible definir también una clase Punto3D, que represente un punto en el espacio. Esta nueva clase contendrá los mismos métodos que se vieron para Punto, pero para tres coordenadas.

Si a ambas clases le agregamos un método para multiplicar por un escalar (__mul__(self, escalar)), podríamos tener la siguiente función polimórfica:

def obtener_versor(punto):
    norma = punto.norma()
    return punto * (1.0 / norma)

Esta función devolverá un versor de dos dimensiones o de tres dimensiones, según a qué clase pertenezca la variable punto.

Advertencia A veces puede suceder que una función polimórfica imponga alguna restricción sobre los tipos de los parámetros sobre los que opera. En el ejemplo anterior, el objeto punto debe tener el método norma y la posibilidad de multiplicarlo por un escalar.

Otro ejemplo que ya hemos visto, utilizando secuencias, es usar un diccionario para contar la frecuencia de aparación de elementos dentro de una secuencia cualquiera.

def frecuencias(secuencia):
    """ Calcula las frecuencias de aparición de los elementos de
        la secuencia recibida.
        Devuelve un diccionario con elementos: {valor: frecuencia}
    """
    # crea un diccionario vacío
    frec = dict()
    # recorre la secuencia
    for elemento in secuencia:
        frec[elemento] = frec.get(elemento, 0) + 1
    return frec

Vemos que el parámetro secuencia puede ser de cualquier tipo que se encuentre dentro de la "familia" de las secuencias. En cambio, si llamamos a la función con un entero se levanta una excepción.

>>> frecuencias(["peras", "manzanas", "peras", "manzanas", "uvas"])
{'uvas': 1, 'peras': 2, 'manzanas': 2}
>>> frecuencias((1,3,4,2,3,1))
{1: 2, 2: 1, 3: 2, 4: 1}
>>> frecuencias("Una frase")
{'a': 2, ' ': 1, 'e': 1, 'f': 1, 'n': 1, 's': 1, 'r': 1, 'U': 1}
>>> ran = xrange(3, 10, 2)
>>> frecuencias(ran)
{9: 1, 3: 1, 5: 1, 7: 1}
>>> frecuencias(4)
Traceback (most recent call last):
    File "<pyshell\#0>", line 1, in <module>
        frecuencias(4)
    File "frecuencias.py", line 12, in frecuencias
        for v in seq:
    TypeError: 'int' object is not iterable
Copyright (c) 2011-2014 Rosita Wachenchauzer, Margarita Manterola, Maximiliano Curia, Marcos Medrano, Nicolás Paez. La copia y redistribución de esta página se permite bajo los términos de la licencia Creative Commons Atribución - Compartir Obras Derivadas Igual 3.0 siempre que se conserve esta nota de copyright.