FUNCIONES DEFINIDAS POR EL USUARIO IV: ARGUMENTOS

VISTA DEL TEIDE DESDE LOS PINARES DE LA LADERA ALTA DE LOS REALEJOS-ICOD.
      En este capítulo abordaremos el caso de contar en nuestras funciones definidas un número indefinido o arbitrario (desconocido de antemano por el programador) de argumentos, así como el desempaquetado de argumentos.

      CIERTAMENTE, LA INTENCIÓN ASÍ EXPRESADA, MÁS LARGA QUE UN DÍA SIN PAN, IMPONE UN POCO (O BASTANTE). PERO NO DEBEMOS PREOCUPARNOS: NO POR SU TAMAÑO ES MAYOR SU COMPLEJIDAD. NI MUCHO MENOS. 😉


      Hasta el momento hemos construido funciones definidas que operan con un número conocido de argumentos, bien porque los incluyamos expresamente, o bien como datos "crudos", o como nombres de variables que los almacenan, o bien indirectamente, obligados por el paso de parámetros. Para refrendarlo no tenemos más que repasar los ejemplos que hemos visto hasta ahora. En ocasiones, hemos construido funciones def con los paréntesis, la zona de parámetros, como también se la conoce, vacíos, sin parámetros/argumentos, proporcionándolos nosotros mismos a medida que los vamos necesitando en paralelo al flujo de ejecución que, recordemos, siempre va de arriba a abajo salvo que una herramienta de control de flujo o una llamada a otra función apunten a otra dirección o a una bifurcación, en el caso de los condicionales y los loops, o un cambio de sentido (de abajo a arriba) cuando invocamos a una función que hayamos modelizado previamente en nuestro código.
Pero también Python tiene respuesta para aquellas funciones que esperan un número indefinido, arbitrario o desconocido, como queramos llamarlo, de argumentos, o que un grupo de argumentos procedentes, pongamos por caso, de un objeto de tipo lista, puedan ser insertos  en una función sin necesidad de llamarlos a todos por su bendito nombre, tal y como ocurre en el ejemplo que mostramos a continuación.



   
      HAY DIFERENCIA, ¿NO? ¡Y ESO QUE HEMOS PUESTO SÓLO TRES ELEMENTOS EN UNA LISTA! ¿Y SI PONEMOS VEINTE?, ¿O CINCUENTA? MENUDO LÍO, ¿VERDAD? FIJÉMONOS CÓMO HEMOS RESUELTO EL EMBOLADO: AÑADIENDO UN ASTERISCO A LA DERECHA DEL ARGUMENTO. SABEMOS QUE EN PROGRAMACIÓN, UN ASTERISCO ES UN CARÁCTER COMODÍN QUE APUNTA A TODOS LOS ELEMENTOS DE UNA SECUENCIA DE ALGO, LO QUE NOS PERMITE LLAMARLOS AÚN SIN SABER NI QUIÉNES NI CUÁNTOS SON. SE ME OCURRIÓ A MÍ, YA VEN. ¡GUAU!



Con el operador *, denominado para el caso "operador de desempaquetado", siempre a la derecha del argumento y nunca a la izquierda pues, en tal caso, y aunque parezca una explicación de perogrullo, no debemos dejar de mencionarlo, estaríamos proponiendo una simple multiplicación, una operación aritmética, que suscitará el lanzamiento de una excepción, podemos traer a la función TODOS los elementos de una colección, por ejemplo, de una lista, y operar con todos ellos. A esta acción se la llama "desempaquetado de una lista". Python tomará por medio del operador de desempaquetado los ítems de una colección y los llevará a la función en forma de tupla, ( ), volviendo la función cumplidamente operativa, tal y como acabamos de ver.
Si hacemos un poco de memoria, recordaremos que el "operador de desempaquetado", *, desempeña en las funciones definidas por el usuario una función similar a la que ejerce en las importaciones (v.  IMPORTACIONES. HACIENDO CRECER TU PROYECTO.): importa todos los archivos del módulo que seleccionemos, de la misma manera que * importa a la función def todos los ítems de una colección.
El "operador de desempaquetado" aún nos permite trabajar con funciones sin conocer el número de argumentos que puede llevar, lo cual viene de perilla para scripts que van aportando datos de manera dinámica, sobre la marcha, según se van ejecutando:



Esta sencillísima función contiene un parámetro, 'items', que por mediación del operador * llevará como argumento una tupla con sus elementos integrantes dispuestos de acuerdo a los elementos que se le pasen: si 7, 7; si 100, 100; si 19, 19; si 1, 1; etc.

PARTE DE UN LAGAR DESCUBIERTO JUNTO A UN CASERÍO EN EL CAUCE DEL BARRANCO DEL CERCADO DE SAN ANDRÉS, SUR DEL MACIZO DE ANAGA.

Resumiendo, el "operador de desempaquetado" colocado siempre a la izquierda del argumento, nos resultará útil para dos situaciones:

  • Para tomar todos los ítems de una colección y pasarlos como argumentos de la función.
  • Cuando desconocemos a priori los argumentos que le vamos a pasar a la función.
Pero Python, como no podía ser menos, nos sigue sorprendiendo. Podemos colocar argumentos de palabra clave (las asignaciones que podíamos pasar como argumentos, como ejemplo, c=4, texto="cero", etc.). Imaginemos, por ejemplo, que queremos hallar el cociente de la división entre 3 de la suma de los números enteros de una lista dada. Podemos proceder como en en el ejemplo de abajo:



Como podemos ver, hemos introducido una palabra clave (div=3) como argumento de la función junto a la llamada genérica a todos los ítems de la lista mediante el operador *.
A continuación proponemos otro ejemplo más, esta vez, sin conocer de antemano los argumentos que vamos a pasar a la función, solicitando la potencia la cubo de cada uno de los números que pasemos, precisamente, como argumentos.



Pues a pesar de todo esto, el operador * aún nos reserva otra cualidad más (¡Qué bien preparada viene este operador, ¿eh?!). Podemos usar el operador * como parámetro, así, tal cual. ¿Por qué? ¿Para qué? Para indicar que puede no haber "argumentos posicionales" (argumentos que se van tomando por su 'posición' por orden de izquierda a derecha, por ejemplo, def num(7, "a", 0.10) tiene tres argumentos posicionales: en la posición 1 tenemos el entero 7; en la posición 2, la cadena "a"; y en la posición 3, el decimal 0.10) a continuación del asterisco salvo, en todo caso, un argumento de palabra-clave. Veámoslo a continuación.



Y fijémonos que sucede cuando intentamos añadir un argumento posicional más del que nos limita el operador * cuando invocamos a la función.



Qué duda cabe que si colocamos el operador al comienzo de la línea de argumentos, no nos quedará más opción que usar palabras-clave lo que resulta, según para qué, bastante útil.



ACANTILADOS DE EL SAUZAL, CON EL BARRIO DE LA BARANDA ASOMÁNDOSE AL MAR. COSTA NORTE DE TENERIFE.

      EL DOBLE ASTERISCO

   
      Éramos pocos y parió la abuela: el insertar un doble asterisco a la izquierda de un nombre también tiene significación en Python. Se representa tal cual, **nombre, y cuando recurrimos a él tenemos que ubicarla al final de una línea de argumentado: debe ser siempre el elemento final en tanto que el intérprete de Python "lee" en un orden jerárquico estricto con el propósito loable de minimizar errores y preservar de este modo una estructura sintáctica coherente, común para todos los programadores. Con este nuevo operador, el doble asterisco, Python construye un mapa o diccionario.
Tengamos en cuenta el concepto siguiente: de la misma manera que podemos desempaquetar una secuencia (por ejemplo, una lista) a través del operador asterisco, *, para llevar todos los ítems que la forma al argumentado de una función (v. EJEMPLO 1), podemos hacer lo propio con un operador de desempaquetado de agrupación que así se llama en puridad al doble asterisco, **, para pasar un diccionario como argumento de una función.

EJEMPLO 1



EJEMPLO 2



Fijémonos en EJEMPLO 2 que en 1. pasamos como argumento de la función asignaciones variables=valor, mientras que en la resolución de la función devuelve un diccionario, con sus correspondientes valores key:value, como comprobamos en 2.
También podemos verlo a la inversa, partiendo de un diccionario, como en el ejemplo que mostramos a continuación con tres posibilidades de muestra: sólo las claves (keys), sólo los valores (values), y claves y valores (keys/values) a la vez:


También podemos encontrarnos con una función que incorpore varios argumentos posicionales más entre paréntesis:



En esta ocasión hemos utilizado un argumento posicional obligatorio, pais, un operador de desempaquetado que convoca a todos los elementos de la lista com_atnmas, *com_atnmas, y un operador de desempaquetado de agrupación, **capitales, para entresacar datos del diccionario capitales. Como podemos ver, hemos incluido en la función un bucle for/in, 1., que recorra (itere) el diccionario de modo que le aplique un formateo (format()), 2., esto es, recordemos de capítulos anteriores, una forma de presentar la información al usuario, muy similar a la que empleamos al mostrar la opción final en el ejemplo precedente.
Normalmente, el recurso al operador de desempaquetado de agrupación en el argumentado de una función  exige la inclusión de la sentencia str.format() con su sintaxis característica. Sin embargo, podemos probar a resolver el ejemplo anterior conservando el bucle for/in y sustituyendo la línea que hemos señalado como 2. por esta otra: print(item, capitales[item]) sin que la devolución difiera mucho.
Veamos otro ejemplo más:


A modo de resumen, nos vendría bien echar un vistazo al siguiente esquema:




Los **args se almacenan como tuplas mientras que los **kwargs lo hacen como diccionarios.


         FUNCIONES CON EL MISMO NOMBRE:

   
      Python nos permite construir funciones con un mismo nombre, bien con el mismo o con un número distinto de argumentos. En caso el de que así fuera, del mismo modo que sucede con las variables, Python recogerá la última función en ser definida, la última que hayamos construido con el mismo nombre (a vueltas con el concepto de "tipado dinámico", ¿hacemos memoria?). Por este motivo, tengamos cuidado con los nombres que asignemos a nuestras funciones.


      NOS VENDRÍA MUY, PERO QUE MUY BIEN, PARA PROGRAMAS COMPLEJOS, CON ABUNDANTE CÓDIGO, CONTAR A MANO CON  UN BUEN DIRECTORIO DE FUNCIONES QUE NOS EVITE DUPLICIDADES INNECESARIAS.





ADVERTENCIA:

     
      Cuando vayamos a pasar parámetros en la zona de parámetros de la cabecera (header) de la función debemos tener cuidado que cuando éstos pasen a argumentos (es decir, cuando los parámetros se concreten en variables que ya tienen asignados valores o en valores propiamente dichos directamente) no sean de tipo mutable, recordemos, que se puedan modificar como sería el caso, por ejemplo, de un tipo de dato lista, pues podríamos obtener resultados no deseados. Habida cuenta de que una lista, como cualquier otra secuencia mutable, es acumulativa, esto es, una vez creada puede ir almacenando todo lo que queramos en ella mientras la sintaxis sea correcta, podríamos codificar el ejemplo inferior y obtener la siguiente devolución:


Como vemos, nuestra lista x va llenándose de elementos con cada llamada a la función. Sin embargo, igual queríamos que sólo tuviéramos una lista con un único elemento cada vez, un único elemento por llamada. Podemos solucionar esto sobre la marcha inicializando el valor de la secuencia mutable (nuestra lista) a None en la zona de parámetros.


Básicamente, lo que hacemos aquí es establecer una condición: que la lista, con cada llamada a la función, sea igual a None. Si esto es así, como por defecto consignamos en la zona de parámetros, esto es, que lista valga None, si, en efecto, la condición se cumple, se crea una lista vacía de las de toda la vida como se aprecia en la tercera línea de código, y en ella podremos insertar los datos que queramos, procediendo como lo hacemos en la cuarta línea. Si volvemos a llamar a la función, como la variable lista vale None, nuestra lista original desaparece y nuestra variable lista vuelve a guardar una lista vacía, cosa que ocurre de manera recurrente con cada nueva llamada a la función.
A continuación mostramos una variante, esta vez con algo menos de código y usando el método de "limpieza" de las listas. Pero tengamos aquí en cuenta que, en esta ocasión, lo que hace la función es imprimir el resultado en pantalla ANTES DE VACIAR LA LISTA porque, lo que la función devuelve a través de la cláusula return no es una lista con un dato como en el ejemplo anterior sino una lista vacía que como tal no vemos:


_____________________________________________________________________________________________________________

      Recomendamos estudiar a continuación las siguientes dos entradas que proponemos ahondando más en el "apasionante" mundo de las funciones: por un lado, LAS FUNCIONES LAMBDA, heredada de la programación funcional, y LAS FUNCIONES RECURSIVAS, que representa un tipo de función autolimitadora, ambas en T2.
Así mismo, proponemos profundizar en el manejo de funciones definidas por el usuario en una página que publicaremos posteriormente dedicada a los GENERATORS.
Veamos unos pocos ejercicios para practicar:

T4. BLOQUE DE EJERCICIOS 11. SOLUCIONES.

ANAGA CUBRIÉNDOSE DE NIEBLA GRACIAS A LOS VIENTOS ALISIOS QUE ENTRAN POR EL NE DE TENERIFE.

No hay comentarios:

Publicar un comentario