Funciones

A medida que nuestro código fuente crece, surge la necesidad de organizarlo. Las funciones nos ayudan en la organización.

# Usa 'def' para crear nuevas funciones
def add(x, y):
    print("x es {} y y es {}".format(x, y))
    return x + y    # Retorna valores con una la declaración return

# Llamando funciones con parámetros
add(5, 6) #=> imprime "x es 5 y y es 6" y retorna 11

# Otra forma de llamar funciones es con argumentos de palabras claves
add(y=6, x=5)   # Argumentos de palabra clave pueden ir en cualquier orden.

En el ejemplo anterior creamos una función add que recibe los argumentos a y b. Sin embargo, podemos recibir un número variable de argumentos (a, b, c...)

# Puedes definir funciones que tomen un número variable de argumentos
def varargs(*args):
    return args

varargs(1, 2, 3) #=> (1,2,3)


# Puedes definir funciones que toman un número variable de argumentos
# de palabras claves
def keyword_args(**kwargs):
    return kwargs

# Llamémosla para ver que sucede
keyword_args(pie="grande", lago="ness") #=> {"pie": "grande", "lago": "ness"}


# Puedes hacer ambas a la vez si quieres
def todos_los_argumentos(*args, **kwargs): # *args son argumentos posicionales en los que el orden importa
    print(args)
    print(kwargs)
"""
todos_los_argumentos(1, 2, a=3, b=4) imprime:
    (1, 2)
    {"a": 3, "b": 4}
"""

# ¡Cuando llames funciones, puedes hacer lo opuesto a varargs/kwargs!
# Usa * para expandir tuplas y usa ** para expandir argumentos de palabras claves.
args = (1, 2, 3, 4)
kwargs = {"a": 3, "b": 4}
todos_los_argumentos(*args) # es equivalente a foo(1, 2, 3, 4)
todos_los_argumentos(**kwargs) # es equivalente a foo(a=3, b=4)
todos_los_argumentos(*args, **kwargs) # es equivalente a foo(1, 2, 3, 4, a=3, b=4)

Lambdas

También conocidas como funciones anónimas porque no tienen un nombre.

  1. Se escribe en una única sentencia (línea).
  2. No tiene nombre (anónima).
  3. Su cuerpo conlleva un return implícito.
  4. Puede recibir cualquier número de parámetros.
(lambda x: x > 0)(3) #=> True

La función anterior es equivalente a:

def is_positivo(x):
    return x > 0

De hecho, como las lambdas son funciones, podemos asignarlas a variables y usarlas como cualquier otra función.

is_positivo = lambda x: x > 0
is_positivo(-1) #=> False

Generadores

Es una forma sencilla de crear iteradores. Es una función que usando la palabra clave yield retorna un valor y conserva su estado, permitiendo que la función continúe donde se quedó.

def contador(max):
    i = 0
    while i < max:
        yield i
        i += 1

for i in contador(5):
    print(i)

Closures

En Python todo es un Objeto, ¡las funciones también!

# Python tiene funciones de primera clase
def crear_suma(x):
    def suma(y):
        return x + y
    return suma

sumar_10 = crear_suma(10) # Podemos guardar en una variable una función definida por nosotros.
sumar_10(3) #=> 13

# Podemos usar listas por comprensión para mapeos y filtros
[sumar_10(i) for i in [1, 2, 3]]  #=> [11, 12, 13]
[x for x in [3, 4, 5, 6, 7] if x > 5] #=> [6, 7]
# también hay diccionarios
{k:k**2 for k in range(3)} #=> {0: 0, 1: 1, 2: 4}
# y conjuntos por comprensión
{c for c in "la cadena"} #=> {'d', 'l', 'a', 'n', ' ', 'c', 'e'}

Funciones de orden superior

Se conocen en inglés como higher-order functions y son funciones que reciben otras funciones como argumentos. En Python, todo es un objeto, incluidas las funciones. Esto significa que, al igual que podemos asignar una función a una variable, también podemos pasar funciones como argumentos a otras funciones. Usando esta estrategia podríamos explotar el paradigma de la programación funcional con python. En concreto, python nos provee de algunas funciones de orden superior como lo son map y filter.

def make_multiplier_of(n):
    def multiplier(x):
        return x * n
    return multiplier # factoría de funciones

m3 = make_multiplier_of(3)
list(map(m3, [1,2,3])) #=> [3, 6, 9]
list(filter(lambda x: x > 5, [3, 4, 5, 6, 7])) #=> [6, 7]

Recursividad

Una función puede llamarse a sí misma. Aquí tienes un ejemplo de cómo se vería una función recursiva que calcula el factorial de un número:

def factorial(x):
    if x == 1: # Esto es el caso base
        return 1
    else:
        return x * factorial(x - 1)

factorial(5) #=> 120

En las funciones recursivas, es importante tener un caso base que detenga la recursión. En caso contrario, la función se llamará a sí misma infinitamente y Python lanzará un error.