LA FUNCIÓN SUPER()

 

TEIDE Y OCASO. CORAZÓN DE TENERIFE.


       Si pensamos que las castañuelas son el sonido del alma del flamenco, que la cerveza es el sabor del alma de Munich, o que las orquídeas son la chispa de color más bella del alma de la selva, en cualquier selva de nuestro pequeño mundo azul, podemos pensar que la función super() es la herramienta por excelencia de la herencia en Python que acabamos de estudiar en la página anterior. Y como tal merece un capítulo aparte.

      La función super() hace referencia, por "super", a una clase que se encuentra en un nivel de jerarquía superior a la clase en la que nos encontramos (a una superclase, clase padre, clase principal, etc.) en ese mismo momento, es decir, se ubicará siempre en un clase hija y apuntará a una clase padre o superclase que está por encima de ella, dentro del código que estemos escribiendo. Por eso, nunca encontraremos una función super() en el ámbito de una clase padre o superclase pero sí podremos encontrarla, una y más veces, en los ámbitos de cualquiera de las clases hijas que hereden o extiendan de la mencionada clase padre o superclase, lo que de paso, nos servirá también para, con echar un simple vistazo sobre un programa cualquiera que disponga de varias clases, dilucidar qué clase o qué clases son decididamente clases hijas de otra (herencia simple) o de otras (herencia múltiple).

Por ende, la función integrada super() de Python nos permite delegar una llamada desde un clase hija, que es su ecosistema, recordémoslo, tal y como acabamos de explicar en el párrafo anterior, a su clase padre. Gracias a ella y por su concurso, podremos invocar a cualquier método instanciado en la clase padre o superclase para incorporarlo al ámbito de nuestra clase hija sin que tengamos que llamarlo textualmente, de manera explícita. Por su nombre, vamos. Contribuimos así a simplificar nuestro código que resultará más sencillo de controlar, conservar, enriquecer y depurar.


Como comprobamos en la captura, existen cuatro posibilidades de uso de la función super(), siendo la más común y sencilla la primera. 

Traemos un ejemplo usando como clase padre un datatype e invocando sus métodos nativos a través de la función super():


En el caso de la herencia múltiple, en el supuesto de que dispongamos de dos o más clases padre o superclases de las cuales podamos invocar el mismo método, la función super() llamará al primero que encuentre en línea ascendente (o el último en línea descendente, si lo preferimos así).

A modo de comentario y en relación directa con lo que acabamos de decir, en Python contamos con el MRO, el Method Resolution Order, es decir, el Orden de Resolución de un Método, ORM, por sus siglas en castellano, y que estipula una jerarquía de llamadas.

Cuando en una clase hija se está buscando un método heredado de dos o más clases padre, primero busca de izquierda a derecha, es decir, entre "clases hermanas", y el primer método que encuentre que coincida con el criterio de búsqueda, ...¡Zas!...¡Lo pillé!..., será el que devuelva. Si no lo encuentra, buscará de abajo a arriba, remontándose en el código, hasta encontrar el primer método que cumpla con el criterio de búsqueda. Y éste será el que devuelva.

Para evitar situaciones que puedan generar conflicto es preferible, y ya lo comentamos en la página anterior dedicada a las herencias, no recurrir a la herencia múltiple, salvo que su inclusión resulte eficiente, recomendable por la estructura del código (eso implica una estructura relativamente simple) y bien controlada por nosotros como programadores, procurando prever en el espacio de nombres, namespaces, utilizar nominaciones que no induzcan a error en el sistema de búsqueda de la MRO.




Vamos a introducir un ejemplo que nos mostrará el funcionamiento de la MRO:




Analicemos qué ha sucedido aquí: en 1. procedemos a instanciar un objeto que llamamos especia, desde la clase ClaseD, la última que hemos modelizado y que es hija o extiende de las superclases o clases padre ClaseB y ClaseC, que hemos introducido en ese orden preciso en la zona de llamadas o herencias de nuestra clase hija ClaseD, por lo que nos encontramos con un caso flagrante de herencia múltiple, 🙉.

A continuación, a este objeto especia le aplicamos el método a1(). Como Python no lo encuentra dentro de ClaseD se dirige a la primera clase padre o superclase incluida en la zona de herencias, de izquierda a derecha, la clase ClaseB, donde encuentra un método con el mismo nombre (con el mismo espacio de nombres, namespace), siendo éste cuyo resultado devuelve en 2.

En la siguiente línea de código le aplicamos al objeto especia el método a2(). Como existe un método con este mismo nombre (espacio de nombres) dentro del cuerpo de ClaseD donde nuestro objeto especia fue instanciado, Python se relaja, se toma un aperitivo junto a la piscina, no busca más y devuelve, en 3., la ejecución del método a2().

Continuamos instanciando un objeto nuevo que llamamos otra_especia desde la ClaseB, que sólo hereda de la clase ClaseA, como vemos en 4. Y a este nuevo objeto le aplicamos el método a2(). Como ya existe un método a2() dentro del cuerpo de la clase en la que el objeto fue instanciado, Python se relaja todavía más, se echa una siestecita bajo la sombrilla y no busca más, devolviendo el resultado que tenemos en 5.

Recuperamos nuestro primer objeto instanciado especia y le aplicamos el método a3(). Como no lo encuentra en la propia ClaseD pasa a buscarlo en las clases padre o superclases que hemos pasado en la zona de herencias. Apunta primero a ClaseB, que es la primera que encuentra en esa zona de herencias, pero tampoco encuentra el método aquí. En consecuencia, descarta la ClaseB y pasa a ocuparse de la siguiente clase de la zona de herencias, ClaseC, en cuyo ámbito, ahora sí, encuentra el método que busca. Sin embargo, si nos fijamos, esta ClaseC es hija de la clase padre o superclase ClaseA que, mire usted por donde, también dispone de un método a3(). Ergo,... ¿Qué método va a llamar ClaseD? ¿El método a3() de ClaseC o el método a3() de ClaseA? Pues la respuesta es que, de acuerdo a la MRO, prima el método homónimo que encuentre en la propia clase padre o superclase donde ya esté, y descarta iniciar una búsqueda en el ámbito de la clase o de las clases padre o superclases que hayamos pasado en su zona de herencias.

Finalmente, llamamos al método a4() para aplicárselo al objeto especia. Como Python no encuentra en la propia ClaseD lo busca en la clase o clases padre o superclases que se hayan en la zona de herencias de la ClaseD. No lo encuentra en la ClaseB pero sí en la ClaseA. Realiza entonces Python una MRO análoga a la del ejemplo anterior, pero ninguna otra clase que herede o extienda de ClaseA, esto es, ni ClaseC ni ClaseB, contienen en su ámbito un método con ese nombre o espacio de nombres, por lo que Python devuelve en 7. el resultado del método a4() que ha encontrado sólo en el cuerpo o ámbito de ClaseA, aún a pesar de no estar este método en los ámbitos de ninguna de las clases padre o superclases que hemos insertado en la zona de herencias de ClaseD: CANELA.

BOMBA VOLCÁNICA DE BASALTO OSCURO DESCANSANDO DESDE MILLONES DE AÑOS ATRÁS SOBRE UN LECHO DE PIEDRA PÓMEZ, PARQUE NACIONAL DE LAS CAÑADAS DEL TEIDE, CENTRO DE TENERIFE.

Vamos a estudiar una variación del código anterior: en lugar de clases creadas al mismo nivel vamos a hacer lo propio con clases anidadas y, por tanto, sin recurrir a herencias, para ver qué sucede.


En este nuevo ejemplo, sólo podemos instanciar objetos a partir de la clase ClaseA, como vemos en 1. Instanciamos, entonces, un objeto que llamamos especia. Sobre este nuevo y flamante objeto especia invocamos al método a1(). Como Python lo encuentra dentro del cuerpo de la propia ClaseA, lo aplica directamente y nos devuelve el resultado, como comprobamos en 2. Si ahora nos interesa llamar sobre el objeto especia el método a1() que corresponde a la clase ClaseB, lo podemos hacer por mediación de la sintaxis del punto (dott method), como vemos en 3., devolviéndonos el resultado esperado. Obviamente, al no incorporar herencias, las llamadas a métodos incluidos en las clases que no sean la clase principal (aquélla a la que se anidan las demás. Fijémonos que aquí no hablamos de clases padre o superclases y clases hijas: no es lo mismo las clases anidadas que las clases heredadas o extendidas) no se pueden invocar directamente, por lo que el intérprete de Python lanza una excepción del tipo AttributeError.

Para terminar con este capítulo dedicado a la función integrada super(), teniendo en cuenta que ya hemos visto algo de ella en la página anterior y volveremos a repasarla en un segundo supuesto que se publicará más adelante, veamos un ejemplo de aplicación de la función y de la importancia que tiene el correcto ordenamiento de las llamadas a las clases padres o superclases en el caso de las herencias múltiples.


AGUILILLA SOBREVOLANDO ALGUNAS FORMACIONES CAPRICHOSAS DE ROCA Y LAVA EN LAS CAÑADAS DEL TEIDE, CENTRO DE TENERIFE.


No hay comentarios:

Publicar un comentario