pythonischer Weg, etwas N-mal ohne Indexvariable zu machen?

160

Jeden Tag liebe ich Python mehr und mehr.

Heute habe ich einen Code geschrieben wie:

for i in xrange(N):
    do_something()

Ich musste N-mal etwas tun. Aber jedes Mal hing es nicht vom Wert von i(Indexvariable) ab. Mir wurde klar, dass ich eine Variable erstellte, die ich nie verwendet habe (i ), und dachte: "Es gibt sicherlich einen pythonischeren Weg, dies zu tun, ohne dass diese nutzlose Indexvariable benötigt wird."

Also ... die Frage ist: Weißt du, wie man diese einfache Aufgabe (pythonischer) schöner macht?

Manuel Aráoz
quelle
7
Ich habe gerade etwas über die Variable _ gelernt, aber ansonsten würde ich überlegen, wie Sie es Pythonic machen. Ich glaube nicht, dass ich jemals eine einfache for-Schleife gesehen habe, die anders gemacht wurde, zumindest nicht in Python. Ich bin mir zwar sicher, dass es bestimmte Anwendungsfälle gibt, in denen Sie sich das ansehen und sagen: "Warten Sie, das sieht schrecklich aus" - aber im Allgemeinen ist xrange der bevorzugte Weg (soweit ich gesehen habe).
Wayne Werner
Mögliches Duplikat von Ist es möglich, eine Python for Range-Schleife ohne Iteratorvariable zu implementieren?
Ciro Santilli 法轮功 冠状 病 六四 事件 24
5
HINWEIS: xrange ist in Python3 nicht vorhanden. Verwenden Sie rangestattdessen.
John Henckel

Antworten:

109

Ein etwas schnellerer Ansatz als das Schleifen xrange(N)ist:

import itertools

for _ in itertools.repeat(None, N):
    do_something()
Alex Martelli
quelle
3
Wie viel schneller? Gibt es noch einen Unterschied in Python 3.1?
Hamish Grubijan
15
@ Hamish: Mein Test mit 2,6 sagt 32% schneller (23,2 us vs 17,6 us für N = 1000). Aber das ist sowieso eine echte Zeit. Ich würde standardmäßig den OP-Code verwenden, da dieser (für mich) sofort lesbar ist.
Mike Boers
3
Das ist gut über die Geschwindigkeit zu wissen. Ich stimme mit Sicherheit Mikes Einschätzung zu, dass der Code des OP besser lesbar ist.
Wayne Werner
@Wayne, ich denke, Gewohnheit ist wirklich sehr mächtig - abgesehen von der Tatsache, dass Sie daran gewöhnt sind, warum sonst "jedes Mal, wenn Sie diese Zählung durchführen, von 0 auf N-1 hochzählen [[und die Zählung vollständig ignorieren]] -unabhängige Operation "an sich klarer sein als" N-mal die folgende Operation wiederholen "...?
Alex Martelli
4
Sind Sie sicher, dass die Geschwindigkeit wirklich relevant ist? Ist es nicht so, dass wenn Sie in dieser Schleife etwas Bedeutendes tun, es sehr wahrscheinlich Hunderte oder Tausende so lange dauern wird wie der von Ihnen gewählte Iterationsstil?
Henning
55

Verwenden Sie die Variable _, wie ich bei der Beantwortung dieser Frage erfahren habe , zum Beispiel:

# A long way to do integer exponentiation
num = 2
power = 3
product = 1
for _ in xrange(power):
    product *= num
print product
GreenMatt
quelle
6
Nicht der Downvoter, aber es könnte sein, dass Sie sich auf einen anderen Beitrag beziehen, anstatt mehr Details in die Antwort aufzunehmen
Downgoat
5
@ Downgoat: Danke für das Feedback. Trotzdem gibt es zu dieser Redewendung nicht viel zu sagen. Mein Punkt bei der Bezugnahme auf einen anderen Beitrag war, darauf hinzuweisen, dass eine Suche die Antwort ergeben haben könnte. Ich finde es ironisch, dass diese Frage mehrmals die positiven Stimmen hat wie die andere.
GreenMatt
39

Ich benutze nur for _ in range(n), es ist direkt auf den Punkt. Es wird die gesamte Liste für große Zahlen in Python 2 generieren, aber wenn Sie Python 3 verwenden, ist dies kein Problem.

L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳
quelle
10

Da die Funktion ein erstklassiger Bürger ist, können Sie einen kleinen Wrapper schreiben (aus Alex Antworten).

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

dann können Sie die Funktion als Argument übergeben.

Anycorn
quelle
@ Hamish: Fast nichts. (17,8 us pro Schleife unter den gleichen Bedingungen wie die Zeitpunkte für Alex 'Antwort, für einen Unterschied von 0,2 us).
Mike Boers
9

Das _ ist dasselbe wie x. Es ist jedoch eine Python-Sprache, die verwendet wird, um einen Bezeichner anzugeben, den Sie nicht verwenden möchten. In Python benötigen diese Bezeichner kein Memor oder weisen keinen Speicherplatz zu, wie dies bei Variablen in anderen Sprachen der Fall ist. Das kann man leicht vergessen. Es sind nur Namen, die auf Objekte verweisen, in diesem Fall eine Ganzzahl bei jeder Iteration.

Khorkrak
quelle
8

Ich fand die verschiedenen Antworten sehr elegant (insbesondere die von Alex Martelli), wollte aber die Leistung aus erster Hand quantifizieren und habe mir das folgende Skript ausgedacht:

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

Ich habe mir auch eine alternative Lösung ausgedacht, die auf der von Martelli aufbaut und map()zum Aufrufen der Nutzlastfunktion verwendet wird. OK, ich habe ein bisschen geschummelt, weil ich mir die Freiheit genommen habe, die Nutzlast dazu zu bringen, einen Parameter zu akzeptieren, der verworfen wird: Ich weiß nicht, ob es einen Weg gibt, dies zu umgehen. Trotzdem hier die Ergebnisse:

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

Die Verwendung der Karte ergibt also eine Verbesserung von ungefähr 30% gegenüber dem Standard für Schleifen und zusätzlichen 19% gegenüber Martellis.

japs
quelle
4

Angenommen, Sie haben do_something als Funktion definiert und möchten diese N- mal ausführen . Vielleicht können Sie Folgendes versuchen:

todos = [do_something] * N  
for doit in todos:  
    doit()
Cox Chen
quelle
45
Sicher. Rufen wir die Funktion nicht nur millionenfach auf, sondern weisen wir auch eine Liste mit einer Million Elementen zu. Wenn die CPU funktioniert, sollte dann nicht auch der Speicher ein wenig belastet werden? Die Antwort kann nicht als definitiv „nicht nützlich“ bezeichnet werden (sie zeigt einen anderen, funktionierenden Ansatz), daher kann ich nicht ablehnen, aber ich bin anderer Meinung und bin völlig dagegen.
Zot
1
Ist es nicht nur eine Liste von N Verweisen auf denselben Funktionswert?
Nick McCurdy
Es ist besser, fn() for fn in itertools.repeat(do_something, N)das Array vorab zu generieren und zu speichern. Dies ist meine bevorzugte Redewendung.
F1Rumors
1
@tzot Warum der herablassende Ton? Diese Person hat sich Mühe gegeben, eine Antwort zu schreiben, und kann jetzt davon abgehalten werden, in Zukunft einen Beitrag zu leisten. Selbst wenn es Auswirkungen auf die Leistung hat, ist es eine funktionierende Option, und insbesondere wenn N klein ist, sind die Auswirkungen auf die Leistung / den Speicher nicht signifikant.
Davidscolgan
Ich bin immer wieder überrascht, wie leistungsbesessen Python-Entwickler sind :) Obwohl ich der Meinung bin, dass es nicht idiomatisch ist und jemand, der neu in Python ist, es möglicherweise nicht so klar versteht, was vor sich geht, wie wenn man einfach einen Iterator verwendet
Asfand Qazi
1

Was ist mit einer einfachen while-Schleife?

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

Sie haben bereits die Variable; warum nicht benutzen?

Carlos Ramirez
quelle
1
Mein einziger Gedanke ist, dass es 3 Codezeilen gegen eine (?)
Sind
2
@AJP - Mehr wie 4 Zeilen gegen 2 Zeilen
ArtOfWarfare
fügt den Vergleich (Zeiten> 0) und das Dekrement (Zeiten - = 1) zu den Gemeinkosten hinzu ... also langsamer als die for-Schleife ...
F1Rumors
@ F1Rumors Habe es nicht gemessen, aber ich wäre überrascht, wenn JIT-Compiler wie PyPy für eine so einfache while-Schleife langsameren Code generieren sollten.
Philipp Claßen