2012-01-30 12 views
19

Cada uso que se me ocurre para la clase itertools.repeat() de Python, puedo pensar en otra solución igual (posiblemente más) aceptable para lograr el mismo efecto. Por ejemplo:¿Cuál es el propósito en itertools.repeat de Python?

>>> (i for i in itertools.repeat('example', 5)) 
('example', 'example', 'example', 'example', 'example') 
>>> ('example') * 5 
('example', 'example', 'example', 'example', 'example') 

>>> map(str.upper, itertools.repeat('example', 5)) 
['EXAMPLE', 'EXAMPLE', 'EXAMPLE', 'EXAMPLE', 'EXAMPLE'] 
>>> ['example'.upper()] * 5 
['EXAMPLE', 'EXAMPLE', 'EXAMPLE', 'EXAMPLE', 'EXAMPLE'] 

¿Hay algún caso en el que sería la solución más adecuada? De ser así, ¿bajo qué circunstancias?

+3

Agregué una nueva respuesta que muestra el caso de uso motivador original para repeticiones itertools. Además, acabo de actualizar los documentos de Python para reflejar esta nota de uso. –

Respuesta

19

La función itertools.repeat es floja; solo usa la memoria requerida para un artículo. Por otro lado, las expresiones (a) * n y [a] * n crean n copias del objeto en la memoria. Para cinco ítems, la expresión de multiplicación es probablemente mejor, pero es posible que note un problema de recursos si tuviera que repetir algo, digamos, un millón de veces.

Aún así, es difícil imaginar muchos estáticos usos para itertools.repeat. Sin embargo, el hecho de que itertools.repeat es una función le permite usarlo en muchas aplicaciones funcionales. Por ejemplo, puede tener alguna función de biblioteca func que funcione en una entrada iterable. A veces, es posible que tenga listas preconstruidas de varios artículos. Otras veces, es posible que desee operar en una lista uniforme. Si la lista es grande, itertools.repeat le ahorrará memoria.

Finalmente, repeat hace posible el llamado "álgebra de iterador" descrito en la documentación itertools. Incluso el módulo itertools usa la función repeat.Por ejemplo, el siguiente código se da como una implementación equivalente de itertools.izip_longest (aunque el código real probablemente esté escrito en C). Observe el uso de repeat siete líneas de la parte inferior:

class ZipExhausted(Exception): 
    pass 

def izip_longest(*args, **kwds): 
    # izip_longest('ABCD', 'xy', fillvalue='-') --> Ax By C- D- 
    fillvalue = kwds.get('fillvalue') 
    counter = [len(args) - 1] 
    def sentinel(): 
     if not counter[0]: 
      raise ZipExhausted 
     counter[0] -= 1 
     yield fillvalue 
    fillers = repeat(fillvalue) 
    iterators = [chain(it, sentinel(), fillers) for it in args] 
    try: 
     while iterators: 
      yield tuple(map(next, iterators)) 
    except ZipExhausted: 
     pass 
+9

Objeción menor: '[a] * n' no crea n copias de a en la memoria.Crea n referencias a una sola copia de a. En algunos casos, la diferencia puede ser bastante significativa; intente 'a = [[]] * 5; a [0] .append (1) '. –

+5

Buen punto. Sigo olvidando que casi todo en Python es una referencia. Supongo que eso también disminuye el problema del uso de la memoria, pero supongo que un millón de referencias todavía tienen un requisito de recursos no triviales. – HardlyKnowEm

+2

Sí, todavía tendría que asignar una matriz de n punteros. –

16

Su ejemplo de foo * 5 parece superficialmente similar a itertools.repeat(foo, 5), pero en realidad es bastante diferente.

Si escribe foo * 100000, el intérprete debe crear 100,000 copias de foo antes de poder darle una respuesta. Por lo tanto, es una operación muy costosa y poco amigable con la memoria.

Pero si se escribe itertools.repeat(foo, 100000), el intérprete puede devolver un iterador que cumple la misma función, y no necesita calcular sus resultados hasta que lo necesite - por ejemplo, mediante su uso en una función que quiere conocer cada resultado en la secuencia.

Esa es la principal ventaja de los iteradores: pueden diferir el cálculo de una parte (o la totalidad) de una lista hasta que realmente necesite la respuesta.

+0

¿Por qué no usar 'for i in range (100000):' y luego acceder a 'foo' dentro del loop en lugar de preguntarle a esta función qué valor le dio? –

+0

@TylerCrompton: El iterador se puede pasar a otras cosas que esperan cualquier tipo de iterador, sin tener en cuenta su contenido interior. No puede hacer lo mismo con un rango (es iterable, pero no es un iterador). –

+0

Veo su punto, pero en lo que respecta al final de su comentario, en Python 3? –

2

Es un iterador. Gran pista aquí: está en el módulo itertools. De la documentación se ha vinculado a:

itertools.repeat (Object []), los tiempos de Hacer una iterador que devuelve objeto una y otra vez. Se ejecuta indefinidamente a menos que se especifique el argumento de tiempos.

Así que nunca tendrá todas esas cosas en la memoria. Un ejemplo en el que desea utilizar podría ser

n = 25 
t = 0 
for x in itertools.repeat(4): 
    if t > n: 
     print t 
    else: 
     t += x 

ya que esto le permitirá un número arbitrario de 4 s, o lo que es posible que tenga una lista infinita de.

+3

Podrías cambiar la línea 3 a 'while True:' y la 'x' en la línea 7 a' 4' y haría exactamente lo mismo, sería más legible y sería un poco más rápido. Es por eso que me preguntaba si tenía algún propósito. –

14

El propósito principal de itertools.repeat es suministrar un flujo de valores constantes para ser utilizado con mapa o postal:

>>> list(map(pow, range(10), repeat(2)))  # list of squares 
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 

El propósito secundario es que da una forma muy rápida de bucle de un número fijo de veces así:

for _ in itertools.repeat(None, 10000): 
    do_something() 

Esto es más rápido que:

for i in range(10000): 
    do_something(). 

El primero gana porque todo lo que tiene que hacer es actualizar el contador de referencia para el Ninguno objeto existente. Este último pierde porque el rango () o xrange() necesita fabricar 10.000 objetos enteros distintos.

Nota, el propio Guido utiliza esa técnica de bucle rápido en el módulo timeit(). Ver la fuente en https://hg.python.org/cpython/file/2.7/Lib/timeit.py#l195:

if itertools: 
     it = itertools.repeat(None, number) 
    else: 
     it = [None] * number 
    gcold = gc.isenabled() 
    gc.disable() 
    try: 
     timing = self.inner(it, self.timer) 
2

Como se ha mencionado antes, funciona bien con zip:

Otro ejemplo:

from itertools import repeat 

fruits = ['apples', 'oranges', 'bananas'] 

# Initialize inventory to zero for each fruit type. 
inventory = dict(zip(fruits, repeat(0))) 

Resultado:

{'apples': 0, 'oranges': 0, 'bananas': 0} 

Para hacer esto sin repito, tendría que involucrar a len(fruits).

+2

'inventario = {fruta: 0 para frutas en frutas}' es más fácil de leer y ligeramente más rápido. –

+0

@TylerCrompton De hecho. No estoy seguro de haber usado esa sintaxis antes para inicializar un diccionario. O acabo de utilizar demasiado LINQ :-) Gracias por el comentario informativo. –

0

Suelo utilizar la repetición junto con la cadena y el ciclo. Este es un ejemplo:

from itertools import chain,repeat,cycle 

fruits = ['apples', 'oranges', 'bananas', 'pineapples','grapes',"berries"] 

inventory = list(zip(fruits, chain(repeat(10,2),cycle(range(1,3))))) 

print inventory 

pone el 2 primeros frutos como valor de 10, entonces los ciclos de los valores 1 y 2 para los frutos restantes.

Cuestiones relacionadas