modo pitonico di fare qualcosa N volte senza una variabile indice?


161

Ogni giorno amo sempre più il pitone.

Oggi stavo scrivendo un codice come:

for i in xrange(N):
    do_something()

Ho dovuto fare qualcosa N volte. Ma ogni volta non dipendeva dal valore di i(variabile indice). Mi sono reso conto che stavo creando una variabile che non avevo mai usato ( i), e ho pensato "Esiste sicuramente un modo più pitonico di farlo senza la necessità di quella inutile variabile indice".

Quindi ... la domanda è: sai come fare questo semplice compito in un modo più (pitone) bello?


7
Ho appena imparato a conoscere la variabile _, ma altrimenti prenderei in considerazione il modo in cui lo stai facendo Pythonic. Non credo di aver mai visto un ciclo semplice per fare diversamente, almeno in Python. Anche se sono sicuro che ci sono casi d'uso specifici in cui lo guardi e dici "Aspetta, sembra terribile" - ma in generale, xrange è il modo preferito (per quanto ho visto).
Wayne Werner,


5
NOTA: xrange non esiste in Python3. Usa rangeinvece.
John Henckel,

Risposte:


110

Un approccio leggermente più veloce rispetto al looping xrange(N)è:

import itertools

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

3
Quanto più veloce? C'è ancora una differenza in Python 3.1?
Hamish Grubijan il

15
@Hamish: il mio test con 2.6 dice il 32% più veloce (23.2 us contro 17.6 us per N = 1000). Ma quello è comunque un momento davvero. Vorrei impostare automaticamente il codice OP perché è più immediatamente leggibile (per me).
Mike Boers,

3
È bello sapere della velocità. Certamente faccio eco al sentimento di Mike sul fatto che il codice dell'OP sia più leggibile.
Wayne Werner,

@Wayne, immagino che l'abitudine sia davvero molto potente - tranne per il fatto che ci sei abituato, perché altrimenti "conta da 0 a N-1 [[e ignora completamente il conteggio]] ogni volta che esegue questo conteggio -operazione indipendente "essere intrinsecamente più chiara di" ripetere N volte la seguente operazione "...?
Alex Martelli,

4
sei sicuro che la velocità sia davvero rilevante? Non è così che se fai qualcosa di significativo in quel ciclo, molto probabilmente ci vorranno centinaia o migliaia di tempo quanto lo stile di iterazione che hai scelto?
Henning,

55

Usa la variabile _, come ho appreso quando ho posto questa domanda , ad esempio:

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

6
Non il downvoter, ma potrebbe essere perché ti riferisci a un altro post invece di aggiungere più dettagli nella risposta
Downgoat,

5
@Downgoat: grazie per il feedback. Detto questo, non c'è molto da dire su questo idioma. Il mio punto di riferimento in un altro post è stato quello di sottolineare che una ricerca avrebbe potuto produrre la risposta. Trovo ironico che questa domanda abbia più volte i voti come l'altra.
GreenMatt,


10

poiché la funzione è un cittadino di prima classe, puoi scrivere un piccolo wrapper (dalle risposte di Alex)

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

quindi puoi passare la funzione come argomento.


@Hamish: quasi niente. (17,8 us per loop nelle stesse condizioni dei tempi per la risposta di Alex, per una differenza di 0,2 us).
Mike Boers,

9

_ È la stessa cosa di x. Tuttavia è un idioma di Python utilizzato per indicare un identificatore che non si intende utilizzare. In Python questi identificatori non memorizzano o allocano lo spazio come fanno le variabili in altre lingue. È facile dimenticarlo. Sono solo nomi che puntano a oggetti, in questo caso un numero intero su ogni iterazione.


8

Ho trovato le varie risposte davvero eleganti (soprattutto quelle di Alex Martelli) ma volevo quantificare le prestazioni in prima persona, quindi ho elaborato il seguente 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))

Ho anche trovato una soluzione alternativa che si basa su quella di Martelli e utilizza map()per chiamare la funzione di payload. OK, ho tradito un po 'il fatto che mi sono preso la libertà di far accettare al parametro payload un parametro che viene scartato: non so se c'è un modo per aggirare questo. Tuttavia, ecco i risultati:

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

quindi l'utilizzo di map produce un miglioramento di circa il 30% rispetto allo standard per loop e di un ulteriore 19% rispetto a Martelli.


4

Supponi di aver definito do_something come una funzione e che desideri eseguirlo N volte. Forse puoi provare quanto segue:

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

45
Sicuro. Non chiamiamo semplicemente la funzione un milione di volte, assegniamo anche un elenco di un milione di elementi. Se la CPU funziona, la memoria non dovrebbe essere stressata un po '? La risposta non può essere definita come sicuramente "non utile" (mostra un approccio diverso e funzionante), quindi non posso sottovalutare, ma non sono d'accordo e sono totalmente contrario.
tzot

1
Non è solo un elenco di N riferimenti allo stesso valore di funzione?
Nick McCurdy,

piuttosto meglio fare fn() for fn in itertools.repeat(do_something, N)e salvare pre-generando l'array ... questo è il mio linguaggio preferito.
F1Rumors

1
@tzot Perché il tono condiscendente? Questa persona si è impegnata a scrivere una risposta e ora può essere scoraggiata dal contribuire in futuro. Anche se ha implicazioni sulle prestazioni, è un'opzione funzionante e soprattutto se N è piccolo le implicazioni prestazioni / memoria non sono significative.
davidscolgan,

Sono sempre sorpreso da quanto gli sviluppatori Python siano ossessionati dalle prestazioni :) Anche se sono d'accordo che non sia idiomatico, e qualcuno che non conosce Python leggendolo potrebbe non capire cosa sta succedendo in modo così chiaro come quando si usa semplicemente un iteratore
Asfand Qazi

1

Che ne dici di un semplice ciclo while?

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

Hai già la variabile; perché non usarlo?


1
Il mio unico pensiero è che si tratta di 3 righe di codice contro una (?)
AJP

2
@AJP - Più come 4 linee contro 2 linee
ArtOfWarfare

aggiunge il confronto (tempi> 0) e il decremento (tempi - = 1) alle spese generali ... così più lento del ciclo for ...
F1Rumors

@ F1Rumors non l'ho misurato, ma sarei sorpreso se compilatori JIT come PyPy generassero un codice più lento per un ciclo while così semplice.
Philipp Claßen,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.