Erste Iteration der "For" -Schleife

76

Ich möchte fragen, ob es eine elegante pythonische Möglichkeit gibt, eine Funktion bei der ersten Schleifeniteration auszuführen. Die einzige Möglichkeit, an die ich denken kann, ist:

first = True
for member in something.get():
    if first:
        root.copy(member)
        first = False
    else:
        somewhereElse.copy(member)
    foo(member)
Rince
quelle

Antworten:

46

Sie haben mehrere Möglichkeiten für das Head-Tail- Designmuster.

seq= something.get()
root.copy( seq[0] )
foo( seq[0] )
for member in seq[1:]:
    somewhereElse.copy(member)
    foo( member )

Oder dieses

seq_iter= iter( something.get() )
head = seq_iter.next()
root.copy( head )
foo( head )
for member in seq_iter:
    somewhereElse.copy( member )
    foo( member )

Die Leute jammern, dass dies irgendwie nicht "TROCKEN" ist, weil der "redundante foo (Mitglied)" Code. Das ist eine lächerliche Behauptung. Wenn das wahr wäre, könnten alle Funktionen nur einmal verwendet werden. Was bringt es, eine Funktion zu definieren, wenn Sie nur eine Referenz haben können?

S.Lott
quelle
Auf memberdiese Weise verschmutzen Sie den Namespace mit einem Extra .
Skilldrick
5
Technisch gesehen ist es nicht trocken, da Sie die Semantik des Umgangs mit einem Mitglied an zwei Stellen replizieren, aber da der Code so kurz und eng beieinander liegt, denke ich, dass dieser Punkt umstritten ist. Wenn jedoch mehr Code zwischen den beiden vorhanden wäre oder wenn die beiden in separate Funktionen abstrahiert würden, würde ich zu Recht darauf hinweisen, dass dies gegen das DRY-Prinzip verstößt.
Daniel Bruce
@Skilldrick: Der Beispielcode hat auch den Namespace mit zwei verschiedenen Bedeutungen für einen Namen verschmutzt member. Einer memberwar der Kopf; der andere memberswo der Schwanz. Sie waren jedoch alle Mitglieder. Ich bin mir nicht sicher, welchen Punkt du machst.
S.Lott
@ Daniel Bruce: Ich kann nicht sehen, wie die Verwendung fooan zwei (oder mehr) Stellen gegen DRY verstößt. Es ist eine Funktion, die wiederverwendet wird. Ist das nicht der Punkt einer Funktionsdefinition?
S.Lott
TROCKEN oder nicht, dies ist am einfachsten zu lesen, solange Sie nicht mehr als ein oder zwei Zeilen Code wiederholen, und das übertrifft die anderen Bedenken in meinem Buch. Wenn die Sequenz lang ist und Sie keine temporäre Kopie aller Elemente außer einem erstellen möchten, können Sie itertools.islice verwenden.
musicinmybrain
72

So etwas sollte funktionieren.

for i, member in enumerate(something.get()):
    if i == 0:
         # Do thing
    # Code for everything

Ich würde jedoch dringend empfehlen, über Ihren Code nachzudenken, um zu sehen, ob Sie es wirklich so machen müssen, weil es irgendwie "schmutzig" ist. Besser wäre es, das Element, das eine spezielle Behandlung benötigt, im Voraus abzurufen und dann eine regelmäßige Behandlung für alle anderen in der Schleife durchzuführen.

Der einzige Grund, warum ich es nicht so machen konnte, war eine große Liste, die Sie von einem Generatorausdruck erhalten würden (den Sie nicht im Voraus abrufen möchten, weil er nicht in den Speicher passt) oder ähnliche Situationen .

Daniel Bruce
quelle
Eigentlich brauchen Sie keine große Liste. Wenn Something.get () einen Generator zurückgibt (im Gegensatz zu einer Liste), sind Sie golden.
Peter Rowell
Wie würde dies für eine Schleife funktionieren, die ein Tupel hat? dh. for i, a, b, c, in os.walk(input_dir):? Das gibt ValueError: need more than 3 values to unpack.
p014k
Ihr Beispielcode weist mehrere Fehler auf: Nachgestelltes Komma in for-expression und kein Aufruf von enumerate (). Sie müssten das Tupel manuell in der for-Schleife auspacken: python for i,tuple in enumerate(os.walk(...)): a, b, c = tuple
Daniel Bruce
Dies überprüft den Zustand jedoch so oft, wie sich Elemente im Etwas-Container befinden ...
aderchox
13

wie wäre es mit:

my_array = something.get()
for member in my_array:
    if my_array.index(member) == 0:
        root.copy(member)
    else:
        somewhereElse.copy(member)
    foo(member)

oder vielleicht:

for index, member in enumerate(something.get()):
    if index == 0:
        root.copy(member)
    else:
        somewhereElse.copy(member)
    foo(member)

Dokumentation der Indexmethode .

Dummy
quelle
1
Die erste Option funktioniert nicht, wenn my_array andere Mitglieder hat, die mit dem ersten Mitglied vergleichbar sind.
Joooeey
Es ist sicherer und leichter zu lesen, wenn Sie schreiben if member is my_array[0]. Dies funktioniert jedoch immer noch nicht, wenn my_array auf dasselbe Objekt bei Index 0 und einen anderen Index verweist.
Joooeey
6

Das funktioniert:

for number, member in enumerate(something.get()):
    if not number:
        root.copy(member)
    else:
        somewhereElse.copy(member)
    foo(member)

In den meisten Fällen würde ich jedoch vorschlagen, nur zu wiederholen whatever[1:]und die Root-Sache außerhalb der Schleife auszuführen. das ist normalerweise besser lesbar. Kommt natürlich auf deinen Anwendungsfall an.

balpha
quelle
1
-1: "keine Nummer"? Das ist wirklich sehr dunkel. Was ist los mit Nummer == 0?
S.Lott
1
Ich würde number == 0 verwenden, da dies der Semantik entspricht, an der Sie interessiert sind (das erste Element der Iterable mit dem Index 0). Diese Syntax ist jedoch alles andere als „gewaltig dunkel“, obwohl sie zum Testen der Leere von Sequenzen nützlicher ist.
musicinmybrain
1
Dann machen Sie es Nummer == 0, wenn das klarer erscheint. 0 ist die einzige Zahl, die mit False bewertet wird.
Balpha
6

Hier könnte ich mit einer pythonischen Sprache kommen, die "pertty" aussehen kann. Obwohl ich höchstwahrscheinlich das Formular verwenden würde, das Sie beim Stellen der Frage vorgeschlagen haben, nur damit der Code offensichtlicher, wenn auch weniger elegant bleibt.

def copy_iter():
    yield root.copy
    while True:
        yield somewhereElse.copy

for member, copy in zip(something.get(), copy_iter()):
    copy(member)
    foo(member)

(Entschuldigung - das erste Formular, das ich vor dem Bearbeiten gepostet habe, würde nicht funktionieren. Ich hatte vergessen, tatsächlich einen Iterator für das 'Kopier'-Objekt zu erhalten.)

jsbueno
quelle
er er, ich bin zu der gleichen Lösung gekommen, bevor ich deine gesehen habe, aber mit itertools :-)
fortran
6

Ich denke, das ist ziemlich elegant, aber vielleicht zu kompliziert für das, was es tut ...

from itertools import chain, repeat, izip
for place, member in izip(chain([root], repeat(somewhereElse)), something.get()):
    place.copy(member)
    foo(member)
fortran
quelle
3

Wenn etwas.get () über etwas iteriert, können Sie dies auch wie folgt tun:

root.copy(something.get())

for member in something.get():
  #  the rest of the loop
Eli Bendersky
quelle
3

Ich denke, die erste S.Lott-Lösung ist die beste, aber es gibt eine andere Wahl, wenn Sie eine ziemlich aktuelle Python verwenden (> = 2.6, da izip_longest vor dieser Version nicht verfügbar zu sein scheint), mit der Sie verschiedene Dinge für die erstes und aufeinanderfolgendes Element und kann leicht modifiziert werden, um unterschiedliche Operationen für das 1., 2., 3. Element auszuführen ... auch.

from itertools import izip_longest

seq = [1, 2, 3, 4, 5]

def headfunc(value):
    # do something
    print "1st value: %s" % value

def tailfunc(value):
    # do something else
    print "this is another value: %s" % value

def foo(value):
    print "perform this at ANY iteration."

for member, func in izip_longest(seq, [headfunc], fillvalue=tailfunc):
    func(member)
    foo(member)
Alan Franzoni
quelle
Das ist vielleicht klug, aber alles andere als klar oder intuitiv.
Aaron McMillin
3

Wie wäre es iter, das erste Element zu verwenden und zu konsumieren?

Bearbeiten: Zurück zur Frage des OP: Es gibt eine gemeinsame Operation, die Sie für alle Elemente ausführen möchten, und dann eine Operation, die Sie für das erste Element ausführen möchten, und eine andere für den Rest.

Wenn es sich nur um einen einzelnen Funktionsaufruf handelt, würde ich sagen, schreiben Sie ihn einfach zweimal. Es wird die Welt nicht beenden. Wenn es mehr involviert ist, können Sie einen Dekorateur verwenden, um Ihre "erste" Funktion und "Ruhe" -Funktion mit einer gemeinsamen Operation zu verpacken.

def common(item):
    print "common (x**2):", item**2

def wrap_common(func):
    """Wraps `func` with a common operation"""
    def wrapped(item):
        func(item)
        common(item)
    return wrapped

@wrap_common
def first(item):
    """Performed on first item"""
    print "first:", item+2

@wrap_common
def rest(item):
    """Performed on rest of items"""
    print "rest:", item+5

items = iter(range(5))
first(items.next())

for item in items:
    rest(item)

Ausgabe:

first: 2
common (x**2): 0
rest: 6
common (x**2): 1
rest: 7
common (x**2): 4
rest: 8
common (x**2): 9
rest: 9
common (x**2): 16

oder du könntest ein Stück machen:

first(items[0])
for item in items[1:]:
    rest(item)
Ryan Ginstrom
quelle
1

Kannst du nicht root.copy(something.get())vor der Schleife tun ?

EDIT: Sorry, ich habe das zweite Bit verpasst. Aber Sie bekommen die allgemeine Idee. Andernfalls aufzählen und prüfen nach 0?

EDIT2: Ok, habe die dumme zweite Idee losgeworden.

Skilldrick
quelle
1

Ich kenne Python nicht, aber ich verwende fast das genaue Muster Ihres Beispiels.
Was ich auch mache, ifist die häufigste Erkrankung. Überprüfen Sie daher normalerweise, if( first == false )
warum? Bei langen Schleifen ist die erste nur einmal wahr und die anderen Male falsch, was bedeutet, dass das Programm in allen Schleifen außer der ersten nach dem Zustand sucht und zum anderen Teil springt.
Wenn überprüft wird, ob zuerst falsch ist, wird nur ein Sprung zum anderen Teil ausgeführt. Ich weiß nicht wirklich, ob dies überhaupt zu mehr Effizienz führt, aber ich mache es trotzdem, nur um mit meinem inneren Nerd in Frieden zu sein.

PS: Ja, ich weiß, dass beim Eingeben des if-Teils auch über das else gesprungen werden muss, um die Ausführung fortzusetzen. Daher ist meine Vorgehensweise wahrscheinlich nutzlos, aber es fühlt sich gut an. : D.

Petruza
quelle
0

Ihre Frage ist widersprüchlich. Sie sagen "Mach nur etwas bei der ersten Iteration", wenn Sie tatsächlich sagen, machen Sie etwas anderes bei der ersten / nachfolgenden Iteration. So würde ich es versuchen:

copyfn = root.copy
for member in something.get():
    copyfn(member)
    foo(member)
    copyfn = somewhereElse.copy
aaa90210
quelle
0

Folgendes funktioniert bei mir

    dup_count = 0
    for x in reversed(dup_list):
        dup_count += 1
        if dup_count == 1:
            print("First obj {}: {}".format(dup_count,x))
        else:
            print("Object # {}:  {}".format( dup_count,x  ))
Operation420.net
quelle