martes, 15 de noviembre de 2022

Python. Estructuras

Funciones en Python

Después de haber visto qué son las funcionescómo se crean y su uso en OOo Basic, es momento de tratar el tema de las funciones en Python. Para ello, además de la información que proporciona la bibliografía, he seguido la excelente información que proporciona Sebastián J. Bustamante, de FreeCodeCamp.


Como en el resto de los lenguaje de programación, también en Python podemos diferenciar entre las funciones nativas (aquellas disponibles en el propio lenguaje) y funciones creadas o propias (aquellas que creamos o que han creado otros usuarios del lenguaje). En esta entrada vamos a hablar de cuestiones generales: cómo se crean y se usan las funciones y qué aportan al lenguaje Python.

Empezando por el final, podemos decir que las funciones cumplen en Python el mismo objetivo que en el resto de los lenguajes: facilitar el proceso de programación, creando subcomponentes reutilizables.

Dado que lo primero que encontramos en Python son funciones nativas (de hecho ya hemos usado unas cuantas), quizá es más conveniente explicar primero cómo se emplean; para ello nada mejor que retomar experiencias ya conocidas de uso de funciones nativas, por ejemplo, la función print(): es esta una función nativa muy utilizada y muy sencilla, por lo que resulta ideal para explicar el uso de una función, nada más sencillo, por otra parte, que escribir dentro del paréntesis que acompaña a la función el dato o datos que precisa la función para desarrollar su labor. Y dado que print() es una función que nos permite escribir algo en consola (o en la interfaz que usemos, lo que debemos es escribir dentro aquello que queramos sea devuelto por nuestro programa: 
  • print("Buenos días") escribirá el string Buenos días
  • print(4+3) devolverá 7 (resultado de la suma de los valores pasado)
Las funciones creadas por nosotros pueden incluir, lógicamente, el uso de funciones nativas, pero su forma de funcionar no siempre obedece a la misma lógica. Veremos algunas de estas diferencias, pero primero tendremos que aprender a crear funciones sencillas.

La sintaxis de una función propia en Python es como sigue:
  • Utilizamos la palabra reservada def, tanto para el caso de funciones que no tienen parámetros o no devuelven valores (los subprocesos en OOo Basic), como para aquellas que sí los tienen y devuelven valores (las function en sentido estricto de OOo Basic). En esto Python se asemeja al modo de operar de PSeInt, aunque restringiendo la expresión a la palabra reservada def
NOTA 1. Recuerda que en PseInt utilizábamos indistintamente las palabras reservadas Subproceso o Funcion, y que en OOo Basic usamos las palabras reservadas Sub, para las subrutinas y Function para las funciones.
  • ... seguida del nombre o identificador de la función: def diHola
  • ... seguido de dos paréntesis: def diHola()que pueden estar vacíos (como en el ejemplo anterior), o contener parámetros de entrada, como en este otro caso: def diHolaNombre(nombre)
  • ... y finalizando la línea con dos puntos: def diHola():, que son imprescindibles para que la función funcione (de no ponerlos da error)
  • Finalmente se escribe el código que ejecuta la función, siempre sangrado respecto a la posición de def), que puede ser muy simple o muy complejo, dependiendo de la función que creemos. Veamos algunas funciones simple.
Primer ejemplo. Función sin parámetros.

 def diHola():

 print("Hola, amigo")

Esta función (propia) utiliza la función nativa print(), no tiene parámetros y devuelve el string Hola amigo al ser llamada.

Para llamarla nada más simple que escribir su nombre, incluyendo el paréntesis vacío:

diHola() -> devuelve Hola, amigo 

 Segundo ejemplo. Función simple con un parámetro

def diHolaNombre(nombre):

print("Hola " + nombre + ", buenos días.") 

En este caso, la función cuenta con un parámetro, pero no con sentencia de retorno. Para llamarla deberemos proceder igual que antes, pero escribiendo algo dentro del paréntesis (que en el caso de ser una cadena deberá ir entre comillas...)

diHolaNombre("Carmen") -> devuelve  Hola Carmen, buenos días

Tercer ejemplo. Función con un parámetro numérico

...  pero que no las llevará en caso de valores numéricos, como en este ejemplo...

def tablaCinco(num):

                    print("Resultado: " , num * 5)

 .... que llamaremos mediante escribiendo un número dentro del paréntesis como único parámetro 

 tablaCinco(4) -> devuelve Resultado: 20

Cuarto ejemplo. Función con dos parámetros.

Y para finalizar este entrada introductoria, una última función que desarrolla la anterior. En este caso una función con dos parámetros y sentencia de retorno (observa que ahora no usamos la función print() y sí la palabra reservada return, que no es una función nativa, sino una expresión)...

def sumaNum(num1, num2):

 return num1 + num2

...que llamaremos como sigue (observa que escribimos ambos parámetros separados por coma, no mediante el símbolo de la suma, como hacíamos al usar directamente la función nativa print()...

sumaNum(3,7) -> devuelve 10

 ... aunque el resultado es equivalente a utilizar print(3+7)

NOTA 2. Cuando la función contenga parámetros, deberemos usar en la llamada tantos parámetros como contenga la función y del mismo tipo. De no hacerlo dará error.

sábado, 12 de noviembre de 2022

Python. Archivos.

Python. Acceso a archivos

Para completar el tema del manejo de archivos externos mediante Python, quedaba pendiente estudiar los métodos de escritura y lectura de ficheros (de texto plano). Dedicaremos esta entrada a trabajar sobre este tema.


En realidad ya hemos empleado estos métodos en las entradas dedicada a trabajar con archivos (de texto plano) externos, también cuando hablamos del uso de interfaces gráficas, pero no los tratamos de forma explícita y me parece necesario hacerlo para no pasar por alto contenidos de tanta utilidad como la escritura y lectura en ese tipo de archivos.

Hasta el momento hemos visto el modo de acceso para creación, lectura y escritura de y en archivos de texto plano, su sintaxis y el modo de actuación de estos modos, pero no hay que confundirlos con los métodos (funciones si se prefiere) de escritura y lectura. Los primeros generan el fichero y acceden a él, y según el modo de acceso están permitidas determinadas operaciones (de lectura y/o escritura), pero esto no implica que estén ejecutadas esas operaciones, como mucho únicamente condicionadas.

Por ejemplo, el método w (write) nos permite crear/abrir un fichero para escribir en él, pero de por sí no escribe nada, únicamente crea el archivo si es que no está ya creado y lo abre y sobreescribe (trunca) si está creado. Comprobemos este funcionamiento:

  • El script ficheros4Sobreescritura.py con el que trabajamos en una entrada anterior, utiliza en la función open() el método "w" (fcrear=open("pruebaCrear.txt","w")), con lo que, al correrlo se presentan dos posibilidades:
    • Que el archivo pruebaCrear.txt no exista, y entonces lo crea (y hasta ese momento no contiene nada)
    • O que exista, lo abra y borre su contenido.

Según está escrito este script, a continuación, y mediante el uso sucesivo de funciones de escritura .write() vamos introduciendo contenido en ese archivo, por lo que al finalizar la primera parte del proceso (fcrear.close()) el archivo creado contiene tres líneas de texto (las que dan contenido al método/función .write() empleado.

  • Pero, ¿ qué sucederá si omitimos el uso de .write()?. Sucede lo que indiqué antes: se crea/abre un archivo (pruebaCrear.txt) (de nuevo se presentan las dos opciones antes indicadas), y al finalizar el proceso (fcrear.close()), si abrimos el archivo .txt comprobaremos que está vacío. El motivo es obvio: no hemos escrito nada en él, lo que traducido a código significa que no hemos utilizado la función/método .write(). Esto nos permite comprobar que no es el método "w" quien escribe el texto, sino el método .write(). El script ficheros5Sobreescritura.py contiene la modificación del código que permite comprobar lo explicado antes; en él he comentado (anulado) las líneas de código en las que se emplea el método .write() para demostrarlo.
Entonces, ¿ qué es lo que hace "w"?. Hace lo que se espera de él:
  • Crea/abre el archivo*
  • Elimina el contenido del archivo abierto
  • Y, en consecuencia, sitúa el puntero al inicio del archivo.

¿Qué no hace?; escribir en el archivo, ya que esa es función es responsabilidad del método/función write()

(*)NOTA. En realidad, "w" actúa en función de que es llamado como método (de trabajo) de la función open(), que es la que se encarga de acceder al archivo externo. "w" da forma/ o concreta el modo en que se ejecuta la función open(), la cual tiene su contrario en la función close(), que cierra el archivo (borra de la memoria RAM la vía de acceso al archivo que abrió open())

Tanto "w" como "a" (apend) determinan el comportamiento del método/función de escritura write(): "w" borrar el contenido previo, por lo que sitúa el puntero al inicio del archivo, y "a" no borra el contenido existente y sitúa el puntero al final del archivo. Pero en realidad, la diferencia entre ambos radica en lo que ambos hacen con el contenido preexistente, no es dónde se sitúa el puntero, ya que "w" podemos entender que sitúa el cursor al inicio, pero también al final (del archivo), puesto que, en su caso, al borrar el contenido, coinciden el inicio y el final del archivo. 

Finalmente"x" cumple la misma función que "w", pero está más volcado en la creación de nuevos archivos. De hecho, si el nombre del archivo coincide con uno existente (esto es, si tratamos de crear un archivo que ya existe), "x" provoca error, como sucede con este script (en el supuesto de que el archivo pruebaCrear.txt ya exista)

Para conocer el posicionamiento del puntero dentro del archivo disponemos de las funciones/métodos tell() y seek()

  • La primera (tell())es de escasa utilidad ya que devuelve la posición actual del puntero en forma de número opaco (que no se corresponde necesariamente con la posición en bytes). Por ello tell() es usado principalmente para marcar posiciones dentro del fichero.
  • La segunda (seek(desp:int,ref:int)) tiene mayor utilidad, por lo que interesa conocer su funcionamiento: esta función/método desplaza el puntero una serie de posiciones indicadas mediante un número entero (desp), tomando como referencia el valor de otro (ref) y que en el caso de los ficheros de texto toma dos valores: 0 para el inicio del archivo y 2 para su final. Esto condiciona, a su vez, los valores que puede tomar desp: 
    • Si ref=0, entonces desp puede ser 0 o un valor de posición obtenido mediante tell()
    • Si ref=2desp sólo puede tomar como valor 0.
Más interés tienen para nosotros los métodos o funciones asociados a la lectura de ficheros, la cual deriva del uso de "r" como complemento de la función open(), y que, como ya vimos en una entrada anterior, permite la apertura de un archivo en modo lectura. Estos métodos (o funciones) que ya hemos empleado son los siguientes:

  • read() Lee y devuelve caracteres desde la posición del puntero hasta el final del fichero, salvo que se especifique el número de caracteres a leer; en ese caso la expresión es como sigue: read(num_car:int):str.
  • readline() Lee y devuelve caracteres en forma de cadena hasta encontrar un salto de línea (\n), que también devuelve. El puntero se queda situado al final de la cadena leída. La fórmula completa de este método/función es el siguiente: readline(limite_car:int): str.
  • readlines() Lee y devuelve en forma de lista de cadenas las líneas del fichero desde la posición del puntero hasta el final. Si indicamos un límite (readlines(limite:int):list) leerá hasta la línea en que se encuentra ese límite, pero si el límite coincide con el final y cambio de línea, devolverá también la línea siguiente.
En este script te muestro las diferentes casuísticas de estos métodos. Menos la primera, el resto de las opciones están comentadas. Para activarlas deberás borrar el carácter # de cada una de ellas (no del comentario real, que va sangrado para que sea más fácil de identificar), comentando a su vez la formulación que estaba activa antes.

miércoles, 9 de noviembre de 2022

Lenguajes. Modelos de programación.

Funciones desde la lógica de programación

PSeInt como recurso orientado al aprendizaje de la programación nos ofrece también formas de desarrollar funciones, también llamados subalgoritmos o subprocesos, nombres éstos de dan a entender sin dudas su sentido y función dentro de la lógica de programación.



Voy a tomar como referencia el contenido de un blog...

https://victomanolo.wordpress.com/funciones-subprocesos-en-pseint/

... que me parece sintetiza muy bien qué son y cómo se crean en PSe Int estos auxiliares del algoritmo principal.

Al igual que sucede en determinados lenguajes de programación, además de la variación en cuanto al nombre, también podemos observar variaciones en cuanto al modo de funcionar estos componentes, que son básicamente dos, tal y como refleja el esquema de la entrada anteriorasumiendo temporalmente el control del proceso, a llamada del algoritmo principal, o bien devolviendo el resultado del procesamiento interno para que aquel pueda continuar ejecutando el proceso.

Para entender el funcionamiento de esta relación entre algoritmo principal y secundarios es necesario hablar de varios conceptos:
  • Llamada. La relación entre el script principal y el secundario se inicia mediante la llamada que el principal hace al secundario. Esta llamada se concreta mediante el nombre del subproceso secundario. Cuando este subproceso devuelve algún valor, se debe colocar además del nombre, una variable (variable de retorno) a la que se asigna el valor devuelto por la función. Si el subproceso no devuelve nada, esta variable no es necesaria.
  • Argumentos. Son las variables que requiere el subproceso para trabajar. Si no los requiere no es necesario especificarlos.
  • Parámetros. Son los argumentos desde la perspectiva del algoritmo principal, esto es: el contenido concreto que se asigna a los argumentos. Cuando son necesarios se pueden entregar bien por referencia, bien por valor. Los parámetros pasados por referencia modifican las variables originales empleadas como parámetros, mientras que los parámetros pasados por valor crean en el subproceso una copia de la variable original sin alterar ésta.
Pasamos a continuación a exponer algunos ejemplos para que sea más sencillo entender los conceptos anteriores. 

Primer ejemplo.

SubProceso Saludar
Escribir 'Este texto es generado desde el subproceso';
  
 
Escribir 'Hola ', Nombre, ', bienvenido a mi script';
FinSubProceso

 // proceso principal que invoca a la función o subproceso Saludar

Proceso PruebaSubproceso

Escribir 'Hola mundo';
Escribir 'Este saludo y estas línea están escritas desde el proceso principal';
Escribir 'Ahora paso el control al subproceso Saludar';
Escribir '***********************************************************';
Saludar; // como Saludar no recibe argumentos pueden omitirse los paréntesis
Escribir '***********************************************************';
Escribir 'En este punto el control del algoritmo regresa al proceso principal...";
Escribir '... y finaliza su ejecución';
FinProceso

Esta es la forma más básica de trabajar con subprocesos o funciones: "trocear" el código del script principal en partes, de modo que cada parte se comporta como un segmento del conjunto. De esta forma, un script muy largo y complejo se puede subdividir en partes, las cuales pueden ser reutilizadas en otras composiciones o algoritmos. Llevando este planteamiento a sus extremos, el script principal puede ser entendido como la organización secuenciada de partes integrables en un todo coherente.

Segundo ejemplo.

// función que recibe un argumento por valor, y devuelve su doble

SubProceso res <- CalcularDoble(num) 
Definir res como real;
res <- num*2; // retorna el doble
FinSubProceso

// proceso principal, que invoca a la función CalcularDoble()

Proceso PruebaFunciones
Definir x Como Real;
Escribir "Ingrese un valor numérico para x:";
Leer x;
Escribir "Llamada a la función CalcularDoble (pasaje por valor)";
Escribir "El doble de ",x," es ", CalcularDoble(x);
Escribir "El valor original de x es ",x;
FinProceso

En este segundo ejemplo la función CalcularDoble recibe como parámetro un valor (paso por valor) y devuelve el resultado de multiplicar ese número por si mismo (res <- num*2). Además se define una variable de retorno (res), a la que se asigna el resultado de la función (res <- CalcularDoble). Aquí el control del proceso lo lleva el Proceso principal (PruebaFunciones), mientras que el subproceso o función devuelve un resultado o valor, calculado en base al argumento (o contenido concreto del parámetro) que recibe como entrada del proceso principal. Es este proceso principal el encargado de mostrar dicho resultado (Escribir "El doble de ",x," es ", CalcularDoble(x);), no el subproceso o función. Además, al tratarse de un valor (y no una referencia), el valor original de la variable utilizada se mantiene sin alterar, tal y como se puede apreciar en la sentencia con la que finaliza el Proceso principal (Escribir "El valor original de x es ",x;)

Tercer ejemplo.

// funcion que recibe un argumento por referencia, y lo modifica

Funcion Triple(num por referencia) 
num <- num*3; // modifica la variable duplicando su valor
FinFuncion

// proceso principal, que invoca a las funciones antes declaradas

Proceso PruebaFuncion
Definir x Como Real;
Escribir "Ingrese un valor numérico para x:";
Leer x;
Escribir "Aun no se ha llamado a la función Triple";
Escribir "El valor original de x es ",x;
Escribir "Llamada a la función Triple (paso por referencia)";
Triple(x);
Escribir "Una vez llamada la función por referencia";
Escribir "El nuevo valor de x es ", x;
FinProceso

Este ejemplo representa el funcionamiento del paso por referencia. No explicitamos variable de retorno y comprobamos que al pasar el parámetro por referencia el valor original de la variable (Escribir "El valor original de x es ",x;) se modifica (Escribir "El nuevo valor de x es ", x;en función del procesamiento que de ella realiza la función (num <- num*3;). Observa que la variable argumento de la función no coincide en nombre con la que utilizamos para establecer el parámetro (Funcion Triple(num por referencia))

Cuarto ejemplo.

// Calcula el promedio de una lista de N datos utilizando una función o  subproceso

SubProceso prom <- Promedio ( arreglo, cantidad )
Definir i como Entero;
Definir sum como Real;
Definir prom como Real;
sum <- 0;
Para i<-0 Hasta cantidad-1 Hacer
sum <- sum + arreglo[i];
FinPara
prom <- sum/cantidad;
FinSubProceso

Proceso Principal

Definir i,N como Entero;
Definir acum,datos,prom como Reales;
Dimension datos[100];
Escribir "Ingrese la cantidad de datos:";
Leer n;
Para i<-0 Hasta n-1 Hacer
Escribir "Ingrese el dato ",i+1,":";
Leer datos[i];
FinPara
Escribir "El promedio es: ",Promedio(datos,n);
FinProceso

Para finalizar, en este cuarto ejemplo, como argumentos/parámetros utilizamos una matriz o arreglo además de una variable (Promedio ( arreglo, cantidad )) . Empleamos también una variable de retorno (prom <- Promedio) y pasamos los datos por valor. La posibilidad de utilizar matrices nos permite crear funciones para tratar un elevado número de datos, lo cual es que mucho interés para desarrollar análisis estadísticos, entre otras posibilidades. En este caso la función calcula la media de una serie de datos, pero podríamos hacer cálculos mucho más complejos.

Con estos ejemplos hemos visto diferentes formas de trabajar con funciones, subprocesos o subrutinas: utilizarlas para segmentar un código extenso, pasar parámetros por valor o por referencia, utilizar variables de retorno, emplear variables y/o matrices... lo que nos abre un amplio abanico de posibilidades.

Te dejo a continuación un archivo txt con los cuatro script desarrollados en PSeInt, por si deseas replicar lo expuesto en esta entrada y/o crear tus propias prácticas. Deberás descargarlo y copiar cada ejemplo en PSe Int para probarlo.
Pero lo cierto es que este análisis en PSeInt, si bien nos ayuda a entender el funcionamiento de la funciones desde la lógica de programación, no evita que tengamos que trabajarlas en función del lenguaje concreto que vayamos a utilizar. Por ello desarrollaré en las entradas siguientes cómo trabajar con funciones en OOo Basic y en Python.

lunes, 7 de noviembre de 2022

Introducción. Modelos de programación.

 Programación modular. Funciones

Cuando un programa, una aplicación o como sea pertinente o preferible llamarlo, es de cierta complejidad, ésta se refleja en su extensión en número de líneas, pero y también, en su complejidad. Dentro de estos dos parámetros podemos incluir la repetición de una serie de líneas de código con funcionalidad específica que se pueden identificar como unidades de acción.


Cuando tomamos estas unidades de acción como objeto de análisis y trabajo, y las aislamos del desarrollo del script principal y los llamamos desde éste, pasamos de trabajar siguiendo un modelo lineal de programación a un modelo modular. Este modelo supone una simplificación de los procedimientos de trabajo y una economía de tiempo, y es posible porque estos segmentos de código pueden ser llamados desde el algoritmo principal tantas veces como sea necesario, ahorrando así su repetición.

El modelo modular no rompe la lógica del modelo lineal, simplemente hace más eficiente nuestro algoritmo, más sostenible y más robusto. Además, esos segmentos pueden ser reutilizados en otros programas o aplicaciones, lo que incrementa nuestra eficiencia a la hora de desarrollar proyectos.

En líneas generales, el comportamiento de estos módulos que llamaremos funciones, en sentido general, por cumplir una función específica, presentan estos comportamientos:

  • En este esquema he representado el script principal y dos funciones que son empleadas por dicho script. La fecha verde representa el discurrir del algoritmo, que, como se puede ver, sigue un proceso lineal (de arriba abajo), tal y como representan esas flechas .
  • La función Secundario A toma el control del flujo en el momento en que es llamada (en ese momento es A quien determina qué hace nuestro algoritmo), finaliza su cometido y devuelve (por decirlo de alguna manera) el control al script principal, que reanuda el control del desarrollo del algoritmo
  • LA función Secundario B es llamada (utilizada) por el script principal, se ejecuta y devuelve al script principal el resultado de su propio procesamiento, pero no toma el control del desarrollo del algoritmo, que sigue siendo responsabilidad del script principal.
Esta simplificación nos permite apreciar el modo en que funciona la relación entre el programa principal y sus auxiliares; también nos muestra que existen diferentes formas de concretarse el papel de estos auxiliares, aunque depende del lenguaje de programación que estemos empleando cómo se concrete y de qué forma se exprese esta relación. 

En la siguiente entrada trabajaremos este esquema en PSeInt, pero antes es necesario decir que no estamos hablando de algo que nos resulte desconocido, ya que lo hemos estado utilizando en OrientAsLO de forma explícita, donde he creado código que utiliza diferentes formas del modelo Secundario A y algunas del secundario B. Pero también de forma implícita, tanto en OrientAsLO como en este blog hemos, ya que todos los lenguajes de programación cuentan con funciones incorporadas o integradas en el propio lenguaje (en la jerga informática: built-in functions), funciones que no hemos creado, pero que sí crearon los autores de los lenguajes para permitir al programador desarrollar sus aplicaciones. Algunas de estas funciones son de uso general y otras son específicas de determinados contenidos. Iremos viendo algunas de ellas, en función de nuestras necesidades, enriqueciendo así nuestro conocimiento sobre temas ya trabajados anteriormente. En concreto, y en lo que se refiere a Python, veremos las funciones disponibles en este lenguaje para la lectura de ficheros externos, tal y como prometí en una entrada reciente.