forma pitónica de hacer algo N veces sin una variable de índice?


161

Todos los días amo Python más y más.

Hoy estaba escribiendo un código como:

for i in xrange(N):
    do_something()

Tenía que hacer algo N veces. Pero cada vez no dependía del valor de i(variable de índice). Me di cuenta de que estaba creando una variable que nunca usé ( i), y pensé "Seguramente hay una forma más pitónica de hacerlo sin la necesidad de esa variable de índice inútil".

Entonces ... la pregunta es: ¿sabes cómo hacer esta tarea simple de una manera más hermosa (pitónica)?


77
Acabo de enterarme de la variable _, pero de lo contrario consideraría Pythonic la forma en que lo estás haciendo. No creo haber visto un bucle simple de otra manera, al menos en Python. Aunque estoy seguro de que hay casos de uso específicos donde lo miras y dices "Espera, eso se ve terrible", pero en general, xrange es la forma preferida (por lo que he visto).
Wayne Werner


55
NOTA: xrange no existe en Python3. Usar en su rangelugar.
John Henckel

Respuestas:


110

Un enfoque un poco más rápido que el bucle xrange(N)es:

import itertools

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

3
¿Cuanto más rápido? ¿Todavía hay una diferencia en Python 3.1?
Hamish Grubijan

15
@Hamish: Mi prueba con 2.6 dice 32% más rápido (23.2 us contra 17.6 us para N = 1000). Pero de todos modos ese es realmente un momento de tiempo. De manera predeterminada, usaría el código del OP porque es más fácil de leer (para mí).
Mike Boers

3
Es bueno saber sobre la velocidad. Ciertamente me hago eco del sentimiento de Mike acerca de que el código del OP es más legible.
Wayne Werner

@Wayne, supongo que el hábito es realmente muy poderoso, excepto por el hecho de que estás acostumbrado, ¿por qué si no "contaría de 0 a N-1 [[e ignoraría por completo el recuento]] cada vez que realice este recuento -la operación independiente "es intrínsecamente más clara que" repetir N veces la siguiente operación "...?
Alex Martelli

44
¿Estás seguro de que la velocidad es realmente relevante? ¿No es así que si haces algo significativo en ese ciclo, es muy probable que tome cientos o miles de tiempo tanto como el estilo de iteración que elegiste?
Henning

55

Use la variable _, como aprendí cuando hice esta pregunta , por ejemplo:

# A long way to do integer exponentiation
num = 2
power = 3
product = 1
for _ in xrange(power):
    product *= num
print product

66
No es el votante negativo, pero podría ser porque te estás refiriendo a otra publicación en lugar de agregar más detalles en la respuesta
Downgoat

55
@Downgoat: Gracias por los comentarios. Dicho esto, no hay mucho que decir sobre este idioma. Mi punto al referirme a otra publicación fue señalar que una búsqueda podría haber producido la respuesta. Me parece irónico que esta pregunta tenga varias veces más votos que la otra.
GreenMatt


10

Dado que la función es un ciudadano de primera clase, puede escribir un pequeño contenedor (de las respuestas de Alex)

def repeat(f, N):
    for _ in itertools.repeat(None, N): f()

entonces puedes pasar la función como argumento.


@ Hamish: Casi nada. (17.8 us por ciclo bajo las mismas condiciones que los tiempos para la respuesta de Alex, por una diferencia de 0.2 us).
Mike Boers

9

El _ es lo mismo que x. Sin embargo, es un idioma de Python que se utiliza para indicar un identificador que no tiene intención de usar. En python, estos identificadores no toman memor ni asignan espacio como lo hacen las variables en otros idiomas. Es fácil olvidar eso. Son solo nombres que apuntan a objetos, en este caso un número entero en cada iteración.


8

Las varias respuestas me parecieron realmente elegantes (especialmente las de Alex Martelli) pero quería cuantificar el rendimiento de primera mano, así que preparé el siguiente script:

from itertools import repeat
N = 10000000

def payload(a):
    pass

def standard(N):
    for x in range(N):
        payload(None)

def underscore(N):
    for _ in range(N):
        payload(None)

def loopiter(N):
    for _ in repeat(None, N):
        payload(None)

def loopiter2(N):
    for _ in map(payload, repeat(None, N)):
        pass

if __name__ == '__main__':
    import timeit
    print("standard: ",timeit.timeit("standard({})".format(N),
        setup="from __main__ import standard", number=1))
    print("underscore: ",timeit.timeit("underscore({})".format(N),
        setup="from __main__ import underscore", number=1))
    print("loopiter: ",timeit.timeit("loopiter({})".format(N),
        setup="from __main__ import loopiter", number=1))
    print("loopiter2: ",timeit.timeit("loopiter2({})".format(N),
        setup="from __main__ import loopiter2", number=1))

También se me ocurrió una solución alternativa que se basa en la de Martelli y que se usa map()para llamar a la función de carga útil. Bien, hice un poco de trampa al tomar la libertad de hacer que la carga útil aceptara un parámetro que se descarta: no sé si hay alguna forma de evitar esto. Sin embargo, aquí están los resultados:

standard:  0.8398549720004667
underscore:  0.8413165839992871
loopiter:  0.7110594899968419
loopiter2:  0.5891903560004721

por lo tanto, el uso del mapa produce una mejora de aproximadamente el 30% sobre el bucle estándar y un 19% adicional sobre el de Martelli.


4

Suponga que ha definido do_something como una función y le gustaría realizarlo N veces. Quizás puedas probar lo siguiente:

todos = [do_something] * N  
for doit in todos:  
    doit()

45
Por supuesto. No solo llamemos a la función un millón de veces, asignemos también una lista de un millón de elementos. Si la CPU está funcionando, ¿no debería también estresarse un poco la memoria? La respuesta no puede caracterizarse como definitivamente "no útil" (muestra un enfoque diferente y funcional), por lo que no puedo rechazar, pero no estoy de acuerdo y me opongo totalmente.
tzot

1
¿No es solo una lista de N referencias al mismo valor de función?
Nick McCurdy

bastante mejor hacer fn() for fn in itertools.repeat(do_something, N)y guardar pregenerando la matriz ... este es mi idioma preferido.
F1Rumors

1
@tzot ¿Por qué el tono condescendiente? Esta persona se esforzó en escribir una respuesta y ahora puede desanimarse de contribuir en el futuro. Incluso si tiene implicaciones de rendimiento, es una opción de trabajo y especialmente si N es pequeño, las implicaciones de rendimiento / memoria no son significativas.
davidscolgan

Siempre me sorprende lo obsesionados que están los desarrolladores de Python con el rendimiento :) Aunque estoy de acuerdo en que no es idiomático, y alguien nuevo en Python que lo lea puede no entender lo que está sucediendo tan claramente como cuando simplemente usa un iterador
Asfand Qazi

1

¿Qué pasa con un simple ciclo while?

while times > 0:
    do_something()
    times -= 1

Ya tienes la variable; ¿Por qué no usarlo?


1
Mi único pensamiento es que son 3 líneas de código versus una (?)
AJP

2
@AJP - Más como 4 líneas frente a 2 líneas
ArtOfWarfare

agrega la comparación (veces> 0) y la disminución (veces - = 1) a los gastos generales ... tan lento que el ciclo for ...
F1Rumors

@ F1Rumors No lo he medido, pero me sorprendería que los compiladores JIT como PyPy generen un código más lento para un ciclo while tan simple.
Philipp Claßen
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.