CLASES I

VISTA DE LA PARTE NORTE DEL VALLE DE AGUERE (LA LAGUNA) DESDE EL MIRADOR DE JARDINA, EN LAS ESTRIBACIONES MÁS OCCIDENTALES DE ANAGA, CON LA MESA DE MOTA EN EL CENTRO Y PARTE DE LOS MONTES DE ANAGA OESTE BAJO LAS NUBES DEL ALISIO. AL FONDO, EL  VOLCÁN DEL TEIDE, CON UNA MANCHITA DE NIEVE. TENERIFE.

      Con lo que ya hemos dicho en la introducción, no podemos empezar de otra manera. En Python, crear o declarar (la expresión correcta es modelizar) una clase resulta extremadamente sencillo: sólo tenemos que utilizar la sentencia o palabra reservada (keyword) class seguida del nombre que le vamos a proporcionar para identificarla, de modo análogo a lo que hacemos cuando construimos una función definida, y poder llamarla/invocarla cuando lo necesitemos. Este nombre, por convención, y a tenor de las especificaciones del PEP8, debe comenzar en mayúsculas.
Continuando con las analogías con las funciones definidas que ya conocemos o, incluso, como recomendábamos cuando declarábamos una simple variable, resulta igualmente una buena práctica proporcionar un nombre de clase (classname) que vaya en consonancia con el tipo de programa que vamos a codificar y/o con los objetos que de ella vayamos a instanciar: nos ahorrará alguna que otra jaqueca.
En nuestro ejemplo de la introducción, podían ser nombres de clase idóneos cochesvehiculos o automoviles (recordamos que no debemos usar tildes ni 'ñ' aún cuando procedan ortográficamente para no entrar en conflictos con los sistemas de codificación estandarizados en los programas) para lo que se espera de ella.
Su sintaxis sería tan sumamente simple como sigue:



Como observamos, al final del nombre de la clase (classname) debemos colocar dos puntos, :, pues el bloque de código de la clase debe ir indentado (sangrado) lo que, subsecuentemente, nos dice que toda clase, igual que toda función definida por nosotros, cuenta con un ámbito de codificación que le es propio (ámbito de la clase o class scope) y exclusivo: en un mismo programa, hay un espacio de codificación que queda DENTRO de la clase, y que le es propio, el ámbito de la clase propiamente dicho, y un espacio que queda FUERA, que ni afecta ni se ve afectado por la clase. Nos sigue valiendo la analogía con las funciones definidas.
Resumiendo, la sentencia class es una sentencia "compuesta" integrada por la palabra clave class, el nombre de la función (classname) y dos puntos, que nos advierten de una indentación o sangrado delimitando su ámbito (scope), donde insertaremos el código que corresponda. Esto se conoce como encabezado o cabecera de la clase, class header.
El esquema de arriba nos enseña el proceso básico de modelización de una clase en Python. Lo que sucede es que como no hemos instanciado ningún objeto en su interior, esta una clase así resulta del todo inútil, inoperante.
Si queremos que esta clase 'exista' y el intérprete de Python no nos lance una excepción tan sólo tenemos que añadir en el cuerpo de la clase la cláusula pass, que permite salvar el código. Así Python cortará el flujo de ejecución de la clase, la "ignorará", y podremos seguir codificando nuestro programa tranquilamente. Ya hemos dicho que los programadores, mientras elaboran la estructura de un programa complejo, sitúan en tal o cual posición una función definida o una clase que saben que van a tener que definir más adelante pero en las que, de momento, no interesa insertar código, soslayándola con la cláusula pass para saltar el flujo de lectura, y proseguir con el desarrollo.
Volviendo a lo que nos ocupa, sabiendo ya cómo modelizar una clase, vamos a ver ahora cómo instanciar un objeto a partir de ella.


El procedimiento es también extremadamente sencillo: elegimos un nombre para nuestro objeto y lo colocamos dentro del ámbito de la clase. Así pues, por mor de la jerarquía que conlleva la indentación, el objeto pasa a ser implícitamente un objeto 'hijo' o 'dependiente' de su clase padre, esto es, aquélla donde ha sido instanciado (creado). A renglón seguido, y mediante el operador = de asignación, le asignamos a nuestro recién creado objeto el valor que queramos: una cadena, un número, una lista, etc.




      ES POSIBLE QUE EN PROGRAMAS DE VERSIONES MÁS ANTIGUAS DE PYTHON ENCONTREMOS QUE AL FINAL DEL NOMBRE DE LA CLASE, ANTES DE LOS DOS PUNTOS, SE COLOCA UN DOBLE PARÉNTESIS, ( ), COMO EN LAS FUNCIONES DEFINIDAS POR EL USUARIO. EN REALIDAD ESTO OBEDECE A QUE TODA CLASE ES DEPENDIENTE O HIJA DE UN OBJETO INTERNO DE PYTHON LLAMADO, PRECISAMENTE, ASÍ: object. SIMILAR AL HECHO DE QUE TODA EXCEPCIÓN DEPENDE A SU VEZ DE BaseException. INCLUSO LO PODEMOS VER PASADO COMO ARGUMENTO DE LA CLASE. EN LAS VERSIONES MÁS RECIENTES DE PYTHON LOS PARÉNTESIS YA NO SE UTILIZAN. OS DEJO UN PAR DE EJEMPLOS MIENTRAS ME VOY A HOCIQUEAR UN RATO POR AHÍ.



En el esquema superior ya tenemos instanciados tres  objetos de la clase Classname, cada uno de ellos con el valor que le hubiéramos asignado. ¡Vaya! ¿Y cómo sabemos si realmente son objetos instanciados pertenecientes a la clase? Aplicando la sintaxis del punto (dott method o dott sintax) a nuestra clase: escribiríamos el nombre de la clase y, a continuación, un punto. Con esto conseguimos llamar al primer objeto dependiente  en la jerarquía de construcción de la clase. Como sólo tenemos instanciados objetos sencillos, podemos invocarlos simplemente con colocar el nombre del objeto tras el punto. Con esto, el intérprete de Python nos mostrará el valor asociado al mismo. Lo vemos:




¿Y qué podemos hacer con esto? Pues, de momento, nada. ¿Por qué? Porque cualquier objeto sin propiedades y sin métodos es un objeto "fantasma".
Pues bien. Vamos a aprender primero cómo añadir atributos o propiedades a un objeto instanciado a partir de una clase. Debemos tener en cuenta que cuando designamos una propiedad a un objeto hay que proporcionarle también un valor en tanto que, como ya hemos explicado, vamos a codificar con pares propiedad = valor.
La sintaxis que vamos a aplicar para conseguir ésto es la siguiente:


A continuación traducimos el esquema a un ejemplo práctico:



En este caso, a los atributos o propiedades les hemos proporcionado el nombre (feísimo) de x e y. Los datos o valores asignados son aquéllos, obviamente, que quedan a la derecha del operador =  de asignación.

      NOTEMOS CÓMO LA MANERA DE INSTANCIAR UN objeto A PARTIR DE UNA clase, EN EL EJEMPLO PRECEDENTE, ES DIFERENTE AL QUE USAMOS MÁS ARRIBA, EN EJEMPLOS ANTERIORES: EN EL EJEMPLO DE LAS CLASES Pintores Y Coches INSTANCIAMOS LOS objetos DENTRO DEL CUERPO (ámbito) DE LA PROPIA clase, POR LO QUE RECURRIMOS  A LA ASIGNACIÓN CLÁSICA propiedad = valor, MIENTRAS QUE EN EL EJEMPLO DE LA clase adicion (SÍ, SE NOS ESCAPÓ LA MAYÚSCULA, NO SOMOS PERFECTOS, GRACIAS A DIOS 😖) INSTANCIAMOS LOS objetos FUERA DEL CUERPO (ámbito) DE LA PROPIA clase, POR LO QUE DEBEMOS RECURRIR A UNA SINTAXIS DISTINTA, QUE SERÁ LA QUE EMPLEEMOS COMÚNMENTE EN EL DESARROLLO DE NUESTROS CÓDIGOS FUTUROS: objeto = nombre_de_la_clase(), CON DOBLE PARÉNTESIS AL FINAL DEL NOMBRE DE LA clase. EN VERSIONES ANTERIORES DE PYTHON SE RECLAMABA INSERTAR ENTRE LOS PARÉNTESIS LA REFERENCIA object, PERO AUNQUE SIGUE FUNCIONANDO, YA NO SE UTILIZA EN LAS VERSIONES MÁS MODERNAS DE PYTHON. VEMOS UN EJEMPLO AQUÍ ABAJO.



Veamos ahora un nuevo ejemplo, igualmente sencillo, pero un poco más elaborado:


Sencillo, ¿verdad? Pues con estas líneas de código ya hemos proporcionado propiedades y valores a nuestros objetos recién instanciados.

CRESTERÍAS DE OBSIDIANA, PENACHOS DE RETAMA Y HIERBA PAJONERA Y MALPAÍS EN LAS CAÑADAS DEL TEIDE, CON NIEVE EN LOS CERROS, AL FONDO, EN EL PARQUE NACIONAL DE LAS CAÑADAS DEL TEIDE. TENERIFE.

Vamos a ver a continuación cómo construir o instanciar métodos para nuestros objetos.



Instanciar métodos para los objetos y, recordemos que un método es una función que apunta a un determinado COMPORTAMIENTO, a una ACCIÓN que un objeto dado, instanciado previamente a partir de una clase, es capaz de realizar, de llevar a cabo dentro del código, permite que ese mismo objeto HAGA ALGO, aquéllo para que lo hemos programado, precisamente. Si instanciamos un objeto coche a partir de la modelización de la clase Vehiculo (class Vehiculo:) podemos instanciar el método "arrancar" que hará que el motor del coche empiece a funcionar, o podemos instanciar el método "frenar" para que, al pisar el pedal del freno o tirar de la palanca de freno, el coche aminore drásticamente la velocidad y se detenga.
De hecho, para aclararnos un poco, podemos acudir a la siguiente regla:


Veámoslo de manera esquematizada:


Este esquema igual nos puede abrumar un poco así que vamos a ilustrarlo con un ejemplo sencillo:


Aunque algunos de los elementos que se muestran no nos suenan o nos suenan pero nos resulta extraño verlos aquí, como la sentencia def, TODO se irá explicando progresivamente a lo largo de este capítulo: no desesperemos. ¡Y es mucho más fácil de lo que parece! De momento, sólo nos interesa que conozcamos, que nos habituemos, al mecanismo de instanciación de métodos (resulta una excelente práctica de aprendizaje construir en casa vuestros propios ejemplos de código a partir de los que os mostramos aquí, modificándolos, alterándolos y/o explorando cosas nuevas que se os ocurran, así que os animamos encarecidamente a ello). Así pues, si con lo que hemos expuesto hasta ahora conseguimos ver un poco más claro el proceso de instanciación de métodos a partir de una clase, algo hemos ganado, ¿verdad?

      

      VAMOS A TOMAR CARRERILLA  Y TRAIGAMOS AQUÍ UN EJEMPLO CONCRETO DE TODO CUANTO HEMOS APRENDIDO HASTA AHORA, ¿DE ACUERDO?

EJEMPLO 1:



EJEMPLO 2:




EJEMPLO 3:




EJEMPLO 4:



EL CONSTRUCTOR __init__():


      En el primer ejemplo recurrimos a lo que se llama  una FUNCIÓN CONSTRUCTORA en Python. Se trata de la función __init__(self), con este nombre, y que definimos nosotros mismos con la consabida keyword def. Realmente, __init__(self) es un método especial (ya veremos pronto lo que son) que no construye nada a pesar de que FUNCIÓN CONSTRUCTORA sea, por convención entre los programadores, su rimbombante nombre de guerra, lo que nos puede llevar a error.  Lo que que hace realmente, por lo que podemos deducir a partir del nombre, init, abreviatura de initialize, en inglés, es que INICIALIZA algo, es decir, hace que algo empiece a funcionar, a servir para algo, a ser útil. ¡Vaya! ¿Y qué es lo que inicializa? Inicializa las instancias, es decir, todas las propiedades de los objetos que construyamos (instanciemos) dentro de la clase. Dicho de otro modo: gracias a __init__(self), cualquier propiedad de cualquier objeto que instanciemos podrá realizar su trabajo y le podremos asignar lindamente los valores iniciales o de partida que estimemos oportunos y, sí, iniciales o de partida porque luego, si queremos, podremos modificarlos a lo largo del código: nos podemos comprar un coche que viene inicializado de fábrica de color (propiedad) rojo (valor) y al cabo de varios  años, repintarlo de color (propiedad) verde (nuevo valor de la propiedad). Este es el cometido básico de __init__(self): inicializar las propiedades de cualquier objeto que podamos instanciar dentro de la clase donde hayamos definido (con def) el método especial __init__(self).
Ciertamente, como comprobamos en el ejemplo, también puede llevar funciones sencillas, normalmente, predefinidas, como la propia función print().



Para ser exactos, la "verdadera" FUNCIÓN CONSTRUCTORA es __new__(), otro método especial más (los podemos reconocer fácilmente por el doble guión bajo __ antes y después del nombre) como __init__(self), quien CREA un objeto nuevo de su clase y lo devuelve. 
A este objeto nuevo recién creado ya le podemos instanciar propiedades a través de __init__(self). Esto significa que cuando modelizamos una clase y construimos (instanciamos) un objeto primero actúa el método especial __new__(), como quien dice, en segundo término, entre bambalinas, y después lo hace el método especial __init__(self), que nos permite asignarle propiedades al mismo.
Con __new__() creamos la estructura, el chasis, el armazón del coche, y con __init__(self) le proporcionamos propiedades, como la marca, el modelo, el color, con sus correspondientes valores (marca=Toyota, modelo=Yoigo, color=azul; marca=Kia, modelo=Picanto, color=rojo; etc.).



El método especial __new__() se ejecuta automáticamente, es decir, no tenemos que llamarlo para instanciar ningún objeto: Python, muy amablemente por su parte, ya lo llama por nosotros. Además, el método especial __new__() debe llevar una autorreferencia como primer argumento que apunta a la propia clase desde la que construye o instancia objetos y que, por convención, igual que sucede con self, de la que hablaremos en breve, es cls, abreviatura de class, en referencia a la class, la clase. Así, nuestro método especial tiene realmente la siguiente sintaxis: __new__(cls). Esto significa que si modelizamos una clase que llamaremos, por ejemplo,  Vehiculos, al instanciar en ella un objeto coche, este nuevo objeto coche pertenecerá a la clase Vehiculos y no a la clase Animales, Paises o TablaPeriodica que podamos haber modelizado antes en cualquier otra parte de nuestro código.
Lo vemos en un ejemplo práctico:



Cuando __new__(cls) retorna una instancia u objeto de la clase a la que autorreferencia con el parámetro cls, en el ejemplo superior, Vehiculo, el método especial __init__(self) se ejecuta de manera implícita porque ya existe el objeto, c1 en el ejemplo, y al que puede asignar propiedades, en el ejemplo, marca, modelo y color, con sus respectivos valores asignados, siendo entonces cuando el parámetro self puede ya apuntar al objeto y cobra todo su sentido.


Esta relación de mutua dependencia y automatismo hace que en otros lenguajes de programación, el método constructor y el método inicializador se "fundan" en un sólo y único método.
Resumiendo mucho, en Python coexisten una FUNCIÓN CONSTRUCTORA (__new__(cls)) y una FUNCIÓN INICIALIZADORA (__init__(self)), ambas como método especial de Python, donde la primera se encarga de crear/instanciar un objeto a partir de la clase a la la que hace referencia con el parámetro cls y que, salvo en ocasiones muy especiales, no suele ser utilizada por el programador, siendo la más frecuente la segunda, ya que permite instanciar propiedades de inicio al objeto recién creado por el constructor y a la que la FUNCIÓN INICIALIZADORA refiere a través del parámetro self
Este parámetro self es OBLIGATORIO, y lo veremos no sólo cuando instanciamos propiedades en  __init__(self) sino cuando instanciemos nuevas propiedades a lo largo del código (que suele ser lo más habitual y práctico) y en la zona de argumentos de los métodos que instanciemos dentro de la clase como primer argumento, como veremos más adelante.
Es posible perfectamente que el inicializador lleve más argumentos a parte del obligatorio self en su zona de parámetros, en cuyo caso, nos serán solicitados en el momento justo en que vayamos a instanciar el objeto.



      CON ESTO DAMOS POR CONCLUIDA, AL MENOS AQUÍ, NUESTRA EXPLICACIÓN SOBRE EL MÉTODO CONSTRUCTOR __new__(self) Y EL MÉTODO INICIALIZADOR __init__(self), SALVO ALGUNOS ASPECTOS QUE IREMOS VIENDO A LO LARGO DE ESTE MANUAL. LO QUE ACABAMOS DE EXPONER ES LO BÁSICO Y NECESARIO PARA ENTENDER AMBOS. DE TODAS FORMAS VOLVEREMOS SOBRE ELLOS CUANDO ENTREMOS A EXPLICAR LOS MÉTODOS ESPECIALES DE PYTHON EN UNA PRÓXIMA PÁGINA DE ESTE MANUAL.

      ESTE ÍNCLITO PARÁMETRO self, ADEMÁS DE SER UN PARÁMETRO OBLIGATORIO, DEBERÁ SER SIEMPRE EL PRIMERO DE TODOS LOS QUE TENGAMOS INTENCIÓN DE INSERTAR  EN LA ZONA DE PARÁMETROS DE CUALQUIER MÉTODO DEFINIDO DENTRO DE LA CLASE, INCLUIDO __init__(self): def Nombre_metodo(self, param1, param2, ...):. CIERTAMENTE, EL USO OBLIGATORIO DEL PARÁMETRO self NOS OBLIGA A "ENGROSAR" EL CUERPO DEL CÓDIGO PERO, A CAMBIO, NOS ADVERTIRÁ SIEMPRE DE CARA A DESARROLLAR DE MANERA CORRECTA NUESTRO TRABAJO COMO PROGRAMADORES, DE QUE ESTAMOS ACCEDIENDO DEBIDAMENTE A UNA propiedad O método DE UN OBJETO INSTANCIADO DESDE TAL O CUAL CLASE. ¡AY MI CABECITA!




NUESTRO PÁJARO EMBLEMÁTICO: EL CANARIO. 



2 comentarios:

  1. Excelente trabajo y explicación. Muchas gracias, con esta entrada pude entender mucho mejor la lógica detrás del self.

    ResponderEliminar
    Respuestas
    1. Muchísimas gracias por su comentario. Un placer. Saludos.

      Eliminar