Esta notebook fue creada originalmente como un blog post por Raúl E. López Briega en Mi blog sobre Python. El contenido esta bajo la licencia BSD.
Es bien sabido que existen muchas formas de resolver un mismo problema, esto, llevado al mundo de la programación, a generado que existan o co-existan diferentes estilos en los que podemos programar, los cuales son llamados generalmente paradigmas. Así, podemos encontrar basicamente 4 paradigmas principales de programación:
La mayoría de los lenguajes modernos son multiparadigma, es decir, nos permiten programar utilizando más de uno de los paradigmas arriba descritos. En este artículo voy a intentar explicar como podemos aplicar la Programación Funcional con Python.
En estos últimos años hemos visto el resurgimiento de la Programación Funcional, nuevos lenguajes como Scala y Apple Swift ya traen por defecto montones de herramientas para facilitar el paradigma funcional. La principales razones del crecimiento de la popularidad de la Programación Funcional son:
Antes de comenzar con ejemplos les voy a mencionar algunos de los modulos que que nos facilitan la Programación Funcional en Python, ellos son:
Cuando tenemos que realizar operaciones sobre listas, en lugar de utilizar los clásicos loops, podemos utilizar las funciones Map, Reduce, Filter y Zip.
La función Map nos permite aplicar una operación sobre cada uno de los items de una lista. El primer argumento es la función que vamos a aplicar y el segundo argumento es la lista.
In [1]:
#creamos una lista de números del 1 al 10
items = list(xrange(1, 11))
items
Out[1]:
In [2]:
#creamos una lista de los cuadrados de la lista items.
#forma imperativa.
cuadrados = []
for i in items:
cuadrados.append(i ** 2)
cuadrados
Out[2]:
In [3]:
#Cuadrados utilizando Map.
#forma funcional
cuadrados = map(lambda x: x **2, items)
cuadrados
Out[3]:
Como podemos ver, al utilizar map las líneas de código se reducen y nuestro programa es mucho más simple de comprender. En el ejemplo le estamos pasando a map una función anónima o lambda. Esta es otra característica que nos ofrece Python para la Programación Funcional. Map también puede ser utilizado con funciones de más de un argumento y más de una lista, por ejemplo:
In [4]:
#importamos pow.
from math import pow
In [5]:
#como vemos la función pow toma dos argumentos, un número y su potencia.
pow(2, 3)
Out[5]:
In [6]:
#si tenemos las siguientes listas
numeros = [2, 3, 4]
potencias = [3, 2, 4]
In [7]:
#podemos aplicar map con pow y las dos listas.
#nos devolvera una sola lista con las potencias aplicadas sobre los números.
potenciados = map(pow, numeros, potencias)
potenciados
Out[7]:
In [8]:
#Sumando los valores de la lista items.
#forma imperativa
suma = 0
for i in items:
suma += i
suma
Out[8]:
In [9]:
#Suma utilizando Reduce.
#Forma funcional
from functools import reduce #en python3 reduce se encuentra en modulo functools
suma = reduce(lambda x, y: x + y, items)
suma
Out[9]:
La función Reduce también cuenta con un tercer argumento que es el valor inicial o default. Por ejemplo si quisiéramos sumarle 10 a la suma de los elementos de la lista items, solo tendríamos que agregar el tercer argumento.
In [10]:
#10 + suma items
suma10 = reduce(lambda x, y: x + y, items, 10)
suma10
Out[10]:
In [11]:
#Numeros pares de la lista items.
#Forma imperativa.
pares = []
for i in items:
if i % 2 ==0:
pares.append(i)
pares
Out[11]:
In [12]:
#Pares utilizando Filter
#Forma funcional.
pares = filter(lambda x: x % 2 == 0, items)
pares
Out[12]:
In [13]:
#Ejemplo de zip
nombres = ["Raul", "Pedro", "Sofia"]
apellidos = ["Lopez Briega", "Perez", "Gonzalez"]
In [14]:
#zip une cada nombre con su apellido en una lista de tuplas.
nombreApellido = zip(nombres, apellidos)
nombreApellido
Out[14]:
In [15]:
#Funcion que no sigue las buenas practias de la programacion funcional.
#Esta funcion tiene efectos secundarios, ya que modifica la lista que se le pasa como argumento.
def cuadrados(lista):
for i, v in enumerate(lista):
lista[i] = v ** 2
return lista
Deberíamos escribir código como el siguiente, el cual evita los efectos secundarios:
In [16]:
#Version funcional de la funcion anterior.
def fcuadrados(lista):
return map(lambda x: x ** 2, lista)
In [17]:
#Aplicando fcuadrados sobre items.
fcuadrados(items)
Out[17]:
In [18]:
#items no se modifico
items
Out[18]:
In [19]:
#aplicando cuadrados sobre items
cuadrados(items)
Out[19]:
In [20]:
#Esta función tiene efecto secundario.
#items fue modificado por cuadrados.
items
Out[20]:
Al escribir funciones que no tengan efectos secundarios nos vamos a ahorrar muchos dolores de cabeza ocasionados por la modificación involuntaria de objetos.
Algunas de las cosas que nos ofrece este modulo son: Estructuras de datos inmutables, lambdas al estilo de Scala, lazy evaluation de streams, nuevas Funciones de orden superior, entre otras.
In [21]:
#Lambdas al estilo scala
from fn import _
(_ + _)(10, 3)
Out[21]:
In [22]:
items = list(xrange(1,11))
In [23]:
cuadrados = map( _ ** 2, items)
cuadrados
Out[23]:
In [24]:
#Streams
from fn import Stream
s = Stream() << [1,2,3,4,5]
s
Out[24]:
In [25]:
list(s)
Out[25]:
In [26]:
s[1]
Out[26]:
In [27]:
s << [6, 7, 8, 9]
Out[27]:
In [28]:
s[6]
Out[28]:
In [29]:
#Stream fibonacci
from fn.iters import take, drop, map as imap
from operator import add
f = Stream()
fib = f << [0, 1] << imap(add, f, drop(1, f))
#primeros 10 elementos de fibonacci
list(take(10, fib))
Out[29]:
In [30]:
#elemento 20 de la secuencia fibonacci
fib[20]
Out[30]:
In [31]:
#elementos 40 al 45 de la secuencia fibonacci
list(fib[40:45])
Out[31]:
In [32]:
#Funciones de orden superior
from fn import F
from operator import add, mul #operadores de suma y multiplicacion
#composición de funciones
F(add, 1)(10)
Out[32]:
In [33]:
#f es una funcion que llama a otra funcion.
f = F(add, 5) << F(mul, 100) #<< operador de composicion de funciones.
In [34]:
#cada valor de la lista primero se multiplica por 100 y luego
#se le suma 5, segun composicion de f de arriba.
map(f, [0, 1, 2, 3])
Out[34]:
In [35]:
func = F() >> (filter, _ < 6) >> sum
In [36]:
#func primero filtra los valores menores a 6
#y luego los suma.
func(xrange(10))
Out[36]:
In [37]:
#Datos a utilizar en los ejemplos
cuentas = [(1, 'Alice', 100, 'F'), # id, nombre, balance, sexo
(2, 'Bob', 200, 'M'),
(3, 'Charlie', 150, 'M'),
(4, 'Dennis', 50, 'M'),
(5, 'Edith', 300, 'F')]
In [38]:
from cytoolz.curried import pipe, map as cmap, filter as cfilter, get
#seleccionando el id y el nombre de los que tienen un balance mayor a 150
pipe(cuentas, cfilter(lambda (id, nombre, balance, sexo): balance > 150),
cmap(get([1, 2])),
list)
Out[38]:
In [39]:
#este mismo resultado tambien lo podemos lograr con las listas por comprensión.
#mas pythonico.
[(nombre, balance) for (id, nombre, balance, sexo) in cuentas
if balance > 150]
Out[39]:
In [40]:
from cytoolz import groupby
#agrupando por sexo
groupby(get(3), cuentas)
Out[40]:
In [41]:
#utilizando reduceby
from cytoolz import reduceby
def iseven(n):
return n % 2 == 0
def add(x, y):
return x + y
reduceby(iseven, add, [1, 2, 3, 4])
Out[41]:
In [42]:
#Datos para los ejemplos
estudiantes_tupla = [
('john', 'A', 15),
('jane', 'B', 12),
('dave', 'B', 10),
]
class Estudiante:
def __init__(self, nombre, nota, edad):
self.nombre = nombre
self.nota = nota
self.edad = edad
def __repr__(self):
return repr((self.nombre, self.nota, self.edad))
def nota_ponderada(self):
return 'CBA'.index(self.nota) / float(self.edad)
estudiantes_objeto = [
Estudiante('john', 'A', 15),
Estudiante('jane', 'B', 12),
Estudiante('dave', 'B', 10),
]
In [43]:
from operator import itemgetter, attrgetter, methodcaller
#ordenar por edad tupla
sorted(estudiantes_tupla, key=itemgetter(2))
Out[43]:
In [44]:
#ordenar por edad objetos
sorted(estudiantes_objeto, key=attrgetter('edad'))
Out[44]:
In [45]:
#ordenar por nota y edad tupla
sorted(estudiantes_tupla, key=itemgetter(1,2))
Out[45]:
In [46]:
#ordenar por nota y edad objetos
sorted(estudiantes_objeto, key=attrgetter('nota', 'edad'))
Out[46]:
In [47]:
#ordenando por el resultado del metodo nota_ponderada
sorted(estudiantes_objeto, key=methodcaller('nota_ponderada'))
Out[47]:
Hasta aquí llega esta introducción. Tengan en cuenta que Python no es un lenguaje puramente funcional, por lo que algunas soluciones pueden verse más como un hack y no ser del todo pythonicas. El concepto más importante es el de evitar los efectos secundarios en nuestras funciones. Debemos mantener un equilibrio entre los diferentes paradigmas y utilizar las opciones que nos ofrece Python que haga más legible nuestro código. Para más información sobre la Programación Funcional en Python también puede visitar el siguiente documento y darse una vuelta por la documentación de los módulos mencionados más arriba. Por último, los que quieran incursionar con un lenguaje puramente funcional, les recomiendo Haskell.