Funciones

Ejecutar este documento en forma dinámica: Binder

En el contexto de la programación, las funciones son secuencias de instrucciones agrupadas, que tienen un nombre y pueden invocarse a través de él.

Las partes que componen una función son:

  1. Un nombre, por el cual puede invocarse la función.
  2. Una lista de argumentos, que la función recibe como entrada para el cómputo que realizará.
  3. Documentación que informa lo que la función hace.
  4. Una secuencia de instrucciones que realizan un cómputo específico.
  5. Un resultado

Veamos la sintaxis de una función en Python con un ejemplo:

In [ ]:
def cuadrado(x):
    """Esta es mi primera función
    Recibe un número y devuelve el cuadrado del mismo.
    """
    print("Argumento: {:.4f}".format(x))
    return x * x

Aquí, usamos la palabra reservada (keyword) def para definir una función denominada cuadrado, que recibe un argumento delimitado por paréntesis.

Luego sigue una string que documenta la función, explicando qué hace, qué argumentos recibe y que resultados devuelve. Este sistema de documentación de Python se denomina Docstrings, y provee una forma conveniente de asociar documentación con módulos, funciones, clases y métodos.

Una docstring es una string literal que aparece en la primera línea en un módulo, función, clase o definición de método, y debe describir lo que la función hace, pero no cómo.

Las docstrings son accesibles a través del atributo doc de cualquier objeto de Python, o de la función help():

In [ ]:
print(cuadrado.__doc__)
In [ ]:
help(cuadrado)

Luego de la docstring sigue el cuerpo de la función, que como todo bloque de código en Python debe estar indentado. Finalmente, la sentencia return devuelve el resultado del cómputo realizado en la función. En nuestro caso de ejemplo, al invocar la función con un argumento, se muestra una string que informa el valor del argumento (con un formato específico), y luego el resultado devuelto con return:

In [ ]:
cuadrado(3)

La devolución de un valor por parte de una función es opcional. Podemos escribir funciones que no devuelvan ningún valor, sino que realicen un cómputo (lo que en algunos lenguajes se denomina procedimiento). Estrictamente, cuando no se incluye explícitamente un resultado utilizando return, la función devuelve None, lo que normalmente es ignorado por el intérprete (si realmente queremos ver el resultado podemos hacerlo con print()). Por ejemplo:

In [ ]:
def fib(n):
    """Imprime una sucesión de Fibonacci hasta n."""
    a, b = 0, 1
    while a < n:
        print(a, end=' ')
        a, b = b, a + b
    print()

fib(2019)
In [ ]:
print(fib(1))

Si queremos tener un resultado con una lista conteniendo la sucesión, podemos obtenerla con return:

In [ ]:
def fib2(n):
    """Devuelve una lista con la sucesión de Fibonacci hasta n."""
    resultado = []
    a, b = 0, 1
    while a < n:
        resultado.append(a)
        a, b = b, a + b
    return resultado

f101 = fib2(101)
print(f101)

Una cuestión muy común en los lenguajes de programación es cuándo pueden modificarse o no los argumentos de una función en el código de la misma (por ejemplo, argumentos por valor o por referencia en C/C++). En Python la regla es clara y muy simple: pueden modificarse dentro de la función los argumentos que son mutables (variables, listas, etc), mientras que no pueden cambiarse los que son inmutables (strings y tuplas):

In [ ]:
def funcion(a):
    a[0] = 1
    return a

funcion([5,4,3])
In [ ]:
funcion((5,4,3))

Argumentos posicionales y argumentos con nombre

La forma más simple en que las funciones toman argumento es cuando los mismos son posicionales:

In [ ]:
def fun_posicional(x, y):
    print('x = {:.2f}, y = {:.2f}'.format(x,y))
In [ ]:
fun_posicional(3,4)
In [ ]:
fun_posicional(4,3)

Los argumentos posicionales son los más simples y por defecto en Python. Python también permite definir valores por defecto en los argumentos con nombre (keywords):

In [ ]:
def fun_keyword(x=3, y=4):
    print('x = {:.2f}, y = {:.2f}'.format(x,y))

Es posible especificar los valores de los argumentos posicionales en dos formas. Las siguientes expresiones son válidas todas:

In [ ]:
fun_posicional(3,4)
fun_posicional(3, y=4)
fun_posicional(x=3, y=4)

Pero la siguiente no lo es:

In [ ]:
fun_posicional(3, x=4)

Como la descripción del error indica, Python interpreta que en la llamada a la función fun_posicional se definen dos valores para x. Para argumentos posicionales, el orden de los valores es importante.

Por otra parte, los argumentos con nombre pueden especificarse por su orden, como en los posicionales, pero también especificando sus nombres. Todas las expresiones siguientes son válidas:

In [ ]:
fun_keyword(3, 4)
fun_keyword(3, y=4)
fun_keyword(x=3, y=4)
fun_keyword(y=3, x=4)

Dado que las keywords tienen en su definición valores por defecto, esto permite invocar a la función con menos argumentos que los que aparecen en la definición, ya que se utilizarán los valores por defecto:

In [ ]:
fun_keyword()
fun_keyword(7)
fun_keyword(y=7)

Debe notarse que al omitir valores de argumentos, se asume que los que se pasan sin nombre van ocupando los valores en el orden en que se definen en la función. Por ejemplo, en fun_keyword(7), el 7 se asigna al primer argumento (x), mientras que el segundo toma su valor por defecto. Si queremos solo pasar un valor, y asignarlo al segundo, debemos necesariamente utilizar el nombre del segundo (y=7), y en ese caso x tomará su valor por defecto.

Es posible mezclar argumentos posicionales con argumentos con valores por defecto, siendo de este modo obligatorio invocar la función con el argumento posicional. Es importante destacar que los argumentos con nombre van después de los posicionales en la definición de la función. Por ejemplo, la función siguiente toma un argumento posicional y dos keywords:

In [ ]:
def fun_mix(x, opcion1='True', opcion2=''):
    print(x, opcion1, opcion2)
    pass

Las llamadas a la función siguientes son válidas:

In [ ]:
fun_mix(4)
fun_mix(4, opcion2='La Plata')
fun_mix(4, opcion1=False, opcion2='La Plata')

Pero las siguientes no:

In [ ]:
fun_mix()
In [ ]:
fun_mix(x=4, False)
In [ ]:
fun_mix(4, x=4)
In [ ]:
fun_mix(4, e=2.4142)

Número arbitrario de argumentos

Python permite llamar a una función con un número arbitrario de argumentos, que pueden ser posicionales o con nombre. Los primeros se organizan en forma de tupla, y llevan un * antes del nombre (en las documentaciones suelen referirse como *args). Antes del número variable de argumentos pueden incluirse argumentos posicionales y con nombre, mientras que los segundos llevan ** antes del nombre, y se agrupan como diccionario (suelen nombrarse como **kwargs en la documentación).

Es usual que estos argumentos de cantidad arbitraria de variables sean los últimos en la lista de parámetros formales, porque toman todo el remanente de argumentos que se pasan a la función. Cualquier parámetro que suceda luego de esta tupla solo puede ser con nombre, o sea que sólo se pueden usar como nombrados y no posicionales:

In [ ]:
def concatenar(*palabras, sep='/'):
    for p in palabras:
        print(p)
    print(10 * '-')
    return sep.join(palabras)
In [ ]:
concatenar('Crusaders', 'Hurricanes', 'Jaguares', 'Brumbies')
In [ ]:
concatenar('Crusaders', 'Jaguares', sep=' vs ')

El siguiente ejemplo muestra cómo utilizar un número variable de argumentos, tanto posicionales como con nombre:

In [ ]:
def variable_args(*args, **kwargs):
    print('Argumentos posicionales:', args)
    print('Argumentos con nombre:', kwargs)
In [ ]:
variable_args('uno', 'dos', x=1, y=2, z=3)
In [ ]:
variable_args('uno', 'dos', 'tres', x=1, z=3)

Al definir funciones con argumentos variables posicionales y con nombre, siempre deben ir primero los posicionales (*args antes que *kwargs).

A veces ocurre que una función espera recibir argumentos que ya los tenemos en una tupla o lista, por lo que necesitamos desempaquetarlos al pasarlos a la función. Para ello podemos utilizar * en la llamada a la función. Por ejemplo, la función range(inicio, fin) requiere como argumentos dos parámetros. Si ya tenemos esos valores en una lista, podemos desempaquetarlos del siguiente modo:

In [ ]:
args = [10, 20]
list(range(*args))

Funciones lambda

Pyhton permite definir pequeñas funciones sin nombre, con la palabra reservada lambda. Estas funciones solo permiten una sola instrucción, y suelen ser útiles como argumentos de otras funciones. Por ejemplo:

In [ ]:
f1 = lambda x: x**2

es equivalente a:

In [ ]:
def f2(x):
    return x**2
In [ ]:
f1(2), f2(2)

Un caso típico de uso de funciones lambda es cuando las utilizamos como argumento de otra función:

In [ ]:
list(map(lambda x: x**3, range(-3,4)))