jueves, 27 de octubre de 2022

Python. Archivos.

Python. Escritura de archivos

Ahora que ya sabemos algo (muy poco aun) sobre la lectura de archivos externos es el momento de aprender cómo crearlos y cómo escribir en ellos. Con estos conocimientos estaremos en disposición para crear nuestros primeros programas plenamente funcionales en Python. Aun no serán gran cosa y se les podrá calificar de "poco atractivos" por emplear Símbolo del sistema como interfaz, pero tendrán plena funcionalidad.


El acceso (lectura) a archivos externos incrementa la cantidad de información a disposición de nuestro algoritmo, pero no da la permanencia del resultado que podemos obtener de su aplicación, lo que limita su funcionalidad. Para ello necesitamos crear archivos y ser capaces de escribir en ellos. De cómo lograr estos objetivos nos ocuparemos en esta entrada.

De las instrucciones que explicamos en la entrada anterior, tres nos permiten crear ficheros: xa. Cada una de ellas tiene un comportamiento particular, por lo que debemos conocer cómo actúan para seleccionar aquella que mejor se adapte a nuestro objetivo. 

  • La opción x (create) parece, en principio, ser la más apropiada, pero tiene un inconveniente: si el archivo que pretendemos crear ya existe, devuelve error, por lo que es necesario asegurar que dicho archivo no existe. La función isfile() nos puede ayudar ya que devuelve un valor booleano (True/False) que nos permite responder a esta eventualidad.
  • La opción w crea el archivo si éste no existe y no devuelve error, pero borrar su contenido y lo sustituye por el nuevo ya que sitúa el cursor al inicio del archivo,.
  • Por último, la opción a funciona igual que la anterior y tiene la ventaja de no alterar el contenido previo del archivo en caso de que éste exista, ya que sitúa el cursor al final del archivo.
Sabiendo lo anterior podemos optar por la opción que mejor se ajusta a nuestras necesidades. Por ejemplo:
  • Si la prioridad es crear un nuevo archivo conociendo a ciencia cierta que no ha sido creado con anterioridad, cualquiera de las tres opciones es válida. x parece la más específica, pero en realidad las tres tienen, en este caso, em mismo comportamiento, ya que la posición del cursor dentro del archivo (conocimiento necesario para proceder a escribir en él) siempre será el inicio del mismo.
NOTA. Es posible que esta sea la causa de que en algunos manuales no se haga mención de la opción x, entendiéndola redundante en caso de inexistencia del archivo y potencialmente negativa en caso de duda al respecto. Esta es una observación personal no contrastada, por lo que ha de tomarse con reservas.
  • Si lo que deseamos es continuar escribiendo en un archivo ya creado, la mejor opción es a, ya que nos garantiza que se respeta el contenido previo y que el nuevo se escribe a continuación. Será una opción ideal para, por ejemplo, crear expedientes acumulativos, como el seguimiento de actuaciones de determinado tipo.
  • Si nuestro cometido es mantener actualizado y sin redundancias un determinado fichero externo, siendo irrelevante o contraproducente la acumulación de datos, la mejor opción es w, ya que nos garantiza que no se producirá error y que el archivo siempre estará actualizado sin que usos precedentes interfieran en el objetivo que se pretende satisfacer.
No obstante, dada la importancia que tiene en el proceso de escritura (y lectura) conocer la posición que ocupa el cursor interno, Python dispone de funciones específicas para este fin. Estas opciones no van a ser tratadas en esta entrada, ya que tiene carácter introductorio, pero las explicaré cuando el objetivo de aprendizaje (y uso práctico) lo haga necesario. Por el momento, vamos a presentar algunos script básicos de uso de las opciones anteriores y de escritura en archivos.

El siguiente script crear un archivo usando x, escribimos en él mediante la función write() y lo cierra. Una segunda fase del script (en realidad se puede considerar un script independiente) accede al archivo creado (mediante r) y lo lee. Si ejecutamos dos veces este script, la segunda (y sucesivas) dará error ya que, como dije antes, devuelve error en caso de que el archivo ya exista.

#Creación y escritura en nuevo fichero .txt
print("Creación de fichero y escritura -------------------------------")
fcrear=open("pruebaCrear.txt","x")
fcrear.write("Primer fichero creado mediante python\n")
fcrear.write("Contiene texto simple creado mediante método .write\n")
fcrear.close()
print("#########  Fichero creado  ##############")
#Acceso para lectura
flectura=open("pruebaCrear.txt","r")
texto=flectura.read()
flectura.close()
print(texto)

#Control de cierre del fichero
print("Fin de archivo. Pulsa INTRO para cerrar.")
input()

 Además de este script te doy acceso a dos mas: uno que emplea la opción w y otro que hace uso de la opción a para que puedas comparar su funcionamiento y estructura. Los tres desarrollan el proceso de creación y posterior lectura, además del control del cierre. Los requisitos de ubicación de estos tres archivos .py son los mismos que te indiqué en la entrada anterior.

Sin perjuicio de lo mucho que nos queda por aprender sobre el trabajo con archivos externos en Python, y con las limitaciones de funcionamiento y de tipo "estético" que esta opción presenta, en este momento disponemos de recursos para crear en Python algoritmos sencillos y un tanto "sosos", pero perfectamente funcionales, similares en esto a los que sabemos crear en OOo Basic. La forma de demostrar esta afirmación es llevándola a la práctica. De ello nos ocuparemos en breve.

miércoles, 26 de octubre de 2022

Python. Archivos

Python. Lectura de archivos

Hasta este momento todo lo que hemos podido hacer desde Python ha sido efímero por estar basado en los datos introducidos directamente en el algoritmo y em los introducidos por el usuario o usuaria. En estas entradas vamos a aprender a incrementar los datos mediante lectura de archivos externos y crear archivos para guardar la información resultante de la ejecución del algoritmo. Con ello buscamos ganar en potencia y funcionalidad.

Empezaremos por aprendiendo los comandos básicos de manejo de ficheros, que cuatro comandos de manejo de ficheros, dos para especificar el tipo de fichero (texto o binario) y uno complementario que comprende los dos modos principales (lectura y escritura.

  • "r" (read) permite abrir un fichero para lectura, aunque si este fichero no existe genera error, por lo que tenderemos que saber de antemano que el fichero sí existe. Es la opción que utiliza Python por defecto (si no se especifica nada) y resulta muy útil cuando queremos trabajar con ficheros ya creados. Un ejemplo de uso es la presentación de un texto explicativo sobre el objetivo del algoritmo y su manejo. De este modo liberamos el algoritmo de información complementaria y podemos modificar ésta sin necesidad de modificar el propio algoritmo.
  • "w" (write) abre un fichero para escritura truncando la información que pueda contener dicho fichero, esto es, la borra. Si el fichero no existe, lo crea, por lo que es útil para crear nuevos archivos y cuando trabajamos con archivos externos que interesa modificar sin acumular versiones diferentes del mismo.
  • "x" (create) crea un fichero para escribir en él. Si el fichero ya existe devuelve error, por lo que, al igual que "r", necesitamos saber previamente en qué situación nos encontramos. Es útil para generar nuevos archivos externos en los que guardar la información derivada del procesamiento de nuestro algoritmo, siempre y cuando sea necesario crear nuevos ficheros por proceso.
  • "a" (append) abre un fichero ya creado para escribir en él situando el cursor al final de su contenido, por lo que no se altera el contenido previo. Esto hace que sea útil cuando queremos completar la información previamente guardada.
Los dos comandos relativos al tipo de contenido (texto o binario) son los siguientes:
  • "b" (binary) abre el fichero en modo binario (0 y 1 en lugar de texto plano), como fotografías, archivos ejecutables, ficheros de LO-Writer o MO-Word, por ejemplo. 
  • "t" (text-mode) abre el fichero en modo texto plano.
Finalmente el signo "+" abre el archivo en modo lectura y escritura simultáneamente.

El modo de acceder/crear un fichero pasa por definir una variable (ej fichero) y asignar a ella...
  • la función de apertura: open()
  • el directorio y nombre del fichero: open("./NombreFichero.txt")
  • el modo de acceso: open("./NombreFichero.txt", "rt")
  • y la codificación:  open("./NombreFichero.txt", "rt", encoding="utf-8")
Y obtenemos con resultado la instrucción...

fichero =  open("./NombreFichero.txt", "rt", encoding="utf-8")

... siempre que nuestro objetivo sea abrir en modo texto un fichero llamado NombreFichero.text, ubicado en el mismo directorio en que se encuentra el archivo python desde el que se le llama...

NOTA. En este caso estaríamos definiendo una ruta relativa, donde también admite como dirección o ruta NombreFichero.txt. Si deseáramos utilizar una ruta absoluta deberíamos escribirla completa:

C:\Users\NombreUsuario\Desktop\BLOG\ORIENTACIONyPROGRAMACION\python\NombreFichero.txt

... empleando la codificación utf-8, que permite el reconocimiento de los caracteres propios del idioma (ñ, tildes...)

NOTA. La expresión encoding="utf-8" puede dar error, además de ser innecesaria, según comprobación propia en Windows10 sobre archivo creado con Bloc de notas e intérprete Python 3.10.0. En ese caso (del que no he encontrado explicación) probar eliminando encoding="utf-8".

Una vez abierto el fichero de tipo texto plano (existente en el directorio indicado) en modo lectura, asignamos el resultado de la función de lectura de su  contenido (fichero.read()) a una variable (texto=fichero.read()) y (por ejemplo) posteriormente imprimimos en pantalla el contenido asignando a la dirección de memoria que se identifica con dicha variable (print(texto)). Finalmente cerramos el acceso al fichero mediante la función close()fichero.close()

En el archivo ficheros1.py te dejo el código antes explicado junto con otros cuatro script que se explican mediante comentarios. Cada uno de ellos realiza una función diferente, aunque similar que te da idea de las opciones disponibles en cuanto a la lectura de archivos. Estos script están comentados (comentario multilínea) por lo que están inactivos. Deberás eliminar esos comentarios (comillas triples) para probar su funcionamiento. También te dejo acceso al archivo .txt utilizado en esta práctica, aunque puedes sustituirlo por el que tu prefieras siempre que mantengas el nombre actual. Ambos archivos (.py y .txt) deberás incluirlos dentro de una carpeta o subdirectorio creado en tu escritorio.

NOTA. La ubicación propuesta es orientativa, ya que siempre que ambos estén ubicados en la misma carpeta, cualquier otra opción será válida dado que en los script se usan rutas relativas.

viernes, 21 de octubre de 2022

Evaluación. Funciones ejecutivas.

Escala ENFEN

Revisando materiales condenados al olvido me he encontrado con documentos e instrumentos relativos a la evaluación de las funciones ejecutivas (FE), más concretamente sobre ENFEN (Portellano, Martínez y Zumárraga).

Por si a alguien le pueden ser de utilidad, presento en esta entrada los materiales a los que me refiero al inicio sin más pretensión.

Considero la evaluación de las funciones ejecutivas en el ámbito escolar como parte del segundo nivel de intervención en el modelo de evaluación psicopedagógica, aunque también tiene cabida como parte del tercer nivel. 

Por actualizar el significado que doy a estos niveles, recordar que el primer nivel se basa en la evaluación contextual/grupal curricular y socio-afectiva, orientada a la detección temprana y a la identificación de dificultades de aprendizaje y de desarrollo socio-emocional. El segundo nivel se concreta como evaluación individual, guiada por hipótesis derivadas de los resultados de la fase anterior y orientada a la detección de causas específicas de las posibles dificultades y, en caso positivo, fundamentar la derivación al clínico (si ésta se considerara pertinente). El tercer nivel se concreta como colaboración con el clínico y a demanda de éste.

El uso de ENFEN como conjunto de pruebas sólo se contempla como parte del tercer nivel, pero el uso de pruebas concretas es perfectamente coherente con el segundo nivel, empleándolas como instrumentos para la confirmación de hipótesis sobre relaciones causales entre las dificultades observadas y niveles de rendimiento en tareas/pruebas específicas orientadas a la evaluación de las funciones ejecutivas (en este caso).

La información que aporta TEA Ediciones confirma ENFEN como recurso "...para evaluar el desarrollo madurativo global de los niños que incide especialmente en la evaluación de las Funciones Ejecutivas (FE) del cerebro." e insiste en que el tipo de respuesta ante la confirmación de alteración/déficit es de naturales terapéutica ("intervención neuropsicológica"), lo que remite a la intervención del clínico y se aleja de la educativa/escolar. 

Nada que objetar al respecto, salvo la dificultad para diferenciar en la práctica ambas intervenciones una vez puestos a concretar las actividades a desarrollar: la intervención educativa no está tan alejada de las actividades que se propone que desarrolle el clínico, aunque los objetivos teóricos sean diferentes, las prácticas no lo son tanto, lo que permite establecer acuerdos de colaboración entre ambas intervenciones, ya que los beneficios inciden en el niño o niña, que es la misma persona, y de ellos se derivan mejoras (también, y es de esperar que así sea) en el proceso de aprendizaje y en la conducta.

Esta batería está compuesta por cuatro pruebas (Fluidez, Senderos, Anillas e Interferencia) que miden diferentes componentes de las FE. En este documento se recogen un listado suficientemente exhaustivo de todas ellas y en este otro se identifican las dificultades más comunes en las FE. Sobre estas cuestiones te podría interesar también  este otro documento, más descriptivo.

Como recurso de evaluación comercializado, actualmente (21/10/2022) tiene un coste de 214,36 €, IVA incluido (TEA Ediciones) y supone un tiempo de aplicación (individual  y teórico) de 20 minutos si se aplican todas las pruebas, aunque no es necesario hacerlo, ya que cada test es independiente y no existe una puntuación que resuman el conjunto de la batería. Desde luego no lo será dentro del planteamiento indicado arriba sobre niveles de intervención, salvo como parte del tercer nivel, que es el menos específicamente propio de un SEO.

Conocer la opinión del autor principal puede ser un buen primer acercamiento a la prueba, pero insuficiente para un conocimiento en profundidad del significado de las funciones ejecutivas y su evaluación. Por ello me parece que estos artículos te pueden aportar una visión más amplia del tema, aunque no sean recientes y algunos se centren en la relación entre el déficit en FE y el TDAH:

Tres más son las aportaciones que puedo hacerte respecto a este tema: 

Empezando a la inversa, el documento sobre intervención es útil en la medida en que se focaliza en alumnado con TDAH, por lo que no constituye referencia para el abordaje de alteraciones en FE en general. Por ejemplo, es de escasa utilidad para abordar los déficits en FE que puede presentar el alumnado TEA. Los modelos de análisis lo son fundamentalmente de carácter cuantitativo, por lo que se deberán complementar con análisis de carácter cualitativo y de funcionamiento en contexto (si se prefiere, obedecen más a lo que sería una descripción de resultados para informe de derivación a clínico, debiendo, en ese caso, ir acompañados con una tabla de resultados cuantitativos. Y, finalmente, los soportes Calc, que proporcionan esa tabla de resultados, están pensados para facilitar el análisis de datos, pero no suplen la realización de valoraciones ni la elaboración de un informe-base.

Me reservo la posibilidad de volver sobre esta prueba y mejorar los soportes aportados. Pero no hoy. 😉

lunes, 17 de octubre de 2022

Python. Interface.

Consola (cmd)

Llegados a este punto, y aunque es mucho lo que aun nos queda por aprender sobre los temas tratados, parece oportuno dar un salto cualitativo en el modo de proseguir con este proceso de aprendizaje. También lo es (oportuno) empezar a desarrollar algoritmos orientados a un uso práctico y no sólo con el propósito de aprendizaje y práctica, aunque ambos siguen siendo objetivos fundamentales y prácticas necesarias. Desde esta nueva perspectiva debemos empezar a correr nuestros algoritmos desde fuera del IDLE, ya que es éste el modo en que se espera sean usados.


El modo primario y directo en el que esto se va a producir es a través de la consola de comandos (cmd), ya que aun no hemos aprendido nada sobre interfaces gráficas (no así en OOo Basic, donde estamos empezando a dar nuestros primeros pasos). No es éste un modo muy elegante de funcionar un programa actual, aunque tampoco extraño, ya que sigue siendo un formato plenamente vigente.

La estética y "modernidad" no es precisamente el primer problema que debemos abordar, sino otra mucho más básica: lo efímero de la presencia activa del programa. 

Hasta este momento todos los ejercicios que hemos realizado los hemos creado y probado en y desde el IDLE de Python, pero no es este el modo esperado y deseado de funcionamiento. Por ello, y aun siguiendo en modo consola, necesitamos hacer que nuestros "programas" funcionen al margen del IDLE si queremos que sean realmente funcionales. Y existe una forma simple y directa de hacerlo: es suficiente con hacer doble clic sobre el archivo .py para que se lance en el entorno de la cmd. Si el algoritmo no contiene errores y el intérprete Python está correctamente instalado (algo que presuponemos en estos momentos y a estas alturas), lo esperable es que el archivo se ejecute correctamente, pero nos espera una desagradable sorpresa: una vez ejecutado, de forma instantánea se cierra el CMD y nos quedamos con dos palmos de narices.

Y no, no es que haya fallado nada, es simplemente que es ese el modo normal de funcionamiento del intérprete: así ahorra espacio de memoria del sistema, evitando efectos negativos en el funcionamiento del procesador. Pero no deja de ser un problema importante, ya que afecta a la funcionalidad del algoritmo al eliminar el componente output en la práctica.

Por desgracia en muchos materiales de aprendizaje del lenguaje Python, algo tan básico parece pasar desapercibido y no se trata con la importancia que tiene para el o la aprendiz, así que se ve (nos vemos) obligad@(s) a indagar por aquí y por allá, no siempre con éxito.

Como resultado de mi propia investigación ofrezco dos soluciones, aunque supongo que no serán las únicas: una es específica del entorno Windows y otra es genérica. Empezaré por exponer la primera.

Desde y para Windows la opción específica consiste en la llamada (importar) al módulo msvcrt y su uso como función .getch() al final del programa. Su sintaxis básica es la siguiente:

import msvcrt

    Código

msvcrt.getch()

El módulo msvcrt contiene un conjunto de rutinas útiles para el funcionamiento en Windows, siendo .getch() una de sus funciones, concretamente una función que lee una pulsación de la tecla y retorna el carácter resultante como una cadena de caracteres de bytes, aunque no muestra nada en la consola (para más información de las rutinas del módulo msvcrt ver descripción detallada aquí).

Otra forma de evitar el cierre prematuro de la consola es escribir la función input() al final del script. De este modo la finalización del algoritmo queda en suspenso hasta que se pulse una tecla, sólo que, en este caso la tecla a pulsar para cerrar la cmd es enter, (y no otra cualquiera), mientras que mediante msvcrt.getch() cualquier pulsación es suficiente.

NOTA. En python.2 se empleaba la función raw_input(), pero en python.3 esta instrucción provoca error.

Esta limitación se contrarresta con la funcionalidad universal del procedimiento. Y sus limitaciones no resultan difíciles de sortear: es suficiente con informar al usuario o usuaria mediante un mensaje previo a la activación de la función. Para ello es suficiente con un print() que contenga el mensaje de advertencia. Un ejemplo sencillo podría ser el siguiente:

vNombre=input("¿Cómo te llamás? ")
print("Bienvenido al programa", vNombre)
print("Ha finalizado el programa. Para salir pulsa ENTER")
input()

En esta entrada hemos dado un primer paso para desarrollar código funcional en Python pensando en utilizar los algoritmos que desarrollemos como programas funcionales, esto es: que no dependan exclusivamente del IDLE para su ejecución y que devuelvan resultados útiles para nuestro trabajo. Para ello, la primera meta a alcanzar es que el resultado del procesamiento permanezca en pantalla hasta que decidamos cerrar la consola (cmd). Esto nos garantiza funcionalidad en la fase output y lo conseguimos añadiendo al script dos sentencias: una específica para Windows y otra genérica.

domingo, 16 de octubre de 2022

Python. Lenguaje

Conjuntos de datos. Diccionarios

El último tipo de colección de datos en Python es el diccionario, estructura cuyos elementos o componentes son pares llave/valor que denominamos entrada.

La llave puede ser cualquier elemento hashable, esto es, un objeto cuyo valor no cambia a lo largo de toda la ejecución del programa, siendo un hash el valor que identifica dichos objetos. Además, la llave no puede estar duplicada. El valor puede ser de cualquier tipo y puede estar repetido. Esta estructura del diccionario nos permite un rápido acceso a los datos que contiene.

La sintaxis del diccionario es la siguiente: al igual que los conjuntos, {} para delimitar su inicio y fin, entradas (pares llave/valor) separadas por comas y elemento llave-valor separados por dos puntos. 

  • Podemos declarar un diccionario directamente:

alumnos={"1º":"Juan", "2º":"Luis", "3º":"Melisa"}

  • O bien mediante el constructor de clase dict

alumnos = dict() -> Diccionario vacío
alumnos= dict(Uno="Juan", Dos="Luis", Tercero="Melisa") -> los números no son identificadores válidos, por lo que no pueden usarse como claves. Por este motivo, esta fórmula es poco empleada.

Al contario que los conjuntos, sí es posible acceder a un elemento concreto de un diccionario. Para ello utilizaremos la referencia llave, de forma similar a cómo hacíamos con listas y tuplas. Por ejemplo, para acceder al elemento 2, solicitaremos...

print(alumnos["2º"]) -> Devuelve Luis.

En este caso, tal y como definimos el contenido del diccionario, no podemos solicitar alumnos[1], ya que 1 no es la llave, sino 2º. Ahí radica una de  las diferencias entre diccionarios y listas/tuplas. Incluso aunque nuestro diccionario se configure como alumnos={1:"Juan", 2:"Luis", 3:"Melisa"}, si solicitamos el alumno situado como primera entrada deberemos hacerlo utilizando la llave de esa entrada (print(alumnos[1])), no su índice (print(alumnos[0])), orden que nos dará error.

También es posible añadir un nuevo elemento a un diccionario existente mediante el uso de la siguiente expresión: NombreDiccionario[llave]=valor. Por ejemplo, si deseamos añadir un cuarto elemento a nuestro diccionario alumnos diremos...

alumnos["4º"]="Carmela"

... por lo que si solicitamos ver el contenido de alumnos (print(alumnos)) obtendremos {1: 'Juan', 2: 'Luis', 3: 'Melisa', '4º': 'Carmela'}.

Igual que en lo relativo a las variables y al resto de las colecciones de datos, aun son muchas las cosas por aprender respecto a los diccionarios, y las iremos viendo en entradas sucesivas, pero por el momento considero que es suficiente como presentación y primer acercamiento a los diccionarios y, por extensión, a las colecciones de datos, por lo que cierro aquí, provisionalmente, este conjunto de entradas dirigidas a su presentación y primer análisis.

Python. Lenguaje.

Colecciones de datos. Conjuntos

El tercer tipo de colección de datos disponible en Python es el conjunto.


Un conjunto se diferencia de lista y tupla en dos características: no dispone de índice, por lo que no se puede acceder a su contenido mediante referencia a índice o valor de posición, y no admite elementos repetidos, lo que le asemeja al concepto matemático de conjunto.

Se recomienda utilizar conjunto en lugar de listas o tuplas cuando el volumen de datos que contiene la colección es muy elevada, ya que los conjuntos son mucho más eficientes: el acceso a la colección es en Python mucho más rápida en conjuntos que en listas o tuplas.

La sintaxis básica de un conjunto es su delimitación mediante llaves ({}), lo que permite crearlos directamente, asociados a una variable, mediante la siguiente instrucción (como ejemplo):

a = {1,3.4,"Casa"}

Al pedir la devolución de un conjunto tenemos que saber que el orden de devolución no es necesariamente el orden en que introdujimos los datos, ya que Python procede a ordenarlos según criterios propios. Por ejemplo, el conjunto {1,7,21,2} es devuelto como {1,2,21,7}

Dado que los conjuntos carecen de índice, no es posible acceder a un elemento específico de los que componen un conjunto, pero sí es posible transformar conjuntos en listas o tuplas, como también lo es convertir ambas en conjuntos. Para ello emplearemos los denominados "constructores de clase": list(), tuple() y set(). Este constructor también es necesario para crear un conjunto vacío, ya que no podemos hacerlo directamente:
  • Conjunto vacío: c0=set() -> devuelve set()
  • Conjunto a partir de lista: cl=set([1,2,3]) -> devuelve set{1,2,3}
  • Lista a partir de conjunto: lc = list({1,2,3} -> devuelve [1,2,3]
  • Conjunto a partir de tupla: ct=set((2,5,7)) -> devuelve {2,5,7}
  • Tupla a partir de conjunto: tc=tuple({3,4,5}) -> devuelve (3,4,5)
Si queremos obtener el contenido individual de una determinada posición en un conjunto, primero deberemos obtener el orden que ocupa ese elemento en el conjunto, ya que no es igual al orden en que lo introducimos, y después convertirlo en lista o tupla y solicitar la posición que nos interese. Por ejemplo:

cj={24,"pato","9",2.3}  -> Creamos el conjunto
print(cj) -> Comprobamos el orden {24, '9', 2.3, 'pato'}
ls=list(cj) -> Convertimos el conjunto en una lista
print(ls[0]) -> Solicitamos el dato situado en primer lugar 24

NOTA. En estos casos siempre es preferible trabajar con listas o tuplas, pero podría ser conveniente utilizar conjuntos si el volumen de datos es muy elevado.

domingo, 9 de octubre de 2022

Python. Lenguaje

Colecciones de datos. Tuplas

Después de las listas, las tuplas son el segundo tipo de colecciones de datos disponible en Python. En esta entrada, siguiendo el modelo de la anterior, recordaremos su definición y características y aprenderemos a manejarlas.

           


La palabra tupla no existe en español, aunque sí en el lenguaje matemático específico, derivada de la generalización de la secuencia dupla, tripla.. n-tupla..., esto es, como lista ordenada finita de elementos.

En informática, una tupla es una lista inmutable y ese es el significado que tiene también en Python. En consecuencia, una tupla no es más que una lista cerrada, que no se puede modificar una vez creada, lo que equivale a decir que es un tipo específico de lista. Mientras que una lista se pueden modificar (por lo que podríamos redefinirla como listas abierta o mutable), una tupla (lista cerrada o inmutable) no se pueden modificar.

En Python para diferenciar listas de tuplas se modifica su sintaxis básica: mientras las listas se identifican por el uso de corchetes en su delimitación, en las tuplas se emplean paréntesis...

miLista = ["perro","gato","lobo"]
miTupla = ("perro","gato","lobo")

... pero en ambas los datos se separan por comas y es el valor de posición (el índice) que ocupan en su seno lo que permite identificarlos:

print(miLista[1]) -> gato 
print(miTupla[1]) -> gato

Al ser las tuplas inmutables las podemos considerar equivalentes a las constantes frente a las variables (que serían las listas), por lo que su utilidad es equivalente a la de aquellas. Los tuplas no admiten ciertas operaciones que sí son posibles en las listas, como añadir elementos, modificarlos o borrarlos, por lo que no cambian ni de estructura ni de contenidos a lo largo de todo el algoritmo.

No obstante, una tupla no deja de ser una colección de datos que puede contener distintos tipos de elementos. Del mismo modo que las listas pueden contener, además de datos de diferentes tipos, otras listas y también tuplas, las tuplas pueden tener la misma composición, incluyendo listas. Y al ser las listas elementos mutables, una tupla que contenga una lista podrá ser modificable, no en cuanto tupla, pero sí ese elemento de la tupla definido como lista. Detengámonos un momento a analizar estas cuestiones.

miTupla = ("casa","mesa")
print(miTupla) -> ('casa', 'mesa')
miTupla[0]="silla"

        -> Devuelve error 

Traceback (most recent call last):
  File "<pyshell#2>", line 1, in <module>
    miTupla[0]="silla"
TypeError: 'tuple' object does not support item assignment

Hemos definido una tupla de dos elementos. Si queremos modificar uno de ellos mediante asignación (como hacemos en una lista) el resultado es un mensaje de error (en rojo) que nos informa que el objeto tupla no soporta la asignación de ítem, o sea, que no se pueden modificar sus ítem. Tampoco podríamos hacer otros cambios que sí son posibles en las listas, como añadir un nuevo elemento, borrarlo... (ver entrada sobre listas), pero eso ya lo dijimos y es lo esperable por la propia definición de tupla.
 
miLista=[1,2,3]
miTupla=("casa","mesa",miLista)
print(miTupla) -> ('casa', 'mesa', [1, 2, 3])
miTupla[0]="armario" -> Devuelve error
Traceback (most recent call last):File "<pyshell#12>", line 1, in <module> miTupla[0]="armario". TypeError: 'tuple' object does not support item assignment 
print(miTupla[2][0]) -> 1
miTupla[2][0]=0
print(miTupla) -> ('casa', 'mesa', [0, 2, 3])

En este segundo ejemplo, la tupla contiene tres elementos, siendo el tercero [2], una lista definida con anterioridad (miLista). Podemos comprobar de nuevo que si queremos modificar el elemento [0] nos devuelve error, pero si lo que deseamos es modificar el elemento [2][0] (tercer elemento de la tupla  y primero de esa lista), es perfectamente posible hacerlo: la orden miTupla[2][0]=0 no devuelve error y se ejecuta correctamente (print(miTupla) -> ('casa', 'mesa', [0, 2, 3])

Para finalizar esta entrada, advirtiendo antes de que aun quedan muchas cosas por aprender de las listas y de las tuplas, vamos a ver una aplicación práctica combinando listas y tuplas y ciclos. En realidad este ejercicio se puede considerar complementario tanto de esta entrada como de la anterior, ya que desarrolla un tema que en aquella quedó indicado pero no resuelto: dije entonces que podíamos crear listas sin elementos (sin contenido), pero no dije qué utilidad podrían tener tales listas. Ahora veremos una de ellas. Utilizar una tupla como marco de referencia (como colección de datos) concreta también una de sus utilidades, precisamente aquella en la que su rigidez se puede considerar una virtud: respetar la estructura básica de una formulación asimilable a base de datos.

El algoritmo resultante queda aquí a tu disposición para que lo corras y lo estudies. La idea es que crees tú algún otro semejante y compares tu solución con la mía, que ya advierto no es perfecta, puesto que el resultado final...

Ejemplo para dos alumnos hipotéticos

(['Carmela', 'Alejandro'], ['Álvarez López', 'Ramírez Suárez'], ['1', '1', '1', '0', '1', '0', '1', '1', '0', '0', '1', '1', '1', '0']) 

... no es del todo satisfactorio: mi idea era obtener el siguientes resultado para el tercer componente de la tupla (dos elementos lista en lugar de un único elemento)

[['1', '1', '1', '0', '1', '0', '1'],[ '1', '0', '0', '1', '1', '1', '0']]

... pero no me ha sido posible... por el momento.

Aunque este error relativo respecto al objetivo es importante y habrá que estudiar cómo remediarlo, sí hay algunos logros de interés que te quiero comentar brevemente:
  • En la entrada sobre listas decía que la expresión  vlstPalabras = vlstPalabras + ["mesita"] permitía añadir un nuevo elemento a la lista, y es correcto, pero para añadir un elemento en otros contextos de mayor complejidad necesitamos emplear una función (append()), como puedes observar en el algoritmo de ejemplo:
vNombre=input("Nombre: ")
    lstNombres.append(vNombre)

  • Podemos declarar listas vacías y cumplimentarlas mediante un bucle en función de una decisión previa: en mi ejemplo, el número de alumnos del listado, que sirve para establecer el número de ciclos o iteraciones que realiza el bucle for
vNumAl = int(input("Dime el número de alumnos y alumnas del grupo: "))
(...)
for i in range(vNumAl):

  • Podemos anidar un bucle for dentro de otro bucle for, generando estructuras complejas de ejecución. En mi ejemplo el bucle principal contiene un bucle secundario. Las iteraciones del primero vienen dadas interactivamente (ver explicación anterior), pero las del segundo vienen determinadas por un valor implícito: el número de ítem de la prueba: for u in range(7):
  • Aunque lo que manejamos son listas (y por eso las podemos modificar), la estructura principal es una tupla, ya que queremos que su estructura básica (la del "registro") sea invariable. 

NOTA. Precisamente la limitación de este algoritmo es no haber conseguido reproducir la forma esperada en un diseño de base de datos: ser resultante de la unión o consulta de dos tablas: datos personales y resultados. Si obtengo un resultado mejor, lo publicaré.

Introducción. Interfaces.

Notas introductorias

Cierto es que hasta ahora hemos empleado el modo consola y que aun tendremos que emplearlo para trabajar tanto con OOo Basic como con Python, ya que resulta conveniente para seguir con el aprendizaje de ambos lenguajes, pero también lo es que para crear ejemplos de cierta complejidad y propuestas de trabajo que resulten útiles para la práctica, el modo consola resulta poco amigable y escasamente estético, ya que estamos acostumbrados a los modos gráficos, basados en ventanas y sistemas que facilitan la interacción entre el usuario o usuaria y el "programa". Así que me ha parecido que ya iba siendo hora de aprender a usar los modos gráficos (GUI) en la implementación de interfaces para empezar a construir propuestas de trabajo.

      

Empezaremos por algunas nociones básicas sobre lo que son las interfaces gráficas y seguiremos por la interface de propósito general de OOo Basic. La idea es crear un ejemplo de docap que hace uso de un cuadro de diálogo sencillo. Es una forma de empezar a familiarizarnos con la creación y uso de componentes hoy por hoy fundamentales en todo recurso digital.

El mundo de las interfaces gráficas es tan complejo que se está produciendo una división del trabajo dentro de los programadores en la que se diferencian aquellos o aquellas que orientan su trabajo a la generación de algoritmos (código oculto) y aquellas o aquellos que están especializado en generar las formas en las que estos programas se muestran e interactúan con los usuarios y usuarias.

Las interfaces de texto, como es la consola, ofrece formas de interacción muy simples, a veces poco amigables. Por eso, aunque son modos de implementar los programas muy eficaces, paulatinamente han sido sustituidas por interfaces gráficas. Una etapa intermedia es aquella en la que determinados programas funcionaban a base de comandos; como ejemplo el procesador de textos WordPerfect que aprendimos a manejar los que nos iniciamos en los programas informáticos cuando se empezaban a popularizar los ordenadores tipo Amstrad y similares. Los anteriores eran meras unidades centrales sin periféricos (había que añadirlos) como pantallas y teclados, que incorporaban sistemas básicos de almacenamiento (cintas de cassette, por ejemplo) trabajaban directamente en modo consola.

Las interfaces gráficas de usuario (GUI, como son llamadas en el mundillo de la programación) ofrecen ventanas, cuadros de diálogo, barras de herramientas, botones, listas desplegables y muchos otros elementos (objetos) con los que estamos tan acostumbrados a tratar que si un programa no cuenta con ellas, instintivamente lo consideramos "arcaico" y "complejo. Y eso que las interfaces de consola son tan eficientes (o más que las gráficas), además de más sencillas y más económicas de crear y de mantener. Y que ciertos lenguajes actuales siguen trabajando básicamente en ese modo (R es un ejemplo, aunque ya cuenta con un programa más "amigable": R-Studio)

De Shmuel Csaba Otto Traian, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=29272838

Este gráfico resume la complejidad que subyace al planteamiento de GUI potentes como aquellas con las que interactuamos a diario en nuestros dispositivos electrónicos. Afortunadamente no es este el nivel mínimo de complejidad al que tenemos que enfrentarnos para implementar una GUI en nuestros programas. 

Afortunadamente también para implementar las más simples no necesitamos nada especial. Sírvanos de ejemplo el propio "IDE" de PSeInt, que nos viene ya dado, o las ventanitas emergentes que hemos creado en OOo Basic (MsgBox, Input, InputBox). 

En PSeInt no nos va a ser posible implementar GUIs para correr los algoritmos que creemos, ya que se trata de un programa para desarrollar lógica de programación en sentido estricto y los flujogramas no pueden ser considerados GUIs, ya que no permiten interactuar.

Hasta ahora en OOo Basic nos hemos limitado a utilizar las formas más elementales de interfaces gráficas, pero más bien llevados por la necesidad que deriva del tipo de lenguaje y las posibilidades que ofrece, que por un uso consciente y específico de las mismas. Esto implica que aun en su sencillez, aun tenemos pendientes descubrir algunos de sus secretos.

Y en cuanto a Python, aun nos falta para iniciarnos en la implementación de las formas más básicas de GUI, aunque trataré de acortar el camino pendiente de recorrer para que este proyecto personal de aprendizaje no se descompense demasiado en el ritmo de avance en cada uno de los lenguajes trabajados.

Por ello en este blog emplearemos una conceptualización simplificada del esquema anterior, que podemos representar de este modo,...

Basada en Shmuel Csaba Otto Traian

  • La GUI sustituye en ella las posiciones/funciones básicas de entrada/salida (input-output), bien ambas, bien únicamente la entrada, como tendremos ocasión de ver en OOo Basic.
  • Esa interface gráfica se conecta con el código de base (el que procesa los datos y genera resultados) mediante una capa intermedia de código específico que debemos conocer para que lo "dibujado en pantalla" sea algo más que una ventana más o menos amigable.
  • El "programa" oculto devuelve a la interface los resultados de su procesamiento 
  • y se una nueva interface gráfica nos muestra el resultado. 
En este último punto es donde OOo Basic presenta las características específicas que implica ser un lenguaje de script: esa devolución de información se presenta generalmente en una GUI que no es más que el servicio con el que estamos trabajando. Pero esto, que no tiene por qué ser necesariamente así, ya lo veremos cuando tratemos sobre las GUI en OOo Basic, coas que queda para una nueva entrada.

De momento me voy a adelantar de nuevo en OOo Basic (es una nueva demostración de la utilidad inmediata que tiene este lenguaje de script para los SEO y para los y las docentes en general)  y voy a tatar en esta entrada sobre uno de los recursos GUI menos empleados en Libre Office y posiblemente también en otros lenguajes de similar propósito: los cuadros de diálogo.

OOo Basic permite trabajar con tres formatos de GUI: las ventanas emergentes, los formularios y los cuadros de diálogo. Aunque trataremos (de hecho ya lo hemos hecho, aunque indirectamente) sobre ventanas emergentes y formularios, considero conveniente empezar por el aprendizaje de los cuadros de diálogo por similitud con otros lenguajes de programación (como Python) y para facilitar la implementación sistemática de este tipo de GUIs (que son de propósito general en OOo Basic) en los ejemplos y los proyectos basados en OOo Basic que desarrollemos a partir de este momento.

viernes, 7 de octubre de 2022

Python. Lenguaje.

Colecciones de datos (1). Listas

Las listas son las colecciones de datos más simples con que cuenta Python. En esta entrada vamos a estudiarlas con cierto detalle.


Como decíamos en una entrada anterior, las colecciones de datos (arreglos o matrices) nos permiten almacenar en una única dirección de memoria RAM un conjunto de datos, mientras que con una variable únicamente podemos acceder a uno concreto. Los diferentes lenguajes de programación imponen condiciones específicas a estas colecciones, pero la ventaja de los arreglos en relación a las variables en cuanto a la cantidad de información a la que podemos acceder es evidente. Si además el lenguaje dispone de diferentes tipos de colecciones, la flexibilidad en el acceso es evidente. 

Este es el caso de Python que, como dijimos en la entrada anterior, cuenta con cuatro tipos de colecciones de datos. Analicemos en primer lugar la más simple y posiblemente más flexible de ellas: Las listas.

Definíamos antes una lista como un conjunto ordenado de datos o elementos de diferentes tipos (caracteres, cadenas, enteros, reales, booleanos) que pueden estar repetidos. Estas características, junto con las operaciones permitidas sobre la lista, la convierten en un recurso muy flexible para el almacenamiento de datos, superando ampliamente en cuanto a flexibilidad a los arreglos o matrices que vimos tanto en PSeInt como en OOo Basic.

La forma de declarar una lista es en Python tan simple como el ejemplo que sigue:

vListaDiasSemana = ["lunes","martes","miércoles","jueves","viernes"]

  • Observa que una lista se declara y asigna de la misma forma y con el mismo operador que una variable, unificando ambos procesos en una única sentencia (frente al procedimiento seguido en PSeInt.
  • El identificador de la lista es su delimitador: los corchetes.
  • Los elementos o datos se separan mediante comas.
  • Aunque en esta lista todos los elementos son del mismo tipo, no es esta una restricción aplicable a los conjunto de datos en Python, como ya dijimos.
  • Aunque podemos escribir la lista sin más ([]), para poder acceder a ella o a alguno de sus elementos, es necesario asignarla a una variable (vListaDiasSemana)
  • Las listas pueden contener 0 elementos o todos los que deseemos.
Es importante entender que cada elemento de la lista ocupa una posición dentro de ella y que esta posición o índice es el que nos permite acceder a cada uno de los elementos contenidos en la lista de forma similar a como accedemos al contenido de una variable, aunque respetando su propia sintaxis. Si para acceder al contenido de una variable únicamente tenemos que referenciarla, para acceder a un elemento determinado de una lista, debemos hacerlo especificando su índice. Si no indicamos ese índice se supone que estamos accediendo a todo el contenido de la lista, algo que es posible en determinadas condiciones, pero no en todas. Vemos unos ejemplos:

vPalabra = "casa"
vlstPalabras = ["casa","mesa","armario"]
print(vPalabra) -> devuelve casa
print(vlstPalabras[0]) -> devuelve casa
print(vlstPalabras) -> devuelve ['casa','mesa','armario']

  • Si la lista vlstPalabras estuviera vacía, print(vlstPalabras[0]) provocaría un error (no hay elemento vlstPalabras[0]), no así print(vlstPalabras) -> devuelve [].
Podemos conocer el número de elementos de una lista mediante la función len(), del cual podemos informar directamente o asignar a una variable para su posterior tratamiento. Por ejemplo, en vListaDiasSemana:

print(len(vListaDiasSemana))                    -> Devuelve 5
vNumDiasSemana=len(vListaDiasSemana)
print(vNumDiasSemana)                            -> Devuelve 5

Una lista es modificable: podemos cambiar su contenido, añadir elementos o borrarlos, todo ello de forma muy simple:
  • Para cambiar un elemento, únicamente tenemos de reasignarle nuevo contenido haciendo uso del índice (lugar que ocupa el elemento en la lista) que le corresponde. Por ejemplo, siendo la lista...
vlstPalabras = ["casa","mesa","armario"]

 ... si queremos cambiar vlstPalabras[0] "casa" por "cama" diremos...

vlstPalabras[0] = "cama"

  • Para añadir un nuevo elemento a la lista anterior utilizaremos la siguiente expresión (el nuevo elemento se añade al final de la lista):

 vlstPalabras = vlstPalabras + ["mesita"]

  • Para borrar un elemento de la lista (la anterior, por seguir con el ejemplo), la instrucción es la siguiente:

del vlstPalabras[2] -> Borra el elemento "armario" que ocupa la tercera posición. 

Podemos trabajar con varias listas, por ejemplo dos, y unirlas en una tercera. Supongamos dos listas:

vlstMuebles1=["mesa","silla","taburete"]
vlstMuebles2=["sofá","armario","mesita"]

... la unión de ambas mediante la instrucción...

vlstMuebles= vlstMuebles1+vlstMuebles2

... nos da como resultado... 

print(vlstMuebles) -> Devuelve ['mesa', 'silla', 'taburete', 'sofá', 'armario', 'mesita']

NOTA. Esta operación es equivalente a la unión de conjuntos (A U B), salvo que en la unión e conjuntos, todos los que son elementos intersección no se repiten, pero en la unión de listas sí, ya que éstas admiten la posibilidad de que se repitan sus elemento. Esto es debido a que lo que marca la pertenencia a la lista es en realidad la posición que ocupa cada elemento y que podemos identificar mediante su índice.

Además de unir dos listas en una tercera, también podemos considerar otras opciones, ya que una lista puede estar formada por una serie de elementos/datos (lista simple), pero también por una combinación de elementos y listas o por una colección de listas. La primera formulación ya la conocemos, tanto en su presentación más sencilla, como resultado de la unión o suma de dos listas; veremos a continuación las otras dos.

  • Lista resultante de la unión de una lista con otra mediante inclusión. Sea la lista inicial vlstMuebles1=["mesa","armario"] y queremos modificarla para que esté conformada por la estructura de contenidos Muebles + Muebles de asiento; para ello creamos una segunda lista con muebles de asiento (vlstAsientos=["silla","taburete","sillón","tresillo"]) y la incorporamos como elementos de la lista original:

vlstMuebles=["mesa","armario",vlstAsientos]

... obteniendo como resultado

print(vlstMuebles) -> Devuelve ['mesa', 'armario', ['silla', 'taburete', 'sillón', 'tresillo']]

Obsérvese que lo que alteramos es la estructura de la primera lista, no unimos ambas; de ahí que los elementos de la segunda lista (que ocupa como elemento de la primera la posición 3 (vlstMuebles[2]), aparecen delimitados como lista mediante []. Esto es importante entenderlo, ya que el acceso a los contenidos de esta lista anidada se rigen por su doble posicionamiento: como elementos [2] de la lista principal y como elementos [x] de la lista secundaria. Si solicitamos el contenido de la posición [2], obtenemos...

 print(vlstMuebles[2]) -> Devuelve ['silla', 'taburete', 'sillón', 'tresillo']

... por lo que si lo que deseamos es obtener el contenido de la posición 2 de la lista anidada (taburete), deberemos referenciarlo indicando ambos índice: el primero como elemento de la lista principal y el segundo de la lista anidada:

print(vlstMuebles[2][1]) -> Devuelve taburete

  • Un procedimiento similar se sigue para construir una lista formada por listas (lista de listas, su se prefiere). Por ejemplo, supongamos que tenemos un conjunto de listas cuyos elementos representan subconjuntos de un conjunto principal y queremos representar esa pertenencia transformado el conjunto principal en una lista de listas, cuyos elementos son esos subconjuntos. 

lstFrutas=["pera","manzana","plátano"]
lstLegumbres=["garbanzo","lenteja","haba"]
lstHortalizas=["zanahoria","tomate","pimiento"]
lstVegetales=[lstFrutas,lstLegumbres,lstHortalizas]
print(lstVegetales) -> Devuelve [['pera', 'manzana', 'plátano'], ['garbanzo', 'lenteja', 'haba'], ['zanahoria', 'tomate', 'pimiento']]

El acceso a un elemento cualquiera de cualquiera de estas tres listas sigue el procedimiento explicado antes. Por ejemplo, para acceder al elemento 2  ([1]) de la lista 3 ([2]), se realiza con las siguiente instrucción:

 print(lstVegetales[2][1]) -> Devuelve tomate

El uso de bucles es una estrategia muy frecuente para recorrer los elementos de una lista y permite desarrollar procesos muy interesantes en la construcción de algoritmos. Esto es debido a la posibilidad de combinar el uso de las variables contador en combinación con los índices de las listas. Veamos un ejemplo sencillo. Supongamos una lista que contiene los nombres de los alumnos, una segunda que contiene sus apellidos y una tercera que contiene las calificaciones en una prueba.

lstNombre=["Julián","Pedro","Matilde", "Ana"]
lstApellidos=["Alonso Pérez","González López","Ramírez De Luis","Ordóñez Núñez"]
lstNotas=[8,9,7,4]
for i in range(4):
    print(lstNombre[i])
    print(lstApellidos[i])
    print(lstNotas[i])

Devuelve
Julián
Alonso Pérez
8
Pedro
González López
9
Matilde
Ramírez De Luis
7
Ana
Ordóñez Núñez
4

Pero en Python también podemos trabajar con el bucle For directamente sobre una lista, lo que simplifica enormemente la formulación de estas estructuras. Un ejemplo sobre una de las listas anteriores. Para ello es suficiente con emplear la expresión for item in NombreLista

lstNombre=["Julián","Pedro","Matilde", "Ana"]
for item in lstNombre:
print(item,end=" ") -> Devuelve Julián-Pedro-Matilde-Ana-

NOTA. Esta segunda forma de sintaxis del bucle For en Python es específica para trabajar con bucles sobre colecciones de datos. No hablé de ella en la entrada en la que se trató el tema del bucle determinista for porque aun no habíamos sabíamos tratado el tema de las colecciones de datos en Python.

Una expresión similar a la anterior es la que usaremos para saber si un datos concreto forma parte de una lista. Sobre la lista anterior (lstNombre=["Julián","Pedro","Matilde", "Ana"]), para saber  si el Carlos forma parte de la lista formularemos la siguiente instrucción: "Carlos " in lstNombre. Esta expresión devuelve un booleano (en este caso False, ya que Carlos no está en lstNombre).

Podemos asociar esta búsqueda a un condicional y en función del resultado generar una bifurcación del algoritmo o, más sencillo, añadir el elemento a la lista de nombres. Para ello primero deberemos asignar el resultado de "Carlos " in lstNombre a una variable:

vBoolA = "Carlos" in lstNombre
if vBoolA==False:
    lstNombre=lstNombre+["Carlos"]
print(lstNombre) -> Devuelve ['Julián', 'Pedro', 'Matilde', 'Ana', 'Carlos']

Mucho es aun lo que falta por aprender sobre las listas y su manejo, pero no se trata de agotar el tema y ya hemos aprendido lo necesario para poder incluirlas eficazmente en nuestros algoritmos.  Es por ello que creo alcanzados los objetivos de esta entrada, y más aun pensando que, de algún modo, tendrá continuidad en la siguiente, en la que trabajaremos sobre las tuplas.