2014-12-16 8 views
5

Hier einige Sample erklären:Python: Durchlaufen Objekt Ausführung von Code sowohl an bestimmten Orten und auch am Ende

outputText="" 
counter=0 
for obj in specialObjects: 
    if (obj.id < 400) or (obj.name.startswith("he")) or (obj.deliberateBreak==True): 
     print "The object %s is causing a section break."%obj.details 
     outputText = outputText.rjust(80) 
     open("file%d.txt"%counter,"w").write(outputText) 
     outputText="" 
    outputText+=obj.shortValue() 
# THIS CODE IS DUPLICATED 
outputText = outputText.rjust(80) 
open("file%d.txt"%counter,"w").write(outputText) 

Was muss ich tun Iterierte über eine Liste dieser speziellen Objekte und lassen ein paar unterschiedliche Bedingungen jedes Mal. Wenn eine der Bedingungen erfüllt ist (wie hier zu sehen ist), muss ich den aktuellen Ausgabepuffer nehmen, ihn in eine Datei schreiben, dann einen neuen Ausgabepuffer starten und die Verarbeitung fortsetzen.

Das Problem hier ist die Code-Duplizierung. Beachten Sie, dass die beiden Zeilen (outputText = und open) dupliziert sind. Wenn ich den zweiten Satz von Zeilen nicht einfüge, wird der letzte Satz von Objekten verarbeitet, aber ihre Ausgabe wird niemals geschrieben.

Ich kann mir zwei mögliche Lösungen vorstellen, um die Code-Duplizierung zu verhindern. Beide scheinen etwas unelegant zu sein, also fragte ich mich, ob es einen noch besseren Weg gab.

1) Wickeln Sie den Code, der in einer Funktion wiederholt werden würde.

2) Verwenden Sie stattdessen eine numerische for-Schleife und zählen Sie bis eins höher als die Länge der Objektliste; verwendet diesen Wert als Flag bedeuten „schreiben, aber jetzt verlassen“:

outputText="" 
counter=0 
for obj in range(len(specialObjects))+1: 
    if (obj = len(specialObjects)) or (specialObjects[obj].id < 400) or (specialObjects[obj].name.startswith("he")) or (specialOejcts[obj].deliberateBreak==True): 
     print "The object %s is causing a section break."%specialObjects[obj].details 
     outputText = outputText.rjust(80) 
     open("file%d.txt"%counter,"w").write(outputText) 
     outputText="" 
     if (obj==len(specialObjects)): 
      break 
    outputText+=specialObjects[obj].shortValue() 

Wenn ich eine Wahl hätte, ich wahrscheinlich # 2, würde holt aber einige seltsamen Grenzfälle zu schaffen mit der könnte am Ende 'if' Anweisung, wenn jemals eine komplexere boolesche Logik verwendet werden muss.

Gibt es einen noch saubereren oder mehr "Pythonic" Weg, dies ohne Code-Duplizierung zu erreichen?

Danke!

+0

Im mittleren Beispiel ändern Sie nur 'counter'. Sollten die erste und die letzte Version die gleiche Datei mehrmals öffnen und dann einfach verwerfen, ohne zu schließen, oder fehlt ihnen etwas Code? – Useless

+0

Dies könnte ein Duplikat von [Vermeidung von Wiederholungen von Code nach Schleife?] Sein (http://stackoverflow.com/questions/11149997/avoiding-repeat-of-code-after-loop) – jme

Antwort

1

Sie könnten den Code trennen, der die Objekte in einen Generator aufteilt, sodass der spätere Verarbeitungsschritt nicht dupliziert werden muss.

def yield_sections(specialObjects): 
    outputText = '' 
    for obj in specialObjects: 
     if (obj.id < 400) or (obj.name.startswith("he")) or (obj.deliberateBreak==True): 
      yield outputText 
      outputText = '' 
     outputText += obj.shortValue() 
    if outputText: 
     yield outputText 


for counter, outputText in enumerate(yield_sections(specialObjects)): 
    outputText = outputText.rjust(80) 
    open("file%d.txt"%counter,"w").write(outputText) 
2

Wenn ich mich Code wie diesen zu schreiben, wo ich über eine Sammlung bin Iterieren und Code nach dem Ende der Schleife zu wiederholen, ich nehme es in der Regel als ein Zeichen, dass ich nicht über die richtigen Iterieren Ding.

In diesem Fall durchlaufen Sie eine Liste von Objekten. Aber was Sie wirklich wollen Iterieren über, denke ich, ist eine Liste von Gruppen von Objekten. Das ist, was itertools.groupby ist nützlich für.

Ihr Code hat viel zu tun, also werde ich ein vereinfachtes Beispiel verwenden, um zu veranschaulichen, wie Sie diesen doppelten Code loswerden können. Sprich, für (sehr gekünstelt) Beispiel, dass ich eine Liste der Dinge wie diese haben:

things = ["apples", "oranges", "pears", None, 
      "potatoes", "tomatoes", None, 
      "oatmeal", "eggs"] 

Dies ist eine Liste von Objekten ist. Wenn Sie genau hinsehen, gibt es mehrere Gruppen von Objekten, die durch None begrenzt sind (beachten Sie, dass Sie normalerweise things als geschachtelte Liste darstellen würden, aber ignorieren wir dies für den Zweck des Beispiels).Mein Ziel ist es aus jeder Gruppe in einer separaten Zeile drucken:

apples, oranges, pears 
potatoes, tomatoes 
oatmeal, eggs 

Hier ist die „hässlich“ Art und Weise, dies zu tun:

current_things = [] 
for thing in things: 
    if thing is None: 
     print ", ".join(current_things) 
     current_things = [] 
    else: 
     current_things.append(thing) 

print ", ".join(current_things) 

Wie Sie sehen können, haben wir die print nach der Schleife dupliziert . Böse!

Hier ist die Lösung mit groupby:

from itertools import groupby 

for key, group in groupby(things, key=lambda x: x is not None): 
    if key: 
     print ", ".join(group) 

groupby nimmt eine iterable (things) und eine Schlüsselfunktion. Es betrachtet jedes Element des Iterablen und wendet die Schlüsselfunktion an. Wenn der Schlüssel den Wert ändert, wird eine neue Gruppe gebildet. Das Ergebnis ist ein Iterator, der (key, group) Paare zurückgibt.

In diesem Fall verwenden wir die Prüfung für None als unsere Schlüsselfunktion. Deshalb brauchen wir die if key:, da es Gruppen der Größe eins geben wird, die den None Elementen unserer Liste entsprechen. Wir werden diese einfach überspringen.

Wie Sie sehen können, groupby ermöglicht es uns, über die Dinge iterieren wir wollen iterieren wirklich: Gruppen von Objekten. Dies ist für unser Problem natürlicher, und der Code vereinfacht sich dadurch. Es scheint, dass Ihr Code dem obigen Beispiel sehr ähnlich ist, außer dass Ihre Schlüsselfunktion die verschiedenen Eigenschaften des Objekts überprüft (obj.id < 400 ...). Ich werde die Implementierung Details zu Ihnen überlassen ...

1

Es gibt eine Lösung, wenn Sie Iteratoren verwenden, next kann einen besonderen Wert am Ende geben. Sie können also ein Sentinel verwenden, um zu überprüfen, ob Ihr aktuelles Objekt ein echtes Objekt ist oder ob Sie die Iteration beendet haben.

versuchen, etwas wie folgt aus:

outputText="" 
counter=0 
ending = object() 
it = iter(specialObjects) 
while True: 
    obj = next(it, ending) 
    if obj is ending or obj.id < 400 or obj.name.startswith("he") or obj.deliberateBreak: 
     outputText = outputText.rjust(80) 
     open("file%d.txt"%counter,"w").write(outputText) 
     counter += 1 
     outputText="" 
    if obj is ending: 
     break 
    outputText+=obj.shortValue() 
2

Hier ist ein Weg, um es mit einem Sentinel-Objekt zu tun. Es ist ähnlich wie deine zweite Option, aber sauberer denke ich.

for obj in itertools.chain(specialObjects, [None]): 
    if (obj is None) or (obj.id < 400) or (obj.name.startswith("he")) or (obj.deliberateBreak==True): 
     outputText = outputText.rjust(80) 
     open("file%d.txt"%counter,"w").write(outputText) 
     if obj is None: break 
     print "The object %s is causing a section break."%obj.details 
     outputText="" 
    outputText+=obj.shortValue()