Artículo viejo
Este artículo lo he rescatado de un borrador que tenía desde 2016. Está sin terminar, pero creo puede interesar tal como está.
En python existe cierta dualidad entre listas e iteradores. Bastantes métodos de la librería estándar que utilizan listas también suelen aceptar iteradores, por lo que no es necesario convertir el iterador en lista para invocar algunas funciones. Incluso es posible sustituir la lista por una expresión generadora contruida a propósito para la llamada.
Por ejemplo, imagina que necesitamos saber cuál es la mayor longitud de las líneas de un fichero:
with open("fichero.txt") as f:
maxlen = max(len(line) for line in f)
print("Longitud mayor:", maxlen)
Hemos procesado todo el fichero, línea a línea, sin necesidad de leer todo el
fichero completo en memoria, ni crear una lista auxiliar con las longitudes de
cada línea. La función max
ha sido capaz de operar con la expresión generadora
para calcular el máximo valor de línea resultante. La principal ventaja es que
no malgastamos recursos de memoria ya que el generador ha leído el fichero línea
a línea, según ha ido necesitando.
Imagina ahora que necesitamos saber la longitud media de línea que tiene un
fichero. Podemos aprovechar el módulo statistics
y hacer algo parecido:
from statistics import mean
with open("fichero.txt") as f:
meanlen = mean(len(line) for line in f)
print("Longitud media:", meanlen)
El problema de los iteradores es que se “agotan”. Si quisiéramos volverlos a usarlos, tendríamos que recrearlos de nuevo tantas veces como cálculos hiciéramos. En el ejemplo que estamos viendo, necesitaríamos volver a leer el fichero, lo que no es un uso eficiente de recursos.
Una técnica que podemos aplicar es una similar a las que vimos en un anterior artículo sobre clausuras consistente en crear una clase que se encargue de realizar los cálculos en cada iteración:
class Calc:
def __init__(self, iterable):
self.num = 0
self.total = 0
self.maxlen = max(iterable, key=self.iteration)
@property
def mean(self):
return self.total / self.num
def iteration(self, nlen):
self.num += 1
self.total += nlen
return nlen
with open("fichero.txt") as f:
calc = Calc(len(line) for line in f)
print("Longitud mayor:", calc.maxlen)
print("Longitud media:", calc.mean)
print("Total líneas:", calc.num)
En el parámetro key
de max()
hemos pasado el método Calc.interation()
.
Este método devuelve el mismo valor que se le pasa como argumento (la longitud
de línea), lo único que hace es incrementar el número de líneas y actualizar el total.
Es un código muy mejorable, pero sirve para ilustrar las posibilidades que existen. En general, es posible operar con un iterador empleando métodos similares a los usados con listas.
En la documentación del módulo itertools
se ven algunas recetas interesantes:
…