CLASES III

PANORÁMICA DE LA PLAYA DE LAS TERESITAS A LA CAÍDA DE LA TARDE, EN LA COSTA SUR DEL MACIZO DE ANAGA. AL FONDO, RECOSTADO SOBRE LA LADERA, EL PUEBLO PESQUERO DE SAN ANDRÉS. AÚN MÁS AL FONDO, EN LO MÁS ALTO DEL PERFIL, LA CIMA DEL VOLCÁN TEIDE.

      Vamos a introducir, continuando con los dos capítulos anteriores, otro ejemplo más de clase basándonos en un ejemplo propuesto por Mark Sommerfield en Python 3, Ed. Anaya Multimedia, y que se apoya en el mismo supuesto de los puntos y coordenadas. Modelizaremos una clase que llamaremos Point donde insertaremos dos coordenadas, x e y, que nos servirán para definir un punto sobre una superficie bidimensional (si fuera tridimensional, necesitaríamos un tercer punto, z). Podemos almacenar nuestro código de la forma habitual en un archivo Python. Recordemos que los archivos en Python se conocen como módulos y se generan con un nombre cualquiera y la extensión .py. En este caso, lo podemos llamar shape.py.


Éste es el código que almacenaremos como shape.py en nuestra carpeta del programa (tengámoslo en cuenta) para poder importarlo cuando lo necesitemos.
Comprobamos ahora si el código funciona como debe ser mediante una importación relativa, pues sólo nos interesa llamar a la clase almacenada en el módulo, por otra parte, la única que tenemos, claro:


Parece que todo funciona que da gusto. 👌
Lo que más nos puede llamar la atención de este script es la proliferación de métodos especiales (los reconocemos fácilmente porque se envuelven entre dobles guiones bajos), cuatro en total, si contamos con el inicializador/constructor __init__() que ya conocemos muy bien a estas alturas, a excepción del método que viene inmediatamente después, distancia_desde_el_origen(), y que es un método instanciado directamente por nosotros mismos. Recordemos que del mismo modo que existen en Python variables especiales (poquitas, la verdad, pero haberlas haylas), con doble guión bajo antes y después del nombre, como __main__, en el caso de la función principal main(), o __debug__, para efectuar depuraciones simples, existen también métodos especiales, también con una sintaxis de doble guión bajo antes y después del nombre, como el mismo __init__(), y de los que hablaremos más adelante en este manual.

      NO HAY QUE SER UN GENIO PELUDO COMO YO PARA DARSE CUENTA QUE SI SE APELLIDAN 'ESPECIALES' ES PORQUE HACEN COSAS 'ESPECIALES', DIGO YO. SON MÉTODOS QUE PERTENECEN AL OBJETO object, VALGA LA REDUNDANCIA, EL CUAL SE CONSTITUYE COMO EL OBJETO PADRE (parent) DEL QUE DESCIENDEN Y DEPENDEN TODOS LOS DEMÁS OBJETOS DE PYTHON SEGÚN UN ORDEN JERÁRQUICO, TANTO LOS QUE VIENEN YA CREADOS POR EL PROPIO LENGUAJE COMO LOS QUE INSTANCIEMOS/CREEMOS NOSOTROS MISMOS COMO PROGRAMADORES. ES COMO EL PIRAMIDÓN, EL VÉRTICE SUPERIOR DE UNA PIRÁMIDE EGIPCIA, EL PRIMER BLOQUE DE GRANITO DESDE Y A PARTIR DEL CUAL DESCIENDEN LOS OTROS MILLONES. LO PODEMOS COMPROBAR EN LA CAPTURA QUE OS TRAIGO:




En este código, como se ha querido hacer "cosas especiales", y ya que Python los pone lindamente a nuestra entera disposición con sólo llamarlos con su ortografía, podemos acceder a ellos y obtener los retornos o devoluciones (return) que deseamos.
Para finalizar esta primera aproximación observamos que la clase Point contiene en la zona de parámetros del inicializador dos variables, x e y. con valores preasignados a 0 para cada una. y que luego, en el ámbito de la función inicializadora, se instancian como propiedades: self.x = x y self.y = y. Así, cualquier objeto punto que construyamos/instanciemos a partir de la clase Point tendrá, por defecto, estas mismas propiedades que definirán su ubicación en el espacio: una coordenada x con valor 0 y una coordenada y con valor 0.

      ¡YO TAMBIÉN QUIERO APORTAR ALGO! COMO YA HEMOS VISTO EN CLASES I Y II, POR NORMA GENERAL, LAS CLASES  SÓLO NECESITAN QUE IMPLEMENTEMOS EL MÉTODO  INICIALIZADOR __init__(), EN TANTO QUE EL MÉTODO ESPECIAL __new__(), EL AUTÉNTICO MÉTODO CONSTRUCTOR ES INVOCADO DIRECTAMENTE POR EL INTÉRPRETE DE PYTHON, A MENOS, CLARO, QUE NOSOTROS MISMOS LLAMEMOS AL MÉTODO __new__(). ASÍ PUES,CUANDO INSTANCIAMOS UN OBJETO A PARTIR DE UNA CLASE, POR EJEMPLO, p = Point(), SE INVOCA PRIMERO AL MÉTODO p.__new__(cls) PARA CREAR AL OBJETO (Y QUE NOSOTROS NO VEMOS PORQUE ES UN PROCESO ELIPSADO), Y DESPUÉS SE INVOCA AL MÉTODO ESPECIAL __init__(self) PARA INICIALIZARLO. LO VEMOS AQUÍ ABAJO:



PAISAJE DE LAS CAÑADAS DEL TEIDE Y LA TORMENTA QUE VIENE. PARQUE NACIONAL DE LAS CAÑADAS DEL TEIDE. TENERIFE.

Aplicando al análisis de nuestro código la aportación de nuestra pequeña amiga, podemos comenzar diciendo que cuando codificamos la asignación p = Point() y pulsamos ENTER, Python comienza el proceso de instanciación (creación) del objeto p. Y lo primero que hace es ponerse a buscar como un loco el método especial constructor __new__() que, normalmente, asigna el propio lenguaje a través de la clase, en este caso, Point, a cualquier objeto hijo que se instancie a partir de ella, si no hemos introducido nosotros antes ninguna formulación alternativa. Y lo buscará en la clase BASE de Point, que es dependiente directa (hija) de la superclase object, que ya contiene por defecto el método especial __new__(). En este momento, en cuanto lo encuentra, el intérprete de Python, que es más listo que el hambre, lo invoca y, por fin, se instancia nuestro objeto p (y todo esto sin darnos cuenta ya que se trata de un proceso interno de Python, compartido con  todo lenguaje de programación desarrollado en C).
Python buscará entonces el método especial inicializador __init__(self) el cual, en este caso sí, podemos configurar nosotros  según nuestras necesidades, con lo que podremos atribuirle al objeto p algunas propiedades iniciales. Así, la secuencia es como sigue:

Y esto, aplicándolo al objeto p que queremos instanciar a partir de la clase Point, podemos verlo perfectamente a continuación:




Lo último que hará Python con nuestro recién instanciado objeto p es configurar la variable p para que se convierta en una referencia de objeto que apunte, mire usted por dónde, al propio objeto p.


Con def __init__(self. x=0, y=0): self.x = x self.y = y tenemos dos variables de instancia: self.x=x  y self.y=y, que se generan primero como argumentos (lo son porque ya se les asigna sendos valores 0 en la zona de parámetros del inicializador) antes que como variables de instancia propiamente dichas, cosa que ocurre en las dos siguientes líneas de código dentro del ámbito del inicializador. En tanto que Python acude  presto y raudo al inicializador de la  clase Point cada vez que instanciamos un objeto nuevo, si no queremos modificar los valores que les asignamos por defecto a las coordenadas x e y, es decir,  x=0 e y=0, no tenemos por qué invocar al inicializador del objeto (p.__init__(nuevo_valor_de_x, nuevo_valor_de_y)) en el propio objeto como hicimos en el último ejemplo, salvo para cambiarle los valores iniciales a éste (x=0 e y=0) por otros nuevos.
Con def distancia_desde_el_origen(self): return math.hypot(self.x, self.y) instanciaremos un método convencional mediante una función definida por el usuario (def) que devuelve la hipotenusa entre dos puntos dados.
Con def __eq__(self, other): return self.x == other.x and self.y == other.y Python evaluará mediante el uso del operador de equivalencia == si el valor de self.x es exactamente el mismo que el valor de other. Notemos que eq de __eq__ es una contracción del término inglés equivalent. Si es así devolverá True. En caso contrario, devolverá False.
Apuntamos que Python, por su parte, ofrece métodos especiales para todos los operadores de comparación:



      SEÑALAMOS QUE LOS métodos NO DEBEN TENER NOMBRES QUE COMIENCEN Y TERMINEN CON DOBLE GUIÓN BAJO PORQUE PYTHON CUENTA CON SUS métodos especiales PREDEFINIDOS DIFERENCIADOS CON ESTA ORTOGRAFÍA Y PODRÍA PROVOCAR CONFLICTOS DE NOMBRES.


Por defecto, el método devolverás False porque todo el conjunto de métodos especiales que hemos visto soportan, de manera predeterminada, ==. Por supuesto, la comparación a través el método especial __eq__() debe hacerse entre objetos del mismo tipo (que ambos sean cadenas, o enteros, o listas, etc.), ya que de no ser así, se lanzará una excepción del tipo AtributteError.
A renglón seguido tenemos el método def __repr__(self): y su bloque de código correspondiente, return "Punto ({0.x!r}, {0.y!r}".format(self). En Python contamos con una función integrada denominada repr() y que devuelve una representación en forma de string, auténtica, canónica, pura pata negra. Lo comprobamos:



Cuando invocamos a repr() ésta invoca directamente al método especial __repr__() para el objeto que se le pase como argumento y que devuelve, como acabamos de comprobar en el ejemplo, una string, una cadena de texto que, además, puede ser de dos tipos. A saber:
  • Cuando podemos hacer una evaluación de la string que se nos ha devuelto mediante la función integrada eval(), que nos permite crear un objeto equivalente a repr() allí donde se invoque.





  • Se implementa cuando no es posible recurrir al anterior.


Finalmente, al final del código,  def __str__(self): y su bloque de código correspondiente, return "({0.x!r}, {0.y!r}".format(self). La función integrada str(), que conocemos ya de largo, funciona igual que repr(), excepto que si la segunda invoca al método especial __repr__(), la primera hace lo propio con __str__() del objeto.


El resultado está pensado para que pueda ser leído y entendido por "humanos" y no se espera que pueda ser pasada como argumento de la función eval().

EL VOLCÁN DEL TEIDE CUBIERTO DE NIEVE Y EXTRUSIONES LÁVICAS EN FORMA DE DIENTES DE SIERRA POR CAUSA DE LA EROSIÓN. PARQUE NACIONAL DE LAS CAÑADAS DEL TEIDE. TENERIFE.

No hay comentarios:

Publicar un comentario