6.1.3 Parámetros y argumentos
Si una función no dispusiera de valores de entrada estaría muy limitada en su actuación. Es por ello que los parámetros nos permiten variar los datos que consume una función para obtener distintos resultados. Vamos a empezar a crear funciones que reciben parámetros.
En este caso escribiremos una función que recibe un valor numérico y devuelve su raíz cuadrada:
Cuando llamamos a una función con argumentos, los valores de estos argumentos se copian en los correspondientes parámetros dentro de la función:
Figura 2: Parámetros y argumentos de una función
Ejercicio
Escriba una función en Python que reproduzca lo siguiente:
f (x,y) = x2 + y2
Ejemplo
- Entrada: 3 y 4
- Salida: 25
Argumentos posicionales
Los argumentos posicionales son aquellos argumentos que se copian en sus correspondientes parámetros en orden.
Vamos a mostrar un ejemplo definiendo una función que construye una «cpu» a partir de 3 parámetros:
Una posible llamada a la función con argumentos posicionales sería la siguiente:
Lo que ha sucedido es un mapeo directo entre argumentos y parámetros en el mismo orden que estaban definidos:
Pero es evidente que una clara desventaja del uso de argumentos posicionales es que se necesita recordar el orden de los argumentos. Un error en la posición de los argumentos puede causar resultados indeseados:
Argumentos nominales
En esta aproximación los argumentos no son copiados en un orden específico sino que se asignan por nombre a cada parámetro. Ello nos permite salvar el problema de conocer cuál es el orden de los parámetros en la definición de la función. Para utilizarlo, basta con realizar una asignación de cada argumento en la propia llamada a la función.
Veamos la misma llamada que hemos hecho en el ejemplo de construcción de la «cpu» pero ahora utilizando paso de argumentos nominales:
Se puede ver claramente que el orden de los argumentos no influye en el resultado final:
Argumentos posicionales y nominales
Python permite mezclar argumentos posicionales y nominales en la llamada a una función:
Pero hay que tener en cuenta que, en este escenario, los argumentos posicionales siempre deben ir antes que los argumentos nominales. Esto tiene mucho sentido ya que, de hacerlo así, Python no tendría forma de discernir a qué parámetro corresponde cada argumento:
Parámetros por defecto
Es posible especificar valores por defecto en los parámetros de una función. En el caso de que no se proporcione un valor al argumento en la llamada a la función, el parámetro correspondiente tomará el valor definido por defecto.
Siguiendo con el ejemplo de la «cpu», podemos asignar 2.0GHz como frecuencia por defecto. La definición de la función cambiaría ligeramente:
Llamada a la función sin especificar frecuencia de «cpu»:
Llamada a la función indicando una frecuencia concreta de «cpu»:
Importante: Los valores por defecto en los parámetros se calculan cuando se define la función, no cuando se ejecuta.
Ejercicio
Escriba una función factorial que reciba un único parámetro n y devuelva su factorial. El factorial de un número n se define como:
n! = n * (n — 1) * (n — 2) * … * 1
Ejemplo
- Entrada: 5
- Salida: 120
Modificando parámetros mutables
Nivel avanzado
Hay que tener cuidado a la hora de manejar los parámetros que pasamos a una función ya que podemos obtener resultados indeseados, especialmente cuando trabajamos con tipos de datos mutables.
Supongamos una función que añade elementos a una lista que pasamos por parámetro. La idea es que si no pasamos la lista, ésta siempre empiece siendo vacía. Hagamos una serie de pruebas pasando alguna lista como segundo argumento:
Aparentemente todo está funcionando de manera correcta, pero veamos qué ocurre en las siguientes llamadas:
Obviamente algo no ha funcionado correctamente. Se esperaría que result tuviera una lista vacía en cada ejecución. Sin embargo esto no sucede por estas dos razones:
- El valor por defecto se establece cuando se define la función.
- La variable result apunta a una zona de memoria en la que se modifican sus valores. Ejecución paso a paso a través de Python Tutor:
A riesgo de perder el parámetro por defecto, una posible solución sería la siguiente:
La forma de arreglar el código anterior utilizando un parámetro con valor por defecto sería utilizar un tipo de dato inmutable y tener en cuenta cuál es la primera llamada:
Empaquetar/Desempaquetar argumentos
Nivel avanzado
Python nos ofrece la posibilidad de empaquetar y desempaquetar argumentos cuando estamos invocando a una función, tanto para argumentos posicionales como para argumentos nominales.
Y de este hecho se deriva que podamos utilizar un número variable de argumentos en una función, algo que puede ser muy interesante según el caso de uso que tengamos.
Empaquetar/Desempaquetar argumentos posicionales
Si utilizamos el operador * delante del nombre de un parámetro posicional, estaremos indicando que los argumentos pasados a la función se empaqueten en una tupla:
También podemos utilizar esta estrategia para establecer en una función una serie de parámetros como requeridos y recibir el resto de argumentos como opcionales y empaquetados:
Existe la posibilidad de usar el asterisco * en la llamada a la función para desempaquetar los argumentos posicionales:
Empaquetar/Desempaquetar argumentos nominales
Si utilizamos el operador ** delante del nombre de un parámetro nominal, estaremos indicando que los argumentos pasados a la función se empaqueten en un diccionario:
Al igual que veíamos previamente, existe la posibilidad de usar doble asterisco ** en la llamada a la función, para desempaquetar los argumentos nominales:
Forzando modo de paso de argumentos
Si bien Python nos da flexibilidad para pasar argumentos a nuestras funciones en modo posicional o nominal, existen opciones para forzar a que dicho paso sea obligatorio en una determinada modalidad.
Argumentos sólo posicionales
Nivel avanzado
A partir de Python 3.8 se ofrece la posibilidad de obligar a que determinados parámetros de la función sean pasados sólo por posición.
Para ello, en la definición de los parámetros de la función, tendremos que incluir un parámetro especial / que delimitará el tipo de parámetros. Así, todos los parámetros a la izquierda del delimitador estarán obligados a ser posicionales:
Figura 3: Separador para especificar parámetros sólo posicionales
Ejemplo:
Argumentos sólo nominales
Nivel avanzado
A partir de Python 3 se ofrece la posibilidad de obligar a que determinados parámetros de la función sean pasados sólo por nombre.
Para ello, en la definición de los parámetros de la función, tendremos que incluir un parámetro especial * que delimitará el tipo de parámetros. Así, todos los parámetros a la derecha del separador estarán obligados a ser nominales:
Ejemplo:
Figura 4: Separador para especificar parámetros sólo nominales
Fijando argumentos posicionales y nominales
Si mezclamos las dos estrategias anteriores podemos forzar a que una función reciba argumentos de un modo concreto.
Continuando con ejemplo anterior, podríamos hacer lo siguiente:
Argumentos mutables e inmutables
Nivel intermedio
Igual que veíamos en la incidencia de parámetros por defecto con valores mutables, cuando realizamos modificaciones a los argumentos de una función es importante tener en cuenta si son mutables (listas, diccionarios, conjuntos, …) o inmutables (tuplas, enteros, flotantes, cadenas de texto, …) ya que podríamos obtener efectos colaterales no deseados:
Funciones como parámetros
Nivel avanzado
Las funciones se pueden utilizar en cualquier contexto de nuestro programa. Son objetos que pueden ser asignados a variables, usados en expresiones, devueltos como valores de retorno o pasados como argumentos a otras funciones.
Veamos un primer ejemplo en el que pasamos una función como argumento:
Veamos un segundo ejemplo en el que pasamos, no sólo una función como argumento, sino los valores con los que debe operar: