
En el artículo anterior hablamos de la programación funcional. Escribí sobre por que es importante y que beneficios ofrece la programación funcional así con el balance que se necesita tener al intentar implementar las mejores prácticas de programación funcional.
En este artículo proveeremos una forma más práctica sobre los módulos y métodos que se deben de aprender en python para poder programar en este estilo de programación.
Iteradores
Uno de los principales funcionalidades de Python para programar de forma funcional es: iteradores (iterators).
Un iterador es un objeto que representa una forma de datos; este objeto regresa los datos de un elemento de uno a la vez. Un iterador de Python deberá soportar un método llamado __next__()
, este no toma argumentos y siempre devuelve el siguiente elemento en una transmisión. Si no existe mas elementos en la transmisión, next()
deberá mostrar una excepción llamada StopIteration
. Los iteradores no tienen que ser finitos, aunque; es perfectamente rasonable escribir un iterador que produsca una transmisión infinita de datos.
La función incluida llamada iter()
toma un objeto arbitrario e intenta devolver un iterador que resolvera el contenido de un objeto o elemento, levantando excepciones de tipo TypeError
si el objeto no soporta la iteración. Muchos de los tipos incluidos en Python soportan iteración, los más comunes siendo listas y diccionarios. Un objeto llamado un objeto iterable si pueden tener un iterador para eso.

Puedes experimentar con la iteración manualmente:
>>> L = [1,2,3]
>>> it = iter(L)
>>> print(it)
<...iterator object at ...>
>>> next(it)
1
>>> next(it)
2
>>> next(it)
3
>>> next(it)
Traceback (most recent call last):
File "", line 1, in ?
StopIteration
>>>
Python espera que los objetos iterables en muchos diferentes contexto, el más importante siendo for
. En un código for X in Y
, Y deberá ser un iterador o algun objeto en el que iter()
pueda crear un iterador. Aquí hay dos declaraciones equivalentes:
for i in iter(obj):
print(i)
for i in obj:
print(i)
Iteradores pueden materializarse como listas o tuplas usando las funciones list()
o tuple()
respectivamentes:
>>> L = [1,2,3]
>>> iterator = iter(L)
>>> t = tuple(iterator)
>>> t
(1, 2, 3)
Funciones incluidas en Python como min()
y max()
pueden tener un argumento del iterador y devolver el elemento más grande o menor respectivamente. Los operadores "in"
y "not in"
pueden soportar iteradores: X in iterator
como verdaderos si X es encontrado en la transmisión devuelta por el iterador.
Nota que solo puedas ir hacia adelante en un iterador; no hay manera de obetener el elemento previo, o reiniciar el iterador o crear una copia de este. Los objetos iteradores pueden proveer esta funcionalidad de forma adicional, pero el protocolo de iterador solo especifica el método __next()__
. Funciones que pueden entonces consumir todas las salidas del iterador, y si necesitas hacer algo diferente con la misma transmisión, deberás crear un nuevo iterador.
Tipos de Datos que Soportan Iteradores
Ya hemos visto como nuestras listas y tuplas soportan iteradores. De hecho, cualquier tipo de secuencia en Python, tal como una cadena, automáticamente soportara la creación de un iterador.
Aquí llamamos iter()
en un diccionario que devuelve un iterador que hará un bucle sobre las llaves de un diccionario:
>>> m = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
... 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
>>> for key in m:
... print(key, m[key])
Mar 3
Feb 2
Aug 8
Sep 9
Apr 4
Jun 6
Jul 7
Jan 1
May 5
Nov 11
Dec 12
Oct 10
Nota que el orden es aleatorio, por que esta basado en un ordenamiento de Hash en los objetos del diccionario.
Aplica iter()
al diccionario siempre entrara en un ciclo de llaves, pero los diccionarios tienen métodos que regresaran otros iteradores. Si quieres iterar sobre los valores o las parejas de valor-llave, puedes explícitamente llamar a los métodos de values()
o items()
para obtener un iterador apropiado.
El constructor dict()
puede aceptar un iterador que regrese una transmisión finita en tuplas de (llave, valor):
>>> L = [('Italy', 'Rome'), ('France', 'Paris'), ('US', 'Washington DC')]
>>> dict(iter(L))
{'Italy': 'Rome', 'US': 'Washington DC', 'France': 'Paris'}
Archivos tambien soportan iteradores con el llamado método readline()
hasta que no haya más lineas en el archivo. Esto significa que cada linea leada de una archivo como esto:
for line in file:
# do something for each line
...
Sets puede tomar el contenido de una iterancia y dejar que itere sobre los elementos del set:
S = {2, 3, 5, 7, 11, 13}
for i in S:
print(i)
Expresiones Generadoras y Comprensión de Listas
Dos operaciones comunes en la salida de iteradores son 1) desempeñar algunas operaciones para cada elemento 2) seleccionar un derivado de los elementos que se ajusten a alguna condición. Por ejemplo, dado una lista de cadenas, quizas quiera eliminar espacios entre cada linea o extraer todas las cadenas contenidas en una sub-cadena.

Lista de comprensión y expresiones generadoras (formulario corto: "listcomps" y "genexps") estas son notas concisas para esta operación, prestadas del lenguaje funcional, Haskel. Puedes extraer los espacios de una transmisión de cadenas con el siguiente código:
line_list = [' line 1\n', 'line 2 \n', ...]
# Generator expression -- returns iterator
stripped_iter = (line.strip() for line in line_list)
# List comprehension -- returns list
stripped_list = [line.strip() for line in line_list]
Ahora puedes seleccionar solo ciertos elementos agregando la condición if
:
stripped_list = [line.strip() for line in line_list
if line != ""]
Con una lista de compresión, obtienes de regreso una lista de Python; stripped_list
es una lista que contiene lineas resultantes, no solo un iterador. Las expresiones generadoras regresa una iterador que calcula los valores necesarios, no se necesita materializar todos los valres de una vez. Esto significa que las listas de comprensiones no son útiles si se esta trabajando con iteradores que resultan en una transmisión infinita o con una gran cantidad de datos. Las expresiones generadoras son preferible en estas situaciones.
Las expresiones generadoras están resultadas por paréntesis y una lista de comprensión rodeada de brackets. La expresiones generadoras tienen esta forma:
( expression for expr in sequence1
if condition1
for expr2 in sequence2
if condition2
for expr3 in sequence3 ...
if condition3
for exprN in sequenceN
if conditionN )
De nuevo, una lista de comprensión solo fuera de los braquets es diferente (braquets cuadradas en vez de paréntesis).
Los elementos están generando una salida que tendrán valores sucesivos de expression
. Si la clausula if
es toda opcional, esta presenta, expression
es solo evaluada y agregada al resultado cuando la condición es verdadera.
Las expresiones generadoras siempre tienen que estar escritas dentro de paréntesis, pero los paréntesis señalan la llamada de la función que también cuenta. Si quieres crear un iterador que sea inmediatamente pasada a una función puedes escribir:
obj_total = sum(obj.count for obj in list_all_obects())
Para las clausulas for...in
que contienen secuencias iterables. La instrucción no tiene que tener la misma longitud, por que estas son iteradas de izquierda a derecha, y no paralela. Para cada elemento, secuencia1
, secuencia2
esta en un bucle sobre el inicio. secuencia3
es cuando el bucle sobre cada par de elementos resultantes de la secuencia1
y secuencia2
.
Para ponerlo todo de una forma, una lista de comprensión o expresión generadora es equivalente al siguiente código de Python:
for expr1 in sequence1:
if not (condition1):
continue # Salta este elemento
for expr2 in sequence2:
if not (condition2):
continue # Salta este elemento
...
for exprN in sequenceN:
if not (conditionN):
continue # Salta este elemento
# Salida del valor de
# una expresión.
Esto significa que cuando hay múltiples clausulas de tipo for...in
pero no hay clausulas if
, la distancia en la salida resultante será igual al producto de la exención de todas las secuencias. Si tienes dos listas de 3 elementos, la salida de la lista es de 9 elementos totales:
>>> seq1 = 'abc'
>>> seq2 = (1,2,3)
>>> [(x, y) for x in seq1 for y in seq2]
[('a', 1), ('a', 2), ('a', 3),
('b', 1), ('b', 2), ('b', 3),
('c', 1), ('c', 2), ('c', 3)]
Para evitar introducir ambiguedades en la gramática de Python, si expression
esta creando una tupla, esta debe estar entre parentesis. La primera lista de comprensión a continuación devuelve un error de sintaxis, mientras que la segunda es correcta.
# Error de sintaxis
[x, y for x in seq1 for y in seq2]
# Correcto
[(x, y) for x in seq1 for y in seq2]
En el siguiente artículo estaré hablando de generadores, así como funciones predeterminadas y finalmente algunos módulos para poder facilitar la programación funcional como itertools y functools.

About me:
Steemer, crypto fan, like to listen to 90s hip hop, and loves to chat about Linux Python and Free software. Runs a local Tech club in sunny Cancun, and enjoys hoping on planes and landing somewhere else.