Ist es möglich, eine Python for Range-Schleife ohne Iteratorvariable zu implementieren?


186

Ist es möglich, ohne das zu folgen i?

for i in range(some_number):
    # do something

Wenn Sie nur N-mal etwas tun möchten und den Iterator nicht benötigen.


21
Das ist eine gute Frage! PyDev kennzeichnet das 'i' sogar als Warnung für 'nicht verwendete Variable'. Die folgende Lösung entfernt diese Warnung.
Ashwin Nanjappa

@Ashwin Sie können \ @UnusedVariable verwenden, um diese Warnung zu entfernen. Beachten Sie, dass ich das 'at'-Symbol umgehen musste, damit dieser Kommentar durchging.
Raffi Khatchadourian

Ich leite die gleiche Frage an Sie. Es ist ärgerlich mit Pylint-Warnungen. Natürlich können Sie die Warnungen durch zusätzliche Unterdrückung deaktivieren, wie von @Raffi Khatchadourian vorgeschlagen. Es wäre schön, Pylint-Warnungen und Unterdrückungskommentare zu vermeiden .
Tangoal

Antworten:


109

Aus dem Kopf, nein.

Ich denke, das Beste, was Sie tun können, ist so etwas:

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

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

Aber ich denke, man kann einfach mit der zusätzlichen iVariablen leben.

Hier ist die Option, die _Variable zu verwenden, die in Wirklichkeit nur eine andere Variable ist.

for _ in range(n):
    do_something()

Beachten Sie, dass _das letzte Ergebnis zugewiesen wird, das in einer interaktiven Python-Sitzung zurückgegeben wurde:

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

Aus diesem Grund würde ich es nicht auf diese Weise verwenden. Mir ist keine von Ryan erwähnte Redewendung bekannt. Es kann Ihren Dolmetscher durcheinander bringen.

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

Und laut Python-Grammatik ist es ein akzeptabler Variablenname:

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

4
"Aber ich denke, du kannst einfach mit dem zusätzlichen" i "leben." Ja, das ist nur ein akademischer Punkt.
James McMahon

1
@nemo können Sie versuchen, für _ in range (n) Folgendes zu tun: Wenn Sie keine alphanumerischen Namen verwenden möchten.
Unbekannt

Ist _ in diesem Fall eine Variable? Oder ist das etwas anderes in Python?
James McMahon

1
@nemo Ja, es ist nur ein akzeptabler Variablenname. Im Interpreter wird automatisch der zuletzt erstellte Ausdruck zugewiesen.
Unbekannt

3
@kurczak Es gibt einen Punkt. Die Verwendung _macht deutlich, dass es ignoriert werden sollte. Zu sagen, dass es keinen Sinn macht, dies zu tun, ist wie zu sagen, dass es keinen Sinn macht, Ihren Code zu kommentieren - weil es sowieso genau das Gleiche tun würde.
Lambda Fairy

69

Sie suchen vielleicht

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

Dies ist DER schnellste Weg, um timesZeiten in Python zu wiederholen .


2
Ich habe mich nicht um die Leistung gekümmert, ich war nur neugierig, ob es einen kürzeren Weg gibt, die Aussage zu schreiben. Während ich Python seit ungefähr 2 Jahren sporadisch benutze, habe ich immer noch das Gefühl, dass mir viel fehlt. Itertools ist eines dieser Dinge, danke für die Information.
James McMahon

5
Das ist interessant, das war mir nicht bewusst. Ich habe mir nur die itertools-Dokumente angesehen. aber ich frage mich, warum das schneller ist als nur range oder xrange zu verwenden?
si28719e

5
@blackkettle: Es ist schneller, da der aktuelle Iterationsindex nicht zurückgegeben werden muss. Dies ist ein messbarer Teil der Kosten für xrange (und der Bereich von Python 3, der einen Iterator und keine Liste enthält). @nemo, Range ist so optimiert wie es nur sein kann, aber das Erstellen und Zurückgeben einer Liste ist unweigerlich schwieriger als ein Iterator (in Py3 gibt Range einen Iterator zurück, wie Py2s xrange; die Abwärtskompatibilität erlaubt eine solche Änderung nicht in Py2), insbesondere einer, der keinen variierenden Wert zurückgeben muss.
Alex Martelli

4
@Cristian, ja, jedes Mal ein Python-Int klar vorbereiten und zurückgeben, inc. GC-Arbeit hat messbare Kosten - die interne Verwendung eines Zählers spielt keine Rolle.
Alex Martelli

4
Ich verstehe jetzt. Der Unterschied ergibt sich aus dem GC-Overhead und nicht aus dem "Algorithmus". Übrigens, ich führe einen schnellen Timeit- Benchmark durch und die Beschleunigung betrug ~ 1,42x.
Cristian Ciupitu

59

Die allgemeine Redewendung für die Zuweisung zu einem Wert, der nicht verwendet wird, ist die Benennung _.

for _ in range(times):
    do_stuff()

18

Was jeder, der Ihnen die Verwendung von _ vorschlägt, nicht sagt, ist, dass _ häufig als Verknüpfung zu einer der gettext- Funktionen verwendet wird. Wenn Sie also möchten, dass Ihre Software in mehr als einer Sprache verfügbar ist, sollten Sie sie am besten vermeiden für andere Zwecke.

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

Für mich _scheint diese Verwendung eine schreckliche Idee zu sein, es würde mir nichts ausmachen, damit in Konflikt zu geraten.
KeithWM

9

Hier ist eine zufällige Idee, die das Datenmodell ( Py3-Link ) nutzt (missbraucht? ).

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

Ich frage mich, ob es so etwas in den Standardbibliotheken gibt.


10
Ich denke, eine Methode wie __nonzero__mit Nebenwirkungen ist eine schreckliche Idee.
ThiefMaster

2
Ich würde __call__stattdessen verwenden. while x():ist nicht viel schwieriger zu schreiben.
Jasmijn

1
Es gibt auch ein Argument, um den Namen zu vermeiden Counter; Sicher, es ist nicht reserviert oder im eingebauten Bereich, aber es collections.Counterist eine Sache , und eine gleichnamige Klasse zu machen, kann zu Verwirrung führen (nicht, dass dies das nicht schon riskiert).
ShadowRanger

7

Sie können _11 (oder eine beliebige Nummer oder eine andere ungültige Kennung) verwenden, um eine Namenskollision mit gettext zu verhindern. Jedes Mal, wenn Sie Unterstrich + ungültige Kennung verwenden, erhalten Sie einen Dummy-Namen, der in der for-Schleife verwendet werden kann.


Nett! PyDev stimmt Ihnen zu: Dadurch wird die gelbe Warnung "Nicht verwendete Variable" entfernt.
Mike Nagetier

2

Möglicherweise hängt die Antwort davon ab, welches Problem Sie mit der Verwendung des Iterators haben. kann verwendet werden

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

oder

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

loop(100, lambda a:a)

aber ehrlich gesagt sehe ich keinen Sinn darin, solche Ansätze zu verwenden


1
Hinweis: Python (definitiv nicht der CPython-Referenzinterpreter, wahrscheinlich nicht die meisten anderen) optimiert die Schwanzrekursion nicht, sodass N auf etwas in der Nähe des Werts von beschränkt ist sys.getrecursionlimit()(der standardmäßig irgendwo in den unteren vier liegt Ziffernbereich auf CPython); using sys.setrecursionlimitwürde das Limit erhöhen, aber irgendwann würden Sie das C-Stack-Limit erreichen und der Interpreter würde mit einem Stapelüberlauf sterben (nicht nur ein nice RuntimeError/ erhöhen RecursionError).
ShadowRanger


1

Anstelle eines nicht benötigten Zählers haben Sie jetzt eine nicht benötigte Liste. Die beste Lösung besteht darin, eine Variable zu verwenden, die mit "_" beginnt und den Syntaxprüfern mitteilt, dass Sie wissen, dass Sie die Variable nicht verwenden.

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

0

Ich stimme im Allgemeinen den oben angegebenen Lösungen zu. Nämlich mit:

  1. Unterstrich in for-loop verwenden (2 und mehr Zeilen)
  2. Definieren eines normalen whileZählers (3 und mehr Zeilen)
  3. Deklarieren einer benutzerdefinierten Klasse mit __nonzero__Implementierung (viele weitere Zeilen)

Wenn man ein Objekt wie in # 3 definieren soll, würde ich empfehlen, das Protokoll für mit dem Schlüsselwort zu implementieren oder contextlib anzuwenden .

Weiter schlage ich noch eine andere Lösung vor. Es ist ein 3-Liner und nicht von höchster Eleganz, aber es verwendet das itertools- Paket und könnte daher von Interesse sein.

from itertools import (chain, repeat)

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

In diesem Beispiel ist 2 die Häufigkeit, mit der die Schleife wiederholt wird. Die Kette umschließt zwei Wiederholungsiteratoren , wobei der erste begrenzt ist, der zweite jedoch unendlich. Denken Sie daran, dass dies echte Iteratorobjekte sind und daher keinen unendlichen Speicher benötigen. Offensichtlich ist dies viel langsamer als Lösung Nr. 1 . Es sei denn als Teil einer Funktion geschrieben könnte es eine Sanierung für erforderlich Zeiten variabel.


2
chainist unnötig, times = repeat(True, 2); while next(times, False):macht das gleiche.
AChampion

0

Wir hatten ein bisschen Spaß mit Folgendem, interessant zu teilen:

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()

Ergebnisse:

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

0

Wenn do_somethinges sich um eine einfache Funktion handelt oder in eine eingewickelt werden kann, kann eine einfache map()Dose do_something range(some_number)mal:

# 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)

Wenn Sie Argumente übergeben möchten do_something, finden Sie möglicherweise auch das Rezept itertoolsrepeatfunc gut lautet:

So übergeben Sie dieselben Argumente:

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)

So übergeben Sie verschiedene Argumente:

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

deque(starmap(do_something, argses), 0)

-1

Wenn Sie wirklich vermeiden möchten, etwas mit einem Namen zu versehen (entweder eine Iterationsvariable wie im OP oder eine unerwünschte Liste oder ein unerwünschter Generator, der die gewünschte Zeit wahr zurückgibt), können Sie dies tun, wenn Sie wirklich möchten:

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

Der verwendete Trick besteht darin, eine anonyme Klasse zu erstellen, type('', (), {})die zu einer Klasse mit leerem Namen führt. Beachten Sie jedoch, dass diese nicht in den lokalen oder globalen Namespace eingefügt wird (selbst wenn ein nicht leerer Name angegeben wurde). Dann verwenden Sie ein Mitglied dieser Klasse als Iterationsvariable, das nicht erreichbar ist, da die Klasse, zu der es gehört, nicht erreichbar ist.


Offensichtlich ist dies absichtlich pathologisch, daher ist es nebensächlich, es zu kritisieren, aber ich werde hier eine zusätzliche Falle bemerken. In CPython, dem Referenzinterpreter, sind Klassendefinitionen natürlich zyklisch (das Erstellen einer Klasse erzeugt unvermeidlich einen Referenzzyklus, der eine deterministische Bereinigung der Klasse basierend auf der Referenzzählung verhindert). Das heißt, Sie warten auf den zyklischen GC, um die Klasse aufzuräumen. Es wird normalerweise als Teil der jüngeren Generation gesammelt, die standardmäßig häufig gesammelt wird. Trotzdem bedeutet jede Schleife ~ 1,5 KB Müll mit nicht deterministischer Lebensdauer.
ShadowRanger

Um eine benannte Variable zu vermeiden, die (normalerweise) deterministisch in jeder Schleife bereinigt wird (wenn sie zurückprallt und der alte Wert bereinigt wird), erstellen Sie eine riesige unbenannte Variable, die nicht deterministisch bereinigt wird und leicht bereinigt werden kann länger halten.
ShadowRanger


-7

Wie wäre es mit:

while range(some_number):
    #do something

3
Das ist eine Endlosschleife, da die Bedingung range(some_number)immer wahr ist!
tödlicher

@deadly: Nun, wenn some_numberes kleiner oder gleich ist, 0ist es nicht unendlich, es läuft einfach nie. :-) Und es ist ziemlich ineffizient für eine Endlosschleife (insbesondere auf Py2), da für jeden Test ein listneues (Py2) oder rangeObjekt (Py3) erstellt wird (es ist aus Sicht des Interpreten keine Konstante, es muss geladen werden rangeund some_numberjede Schleife aufrufen range, dann das Ergebnis testen).
ShadowRanger
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.