Link Search Menu Expand Document

Programaci贸n Funcional en Python

En pocas palabras, la programaci贸n funcional es un paradigma de programaci贸n distinto al tradicional estructurado u orientado a objetos al que solemos estar acostumbrados. Se basa principalmente en el uso de funciones, siendo las mismas practicamente la 煤nica herramienta que el lenguaje nos ofrece. Por ello, en lenguajes puramente funcionales como Haskell no existen bucles for o while.

驴Un lenguaje de programaci贸n sin bucles? Aunque pueda parecer una locura, tambi茅n tiene sus ventajas, y ofrece ciertas caracter铆sticas muy importantes que veremos a continuaci贸n.

A pesar de que Python no es un lenguaje puramente funcional, nos ofrece algunas primitivas propias de lenguajes funcionales, como map, filter y reduce. Todas ellas ofrecen una alternativa al uso de bucles para resolver ciertos problemas. Veamos unos ejemplos.

map en Python

La funci贸n map toma dos entradas:

  • Una lista o iterable que ser谩 modificado en una nueva.
  • Una funci贸n, que ser谩 aplicada a cada uno de los elementos de la lista o iterable anterior.

Nos devuelve una nueva lista donde todos y cada uno de los elementos de la lista original han sido pasados por la funci贸n.

map(funcion_a_aplicar, entrada_iterable)

Imaginemos que queremos multiplicar por dos todos los elementos de una lista. La primera forma que se nos podr铆a ocurrir ser铆a la siguiente. N贸tese que tambi茅n podr铆a usarse list comprehension, pero eso lo dejamos para otro art铆culo.

lista = [1, 2, 3, 4, 5]
lista_pordos = []
for l in lista:
    lista_pordos.append(l*2)

print(lista_pordos)
# [2, 4, 6, 8, 10]

Pero veamos como darle un enfoque funcional. Como hemos dicho, map toma una funci贸n y un iterable como entrada, aplicando la funci贸n a cada elemento. Entonces podemos definir una funci贸n por_dos, que pasaremos a map junto con nuestra lista inicial.

lista = [1, 2, 3, 4, 5]

def por_dos(x):
    return x * 2

lista_pordos = map(por_dos, lista)

print(list(lista_pordos))
# [2, 4, 6, 8, 10]

Como podemos observar, ya no usamos bucles. Simplemente le pasamos a map la funci贸n y la lista y ya hace el trabajo por nosotros. Dado que por_dos se trata de una funci贸n muy sencilla, es posible usar funciones lambda para compactar un poco el c贸digo, quedando lo siguiente:

lista = [1, 2, 3, 4, 5]
lista_pordos = map(lambda x: 2*x, lista)

print(list(lista_pordos))
# [2, 4, 6, 8, 10]

filter en Python

La funci贸n filter tambi茅n recibe una funci贸n y una lista pero el resultado es la lista inicial filtrada. Es decir, se pasa cada elemento de la lista por la funci贸n, y s贸lo si su resultado es True, se incluye en la nueva lista.

filter(funcion_filtrar, entrada_iterable)

Al igual que hac铆amos antes, usamos las funciones lambda que nos permiten declarar y asignar una funci贸n en la misma l铆nea de c贸digo. En el siguiente ejemplo filtramos los n煤meros pares.

lista = [7, 4, 16, 3, 8]
pares = filter(lambda x: x % 2 == 0, lista)

print(list(pares))
# [4, 16, 8] 

N贸tese que el siguiente c贸digo ser铆a equivalente:

lista = [7, 4, 16, 3, 8]

def es_par(x):
    return x % 2 == 0

pares = filter(es_par, lista)

print(list(pares))
# [4, 16, 8]

Una vez m谩s, resaltar que no estamos usando bucles. Simplemente damos la entrada y la funci贸n a aplicar a cada elemento, y filter se encarga del resto. Esta es una de las caracter铆sticas clave de la programaci贸n funcional. Se centra m谩s en el qu茅 hacer que en el c贸mo hacerlo. Para ello se usan funciones predefinidas como las que estamos viendo, a las que s贸lo tenemos que pasar las entradas y hacer el trabajo por nosotros.

reduce en Python

Por 煤ltimo, podemos usar reduce para reducir todos los elementos de la entrada a un 煤nico valor aplicando un determinado criterio. Por ejemplo, podemos sumar todos los elementos de una lista de la siguiente manera.

from functools import reduce
lista = [1, 2, 3, 4, 5]
suma = reduce(lambda acc, val: acc + val, lista)
print(suma) # 15

Lo que podr铆a reescribirse usando la funci贸n add:

from operator import add
from functools import reduce
lista = [1, 2, 3, 4, 5]
suma = reduce(add, lista)
print(suma) # 15

O tambi茅n los podemos multiplicar, modificando la funci贸n lambda.

from functools import reduce
lista = [1, 2, 3, 4, 5]
multiplicacion = reduce(lambda acc, val: acc * val, lista)
print(multiplicacion) # 120

Es importante notar que la funci贸n recibe dos argumentos: el acumulador y cada uno de los valores de la lista.

  • El acumulador es el valor devuelto en la iteraci贸n anterior, que va acumulando un resultado hasta que llegamos al final.
  • El valor es cada uno de los elementos de nuestra lista, que en nuestro caso vamos a帽adiendo al acumulador.

El uso de reduce es especialmente 煤til cuando tenemos por ejemplo una lista de diccionarios y queremos sumar todos los valores de un campo en concreto. Veamos un ejemplo donde calculamos la edad media de varias personas.

from functools import reduce
personas = [
    {'Nombre': 'Alicia', 'Edad': 22},
    {'Nombre': 'Bob', 'Edad': 29},
    {'Nombre': 'Charlie', 'Edad': 33}
]
suma_edad = reduce(lambda total, p: total + p['Edad'], personas, 0)
print(suma_edad/len(personas)) # 28.0

N贸tese que llamamos a reduce con un valor adicional 0, que es el valor inicial del acumulador. Una vez m谩s, hemos resuelto un problema en el que tradicionalmente usar铆amos bucles con las primitivas de la programaci贸n funcional.

Caracter铆sticas de la Programaci贸n Funcional

Una vez entendido el funcionamiento de map, filter y reduce, ya tenemos unas nociones b谩sicas de la programaci贸n funcional. Sus caracter铆sticas m谩s importantes son las siguientes:

  • Los lenguajes de programaci贸n puramente funcionales carecen de bucles for y while.
  • Se dice que en la programaci贸n funcional, las funciones son 鈥渃iudadanas de primera clase鈥, lo que nos viene a decir que pr谩cticamente todo se hace con funciones, y no con bucles.
  • La programaci贸n funcional prefiere tambi茅n las funciones puras, es decir, funciones sin side effects. Las funciones puras no dependen de variables externas o globales. Esto significa que para las mismas entradas, siempre se producen las mismas salidas.
  • Por otro lado, en la programaci贸n funcional se prefiere variables inmutables, lo que significa que una vez creadas no pueden ser modificadas. Esto es algo que verdaderamente ahorra problemas, aunque la eficiencia puede ser discutible.
  • En general, se considera que los lenguajes de programaci贸n funcionales son m谩s seguros, y ofrecen formal verification.

Por 煤ltimo, resaltar una vez m谩s que aunque Python no es un lenguaje puramente funcional, ofrece algunas funciones propias de lenguajes funcionales como map, filter y reduce. Si est谩s interesado en m谩s, puedes echar un vistazo a el paquete itertools.

Ejemplos Programaci贸n Funcional

Dada una lista de personas almacenadas en diccionarios:

  • Filtrar de acuerdo al sexo
  • Y calcular la media
from functools import reduce
personas = [
    {'Nombre': 'Alicia', 'Edad': 22, 'Sexo': 'F'},
    {'Nombre': 'Bob', 'Edad': 25, 'Sexo': 'M'},
    {'Nombre': 'Charlie', 'Edad': 33, 'Sexo': 'M'},
    {'Nombre': 'Diana', 'Edad': 15, 'Sexo': 'F'},
    {'Nombre': 'Esteban', 'Edad': 30, 'Sexo': 'M'},
    {'Nombre': 'Federico', 'Edad': 44, 'Sexo': 'M'},
]

hombres = list(filter(lambda x: x['Sexo'] == 'M', personas))
suma_edades = reduce(lambda suma, p: suma + p['Edad'], hombres, 0)
media_edad = suma_edades/(len(hombres))
print(media_edad) # 33.0

Tal vez no muy legible, pero todo lo anterior se podr谩 expresar en una 煤nica l铆nea de c贸digo.

media_edades = reduce(lambda suma, p: suma + p['Edad'], filter(lambda x: x['Sexo'] == 'M', personas), 0) / len(list(filter(lambda x: x['Sexo'] == 'M', personas)))
print(media_edades) # 33.0