È possibile implementare un loop Python per range senza una variabile iteratore?


187

È possibile fare quanto segue senza il i?

for i in range(some_number):
    # do something

Se vuoi solo fare qualcosa N volte e non hai bisogno dell'iteratore.


21
Questa è una buona domanda! PyDev contrassegna persino "i" come avvertimento per "variabile non utilizzata". La soluzione seguente rimuove questo avviso.
Ashwin Nanjappa,

@Ashwin È possibile utilizzare \ @UnusedVariable per rimuovere tale avviso. Nota che avevo bisogno di sfuggire al simbolo 'at' per far passare questo commento.
Raffi Khatchadourian,

Ho la stessa domanda per te. È fastidioso con gli avvisi di pylint. Ovviamente puoi disabilitare gli avvertimenti con una soppressione aggiuntiva come proposto da @Raffi Khatchadourian. Sarebbe bello evitare avvertimenti sui pylint e commenti di soppressione.
tangoal,

Risposte:


110

Fuori dalla mia testa, no.

Penso che il meglio che potresti fare sia qualcosa del genere:

def loop(f,n):
    for i in xrange(n): f()

loop(lambda: <insert expression here>, 5)

Ma penso che tu possa semplicemente vivere con la ivariabile extra .

Ecco l'opzione per usare la _variabile, che in realtà è solo un'altra variabile.

for _ in range(n):
    do_something()

Si noti che _viene assegnato l'ultimo risultato restituito in una sessione interattiva di Python:

>>> 1+2
3
>>> _
3

Per questo motivo, non lo userei in questo modo. Non sono a conoscenza di alcun linguaggio come menzionato da Ryan. Può rovinare il tuo interprete.

>>> for _ in xrange(10): pass
...
>>> _
9
>>> 1+2
3
>>> _
9

E secondo la grammatica di Python , è un nome variabile accettabile:

identifier ::= (letter|"_") (letter | digit | "_")*

4
"Ma penso che tu possa semplicemente vivere con un extra" i "" Sì, è solo un punto accademico.
James McMahon

1
@nemo, puoi provare a fare per _ nell'intervallo (n): se non vuoi usare nomi alfanumerici.
Sconosciuto

_ È una variabile in quel caso? O è qualcos'altro in Python?
James McMahon

1
@nemo Sì, è solo un nome variabile accettabile. Nell'interprete, viene automaticamente assegnata l'ultima espressione che hai creato.
Sconosciuto

3
@kurczak C'è un punto. L'uso _chiarisce che dovrebbe essere ignorato. Dire che non ha senso farlo è come dire che non ha senso commentare il codice, perché farebbe comunque esattamente lo stesso.
Lambda Fairy,

69

Forse stai cercando

for _ in itertools.repeat(None, times): ...

questo è il modo più veloce per ripetere i timestempi in Python.


2
Non ero preoccupato per le prestazioni, ero solo curioso di sapere se c'era un modo più difficile di scrivere la dichiarazione. Mentre uso Python sporadicamente da circa 2 anni, sento ancora che mi manca molto. Itertools è una di quelle cose, grazie per l'informazione.
James McMahon,

5
È interessante, non ne ero a conoscenza. Ho appena dato un'occhiata ai documenti di itertools; ma mi chiedo perché sia ​​più veloce del semplice utilizzo di range o xrange?
si28719e

5
@blackkettle: è più veloce perché non ha bisogno di restituire l'indice di iterazione corrente, che è una parte misurabile del costo di xrange (e la gamma di Python 3, che fornisce un iteratore, non un elenco). @nemo, la gamma è ottimizzata quanto può essere, ma la necessità di creare e restituire un elenco è inevitabilmente un lavoro più pesante di un iteratore (in Py3, la gamma restituisce un iteratore, come lo xrange di Py2; la compatibilità all'indietro non consente tale cambiamento in Py2), in particolare uno che non ha bisogno di restituire un valore variabile.
Alex Martelli,

4
@Cristian, sì, chiaramente preparando e restituendo un Python int ogni volta, inc. gc work, ha un costo misurabile - l'utilizzo di un contatore internamente non ha importanza.
Alex Martelli,

4
Ora capisco. La differenza deriva dal sovraccarico GC, non dall'algoritmo. A proposito, ho eseguito un rapido timeit punto di riferimento e l'aumento di velocità è stato ~ 1.42x.
Cristian Ciupitu,

59

L'idioma generale per l'assegnazione a un valore non utilizzato è di nominarlo _.

for _ in range(times):
    do_stuff()

18

Quello che tutti ti suggeriscono di usare _ non sta dicendo che _ è spesso usato come scorciatoia per una delle funzioni di gettext , quindi se vuoi che il tuo software sia disponibile in più di una lingua, è meglio evitare di usarlo per altri scopi.

import gettext
gettext.bindtextdomain('myapplication', '/path/to/my/language/directory')
gettext.textdomain('myapplication')
_ = gettext.gettext
# ...
print _('This is a translatable string.')

A me questo uso _sembra un'idea terribile, non mi dispiacerebbe essere in conflitto con esso.
KeithWM,

9

Ecco un'idea casuale che utilizza (abusa?) Il modello di dati ( collegamento Py3 ).

class Counter(object):
    def __init__(self, val):
        self.val = val

    def __nonzero__(self):
        self.val -= 1
        return self.val >= 0
    __bool__ = __nonzero__  # Alias to Py3 name to make code work unchanged on Py2 and Py3

x = Counter(5)
while x:
    # Do something
    pass

Mi chiedo se c'è qualcosa di simile nelle librerie standard?


10
Penso che avere un metodo come quello __nonzero__degli effetti collaterali sia un'idea orribile.
ThiefMaster,

2
Vorrei usare __call__invece. while x():non è molto più difficile da scrivere.
Jasmijn,

1
C'è anche un argomento per evitare il nome Counter; certo, non è riservato o nell'ambito incorporato, ma collections.Counterè una cosa , e fare una classe con lo stesso nome rischia di confondere il manutentore (non che questo non lo stia già rischiando).
ShadowRanger

7

È possibile utilizzare _11 (o qualsiasi numero o un altro identificatore non valido) per impedire la modifica del nome con gettext. Ogni volta che usi underscore + identificatore non valido ottieni un nome fittizio che può essere usato per il ciclo.


Bello! PyDev è d'accordo con te: questo elimina l'avviso giallo "Variabile non utilizzata".
mike rodent,

2

La risposta potrebbe dipendere dal problema che hai con l'utilizzo dell'iteratore? può essere l'uso

i = 100
while i:
    print i
    i-=1

o

def loop(N, doSomething):
    if not N:
        return
    print doSomething(N)
    loop(N-1, doSomething)

loop(100, lambda a:a)

ma francamente non vedo alcun senso nell'utilizzare tali approcci


1
Nota: Python (sicuramente non l'interprete di riferimento CPython almeno, probabilmente non la maggior parte degli altri) non ottimizza la ricorsione della coda, quindi N sarà limitato a qualcosa nelle vicinanze del valore di sys.getrecursionlimit()(che per impostazione predefinita è da qualche parte nei bassi quattro intervallo di cifre su CPython); usare sys.setrecursionlimitaumenterebbe il limite, ma alla fine colpiresti il ​​limite dello stack C e l'interprete sarebbe morto con un overflow dello stack (non solo alzando un bel RuntimeError/ RecursionError).
ShadowRanger,

1
t=0    
for _ in range(10):
    print t
    t = t+1

PRODUZIONE:

0
1 
2 
3 
4 
5 
6 
7
8
9

1

Invece di un contatore non necessario, ora hai un elenco non necessario. La soluzione migliore è utilizzare una variabile che inizia con "_", che indica ai controllori della sintassi che si è consapevoli di non utilizzare la variabile.

x = range(5)
while x:
  x.pop()
  print "Work!"

0

Sono generalmente d'accordo con le soluzioni sopra indicate. Vale a dire con:

  1. Uso del trattino basso in for-loop (2 e più righe)
  2. Definizione di un whilecontatore normale (3 e più righe)
  3. Dichiarazione di una classe personalizzata con __nonzero__implementazione (molte più righe)

Se uno deve definire un oggetto come in # 3 , consiglierei di implementare il protocollo con la parola chiave o applicare il contestolib .

Inoltre propongo ancora un'altra soluzione. È un 3 liner e non è di suprema eleganza, ma utilizza il pacchetto itertools e quindi potrebbe essere di interesse.

from itertools import (chain, repeat)

times = chain(repeat(True, 2), repeat(False))
while next(times):
    print 'do stuff!'

In questo esempio 2 è il numero di volte per ripetere il ciclo. chain sta avvolgendo due ripetitori iteratori, il primo è limitato ma il secondo è infinito. Ricorda che questi sono veri oggetti iteratori, quindi non richiedono memoria infinita. Ovviamente questo è molto più lento della soluzione n . 1 . A meno che non sia scritto come parte di una funzione, potrebbe richiedere una pulizia per la variabile times .


2
chainnon è necessario, times = repeat(True, 2); while next(times, False):fa la stessa cosa.
AChampion,

0

Ci siamo divertiti con i seguenti, interessanti da condividere così:

class RepeatFunction:
    def __init__(self,n=1): self.n = n
    def __call__(self,Func):
        for i in xrange(self.n):
            Func()
        return Func


#----usage
k = 0

@RepeatFunction(7)                       #decorator for repeating function
def Job():
    global k
    print k
    k += 1

print '---------'
Job()

risultati:

0
1
2
3
4
5
6
---------
7

0

Se do_somethingè una funzione semplice o può essere racchiusa in una, una semplice map()può do_something range(some_number)volte:

# Py2 version - map is eager, so it can be used alone
map(do_something, xrange(some_number))

# Py3 version - map is lazy, so it must be consumed to do the work at all;
# wrapping in list() would be equivalent to Py2, but if you don't use the return
# value, it's wastefully creating a temporary, possibly huge, list of junk.
# collections.deque with maxlen 0 can efficiently run a generator to exhaustion without
# storing any of the results; the itertools consume recipe uses it for that purpose.
from collections import deque

deque(map(do_something, range(some_number)), 0)

Se vuoi passare argomenti a do_something, potresti anche trovare la ricetta di itertools cherepeatfunc legge bene:

Per passare gli stessi argomenti:

from collections import deque
from itertools import repeat, starmap

args = (..., my args here, ...)

# Same as Py3 map above, you must consume starmap (it's a lazy generator, even on Py2)
deque(starmap(do_something, repeat(args, some_number)), 0)

Per passare argomenti diversi:

argses = [(1, 2), (3, 4), ...]

deque(starmap(do_something, argses), 0)

-1

Se vuoi davvero evitare di mettere qualcosa con un nome (o una variabile di iterazione come nell'OP, o elenco indesiderato o generatore indesiderato che restituisce vero il tempo desiderato), puoi farlo se davvero volessi:

for type('', (), {}).x in range(somenumber):
    dosomething()

Il trucco utilizzato è quello di creare una classe anonima type('', (), {})che si traduce in una classe con nome vuoto, ma NB che non è inserito nello spazio dei nomi locale o globale (anche se è stato fornito un nome non vuoto). Quindi si utilizza un membro di quella classe come variabile di iterazione che è irraggiungibile poiché la classe di cui è membro non è raggiungibile.


Ovviamente questo è intenzionalmente patologico, quindi criticare è a parte il punto, ma noterò un ulteriore insulto qui. Su CPython, l'interprete di riferimento, le definizioni di classe sono naturalmente cicliche (la creazione di una classe crea inevitabilmente un ciclo di riferimento che impedisce la pulizia deterministica della classe basata sul conteggio dei riferimenti). Ciò significa che stai aspettando su GC ciclico di dare il via e ripulire la classe. Di solito sarà raccolto come parte della generazione più giovane, che per impostazione predefinita viene raccolta frequentemente, ma nonostante ciò, ogni ciclo significa ~ 1,5 KB di immondizia con durata non deterministica.
ShadowRanger,

Fondamentalmente, per evitare una variabile con nome che verrebbe (tipicamente) ripulita in modo deterministico su ogni ciclo (quando è rimbalzato e il vecchio valore ripulito), stai creando un'enorme variabile senza nome che viene ripulita in modo non deterministico e che potrebbe facilmente durare più a lungo.
ShadowRanger


-7

Che dire:

while range(some_number):
    #do something

3
Questo è un ciclo infinito in quanto la condizione range(some_number)è sempre vera!
mortale

@deadly: beh, se some_numberè inferiore o uguale a 0, non è infinito, non funziona mai. :-) Ed è piuttosto inefficiente per un ciclo infinito (specialmente su Py2), poiché crea un nuovo list(Py2) o un rangeoggetto (Py3) per ogni test (non è una costante dal punto di vista dell'interprete, deve caricare rangee some_numberogni ciclo, chiama range, quindi verifica il risultato).
ShadowRanger
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.