martes, 12 de abril de 2016

T2. LA SENTENCIA DEL. FUE BONITO MIENTRAS DURÓ

TAJINASTES BLANCOS COMENZANDO A FLORECER, CAMINO RURAL DE CHIRCHE, GUÍA DE ISORA, OESTE DE TENERIFE.
      Como podemos suponer con sólo conocer un poco de informática, una "herramienta" que empiece (o directamente se llame) 'DEL' no puede referenciar otra cosa que "delete", en castellano, "borrar, eliminar". Y efectivamente, eso es lo que hace en Python la sentencia del. Sin embargo, no lo olvidemos, la singularidad de las listas y sus elementos componentes. Recordemos que estos no son los objetos en sí sino referencias a los propios objetos, algo así como copias exactas de sí mismas.
Como consecuencia de ello, la aplicación de la sentencia del a los elementos de una lista lo que realmente provoca es, primero, la desvinculación entre la referencia al objeto x y el objeto x en sí, la eliminación de la referencia y la remisión del objeto tal cual a la basura donde éste se recoge y almacena, siempre y cuando no existan otras referencias de objeto que lo apunten explícitamente.




La sentencia del mantiene concomitancias claras con el método list.clear() cuando emplea una rebanada vacía, pues ambas eliminan todos los ítems de una lista devolviendo un resultado similar. Igualmente mantiene esa misma afinidad con los métodos list.remove() cuando el ítem es señalado por el índice, y con list.pop() y list.pop(índice), aunque no se nos devuelve el ítem eliminado.


LAS REFERENCIAS EN PYTHON



Consideramos llegados a este punto que conviene ahondar un poco más en un aspecto fundamental de Python: la referencia. Y para hacerlo nos vendría bien comenzar con un ejemplo. Veamos:



Fijémonos que a pesar de que tenemos dos variables  distintas, lista1 y lista2, al modificar el valor de una de ellas, en virtud de su asignación por el operador =, la mentada modificación se refleja automáticamente en la segunda. De hecho, la llamada al verificador is de identidad bajo la sintaxis lista1 is lista2 nos devuelve el booleano True. Pero la pregunta que podríamos hacernos es: ¿Por qué esto es así? Si lista1 y lista2 eran las mismas cuando se asignaron, lo cual es perfectamente comprensible, ¿por qué ahora, a posteriori, cuando hemos modificado una de ellas y no hemos vuelto a reasignarlas, la secuencia de datos que contienen ambas son idénticas? ¿Por qué ha cambiado lista2 al modificar lista1 si no las hemos vuelto a asignar desde que modificamos lista1?
Pues la respuesta está en la referencia ¿ein?
Lo que realmente ocurre es que, no nos olvidemos de ello, para Python todo cuanto existe en el mundo mundial, todo cuanto ha sido, es y será son objetos (objects), lo que tendrá su importancia cuando nos internemos en la Programación Orientada  a Objetos, POO,Cuando hemos procedido a modificar la lista1 sustituyendo un objeto por otro, hemos creado un objeto nuevo: el objeto 30, con el tipo de dato int, nuestro recién alumbrado objeto reside en algún lugar recóndito, frío, oscuro y tenebroso de la memoria de Python, eso sí, con un identificador, id, único y exclusivo suyo que lo diferencia y distingue de cualquier otro objeto almacenado en ella. Contamos con la función id() para conocer el identificador (la matrícula, el DNI, el número de serie, o como nos lo queramos imaginar) de nuestro objeto.



BEJEQUE CRECIENDO SOBRE UNA GRIETA EN LA ROCA, BARRANCOS DE GÜÍMAR, CENTRO SUR DE TENERIFE.

Lo singular del caso está en que si, por ejemplo, creáramos una variable a y le asignamos el valor de lista[4], esto es, 30, dicha variable a no sería más que una referencia a ese objeto en sí: una especie de puntero láser imaginario que dibuja un inquietante puntito rojo sobre el corazón mismo del objeto 30.



Y si ahora asignáramos a una variable b el valor de a obtendríamos el mismo id para ambas: a y b están en la misma dirección de memoria y no en direcciones distintas como sucede en otros lenguajes.
Si modificamos el valor de a, la variable continúa ocupando el mismo espacio de memoria, con una id distinta pues contamos con un objeto nuevo que requiere de su propio identificador exclusivo, y lo único que ha cambiado es su valor.



Aprovechando la condición mutable de las listas, contando con una misma lista de datos con dos variables distintas, si modificáramos directamente el objeto lista mediante la sustitución, por ejemplo, de uno cualquiera de sus ítems, comprobaremos que tanto una como otra variable continúan referenciando al mismo objeto lista. En el ejemplo anterior, con a = 45, que es un objeto de tipo int, se creó un nuevo objeto que, como era de prever, tendría una id distinta a la de b, pero en el caso de la lista que nos ocupa ahora, podemos modificar el objeto "sobre la marcha", haciendo uso de la condición mutable que poseen las listas.



Otra cosa muy distinta sería si hubiéramos procedido de la siguiente manera:



La diferencia estriba en que al modificar la lista mediante una asignación nueva que contiene una secuencia de datos con un ítem modificado, 12 en lugar de 8, con respecto al original, hemos creado en 'pares' un objeto list distinto al objeto list 'divx2' y, en consecuencia, ambas variables  apuntan a objetos list diferentes, mientras que en el caso anterior, hemos modificado el objeto int 8 convirtiéndolo en el nuevo objeto int 12 sin haber tocado para nada las listas salvo por la llamada al índice (pares[3] = 12): hemos modificado directamente la lista `pares`sobre la marcha, actuando sobre ella. De esta manera, al no haber tocado a las listas en sí, aunque sí a sus ítems para modificarlos, ambos objetos devuelven el mismo resultado, son idénticos y responden al mismo id.
Vamos a considerar a continuación el siguiente caso:



Lo vemos claro, ¿verdad? Pongamos nuestra atención ahora en lo que ocurre cuando efectuamos una operación sobre la marcha actuando de manera directa sobre el objeto.



Lo que obtenemos como resultado es: lista1 = None.
Sucede con todos los ejemplos que hemos estudiado en este apartado que Python fue diseñado en su génesis de esta manera tan singular, tan "en la orilla opuesta" de lo que constituye el protocolo habitual en tantos lenguajes de programación, de tal modo que una operación, fuera cual fuere, si modificamos in situ un objeto, Python no devuelve ningún valor en tanto que contiene implícitamente el valor None.



Es por esta razón que la variable y no muestra ningún valor cuando se le llama, dado que su valor es None. El método list.reverse() ha funcionado en x pero no ha ocurrido igual en y. De esta manera, Python impide que se genere un previsible caos cuando codificamos operaciones que combinen modificaciones in situ con objetos que se crean nuevos, manteniendo un nivel adecuado de coherencia con respecto a la gestión de la memoria.
En el caso anterior obtenemos un resultado deseable procediendo de la forma siguiente:



Esto es, primero ejecutamos la inversión y luego realizamos la asignación y = x, después de que x haya sido modificada.


      Para finalizar, regresando a nuestra sentencia del que encabeza esta entrada y de acuerdo a todo lo anterior, tenemos que aclarar que la mencionada sentencia, considerada como "destructor" de Python, sólo elimina la referencia al objeto que se aloja en un espacio de memoria interna concreto pero no exactamente al objeto en sí, salvo en el caso de que tan sólo exista una única referencia al objeto, en cuyo caso, el "recolector de basura" que tiene "asociado", al no poder ya nosotros por nuestra cuenta como programadores llamar al objeto porque carece de "nombre/referencia" que lo señale, elimina (ahora sí) al objeto y libera su espacio de memoria poniéndolo a disposición de un nuevo objeto que queramos almacenar en él, eso sí, con una nueva referencia.
Este proceso lo veremos mejor cuando estudiemos los MÉTODOS ESPECIALES en la Programación Orientada a Objetos. ¡Uy! Creo que me están llamando para sacarme de paseo... ¡Hasta otra!

LADERAS DE TENO EN LA VECINDAD DEL BARRANCO DE GUERGUES, NOROESTE DE TENERIFE.




lunes, 4 de abril de 2016

T1. LA FUNCIÓN RANGE(). HASTA AQUÍ HEMOS LLEGADO.

MUSGO SOBRE LAS PIEDRAS Y HOJARASCA EN EL SOTOBOSQUE DEL MONTE DEL AGUA, NOROESTE DE TENERIFE.

      Esta nueva función de Python nos va a caer simpática. Ya veremos que sí. Se trata de una función integrada que, como su nombre sugiere, determina un rango, delimita un "terreno", un espacio, una porción de algo, por ejemplo, de una lista donde aplicamos una expresión concreta. Precisamente por eso, la función range() incorpora como argumentos un índice de inicio y un índice final que sirve, como nos podemos imaginar, para  encapsular un espacio específico a partir de otro dado, casi lo mismo que consiguen las rebanadas o slices con una string, por ejemplo.


Una FUNCIÓN INTEGRADA puede definirse como aquélla que no requiere ser llamada o invocada desde módulo alguno, en tanto que es accesible por defecto, esto es, que su carga y uso se realiza automáticamente lo que las emparenta con las funciones preconstruidas built-in.
Tengamos en cuenta que la función range() no funciona por sí sola sino que que debe ir asociada a un bucle en for/in para poder desplegar todos sus encantos. Veamos un ejemplo:



En 1. construimos un objeto de tipo de dato list para albergar una colección de números enteros.
Ya en 2. establecemos el bucle for/in para la lista. Hagamos hincapié en que no escribimos la fórmula incluyendo el nombre de la lista, lista_de_números, sino que escribimos directamente la función range(), dado que el incluirla en la fórmula como 'for ítem in lista_de_números in range(5, 10):', por ejemplo, lanzaría una excepción advirtiéndonos de un error de tipo booleano. Esto se debe a que presentada la lista, Python interpreta que el segundo 'in' es un operador de verificación o pertenencia sobre la función range(), indagando sobre sí la lista está presente en range() o no lo que, como podemos deducir, no tiene sentido gramatical.
En 3. Python nos devuelve el resultado iterado, en formato de columna, que muestra la devolución por defecto de la función range() como un ITERADOR o ITERABLE, en tanto que la función permite iterar por todos y cada uno de los elementos que componen la acotación, de principio a fin y de izquierda a derecha. Fijémonos en que al igual que sucede con las rebanadas, el índice de inicio se incluye en el resultado pero el índice final es n-1. Tengámoslo presente.
En 4. añadimos un entero 'z' para que Python nos devuelva el resultado de dos en dos, con lo que hemos obtenido los impares que van del 1 al 10.
En 5. añadimos un entero 'z' para que Python nos devuelva el resultado de dos en dos, con lo que hemos obtenido los pares que van del 10 al 21.
Contamos con una sintaxis singular aprovechando la cualidad de la función range() de recorrer una lista completa establecidos un índice de inicio y otro final, que nos permite realizar una acción sobre todos y cada uno de los ítems de una lista dada. Para hacerlo no tenemos más que pasar como argumento de la función otra función, una de nuestras viejas conocidas. ¿Cuál? La función len() que, repasemos, devuelve la longitud de una colección de datos. Veámoslo en el ejemplo que sigue:


En 1., una vez tenemos la lista, introducimos la sintaxis anteriormente mencionada.
A continuación, en 2., establecemos una llamada a los índices. Al colocar 'item' de la lista estamos diciendo que lo que vayamos a hacer se va a aplicar a todos y a cada uno de los elementos de la lista, gracias a que hemos pasado advirtiendo de ello la función len() como argumento de range().  ¿Y qué es lo que le vamos a hacer a cada uno de estos elementos? Pues  lo que hemos escrito justo a la derecha: un operador de incremento (se utiliza muy a menudo en programación) que sumará 1 a cada ítem, como podemos comprobar en la devolución.
En 3. optamos por incrementar en 10. En 4. por restar o sustraer 1. Y en 5. por multiplicar por 7 cada elemento de nuestra lista. Con este último ejemplo, podríamos construir perfectamente una sencilla tabla de multiplicar.

FLOR DE LA CERRAJA, PLANTA CARACTERÍSTICA DEL BOSQUE HÚMEDO DE TENERIFE.

Fijémonos a continuación en el siguiente ejemplo:



Observemos que en 1., como no nos es posible efectuar la operación de incremento de 1 para todos y cada uno de los ítems, ya que se nos lanza una excepción que nos advierte que el índice que hemos pasado, x, está fuera de rango.
En 2. planteamos la alternativa lógica a la propuesta de sintaxis que hemos visto más arriba con len(), y que pasa por proponer el range() completo, del primero al último de los índices, o lo que es lo mismo:
                                                                      range(índice de inicio, índice final) = len(lista)

Percatémonos de que para que el asunto funcione, le hemos pasado como índice de inicio el número inmediatamente anterior (o menor) que el primero de nuestra lista que, siendo 1, corresponde al 0; mientras que como índice final hemos puesto el último elemento de la lista, 10.
En 3. vemos cómo podemos llevar a cabo la acción seleccionada sobre un grupo de elementos concreto recurriendo al acotamiento que pasamos como argumento de range().


   
      A CONTINUACIÓN MOSTRAMOS UNA TABLA QUE NOS SERÁ SUMAMENTE ÚTIL PARA REALIZAR OPERACIONES ARITMÉTICAS APLICABLES AL TOTAL DE ÍTEMS DE UNA COLECCIÓN.






Es importante tener en cuenta, como acabamos de ver, la dependencia singular, si podemos decirlo así, entre las listas y el bucle for/in para realizar un alto número de operaciones. entraremos en ello con mucha más profundidad cuando tratemos el bucle for/in.
Existe otra forma...mmm...¿cómo decirlo?... más "amable", "elegante" y que consiste en aplicarle a una lista dada un recorrido con la función range() y es recurriendo, de nuevo, a las técnicas de troceado. Pero eso sí, con una rebanada un tanto especial que pasa por incluir dos veces los dos puntos: [x::y], donde x es el índice de inicio e y el valor que se le suma a x dentro de len() de una lista dada. Miremos el ejemplo:



En 1. construimos un bloque de código cuya sintaxis ya conocemos y que consiste en aplicarle a una lista dada un recorrido con la función range() (hubiéramos podido sustituir los datos que pasamos como argumentos por len()) de principio a fin y contando de dos en dos. La aplicación de esta función nos devuelve un resultado iterado de ítems de 1 al 9.
Pero en 2. tenemos que la lista original ha sido transformada en una nueva lista que contiene los mismos valores numéricos que obtuvimos en 1.
En 3. creamos una nueva lista 'k' conformada por strings. Sin embargo, al aplicarle la función iteradora range() obtenemos un resultado similar a 1. ¿Por qué sucede esto? Porque iterará sólo sus índices y no los valores que representan. Para conseguirlo tenemos que escribir:



Finalmente, en 4. observamos cómo obtenemos una nueva lista 'k' con los string que buscábamos.
Terminamos esta entrada dedicada a la función range() aportando unos pocos ejemplos más que nos permitirán comprender mejor las posibilidades y la plasticidad de esta función integrada de Python.





Podríamos necesitar que, en vez de contar hacia a delante, esto es, por ejemplo, de 1 a 10, de menor a mayor, hacerlo al revés, es decir, de 10 a 1, de mayor a menor. Para conseguirlo, tan sólo necesitamos dos cosas: pasar en negativo el primer valor del argumento (nos basta con añadir delante el signo menos, -), que es desde donde empezará a contar, mientras que el segundo lo podemos dejar también con el signo  menos, -, o sin signo dependiendo hasta dónde queramos contar; e incluir la función nativa abs(), de absolute, que como su nombre indica, nos devuelve valores absolutos, sin signo aplicado sobre cada elemento del iterable. Vamos a verlo con ejemplos:



Para finalizar, tengamos presente que la función range() SÓLO admite como argumentos números enteros. No admite ningún otro tipo de dato distinto. De no ser así se lanzará una excepción, un error, desde el intérprete de Python.

AMANECER SOBRE EL TEIDE, CON SU SOMBRA PROYECTÁNDOSE SOBRE EL PICO VIEJO Y TODO A LO LARGO DE LA ISLA.