2014-12-16 12 views
5

Oto samplecode wyjaśnić:Python: iterację obiektu wykonującego kodu zarówno w określonych miejscach, a także na koniec

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) 

Co muszę zrobić to iteracyjne nad listą tych specjalnych przedmiotów i sprawdzić kilka różne warunki za każdym razem. Jeśli którykolwiek z warunków jest spełniony (jak widać tutaj), to muszę pobrać bieżący bufor wyjściowy, zapisać go do pliku, a następnie uruchomić nowy bufor wyjściowy i kontynuować przetwarzanie.

Problem polega na powielaniu kodu. Zwróć uwagę, że dwa wiersze (outputText = i open) są duplikowane. Jeśli nie uda mi się wstawić drugiego zestawu linii, ostatni zestaw obiektów zostanie przetworzony, ale ich wynik nigdy nie zostanie zapisany.

Potrafię wymyślić dwa możliwe rozwiązania, aby zapobiec duplikacji kodu. Oba wydają się nieco nieeleganckie, więc zastanawiałem się, czy istnieje jeszcze lepszy sposób.

1) Owiń kod, który będzie powtarzany w funkcji.

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 
     counter = writeData(outputText) 
     outputText="" 
    outputText+=obj.shortValue() 
writeData(outputText,counter) 

def writeData(outputText,counter): 
    outputText = outputText.rjust(80) 
    open("file%d.txt"%counter,"w").write(outputText) 
    return counter+1 

2) Zamiast tego należy użyć pętli numerycznej i zliczać do wartości wyższej niż długość listy obiektów; stosować tę wartość jako flaga oznacza „pisać, ale teraz zamknąć”:

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

Gdybym miał wybrać jedną, to bym pewnie pick # 2, ale może to prowadzić do stworzenia dziwne przypadki krawędzi, przy czym Instrukcja "if", jeśli potrzebna jest jeszcze bardziej złożona logika logiczna.

Czy istnieje jeszcze bardziej czysty lub bardziej "Pythoniczny" sposób na osiągnięcie tego celu bez powielania kodu?

Dzięki!

+0

Zmieniasz tylko "licznik" w środkowym przykładzie. Czy pierwsza i ostatnia wersja powinna otwierać wielokrotnie ten sam plik, a następnie odrzucić go bez zamykania, czy też brakuje jakiegoś kodu? – Useless

+0

Może to być duplikat [Unikanie powtórzenia kodu po pętli?] (Http://stackoverflow.com/questions/11149997/avoiding-repeat-of-code-after-loop) – jme

Odpowiedz

1

Można oddzielić kod, który dzieli obiekty na generator, aby późniejszy etap przetwarzania nie musiał być duplikowany.

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

Kiedy znajdę się pisania kodu jak to, gdzie jestem iteracji po kolekcji i powtórzenie kodu po zakończeniu pętli, ja zazwyczaj wziąć to jako znak, że nie jestem iteracji po prawej stronie rzecz.

W tym przypadku przeglądasz listę obiektów. Ale to, co naprawdę chceszchcesz przerobić, to według mnie lista grup obiektów. To jest przydatne dla itertools.groupby.

Twój kod dużo się dzieje, więc zamierzam użyć uproszczonego przykładu, aby zilustrować, w jaki sposób można pozbyć się tego duplikatu kodu. Powiedzmy, na (bardzo contrived) przykład, że mam listę rzeczy tak:

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

Jest to lista obiektów. Patrząc uważnie, istnieje kilka grup obiektów rozgraniczonych przez None (zauważ, że zazwyczaj reprezentujesz things jako listę zagnieżdżoną, ale zignorujmy to dla celów przykładu).Moim celem jest, aby wydrukować każdej grupy w osobnej linii:

apples, oranges, pears 
potatoes, tomatoes 
oatmeal, eggs 

tu jest „brzydki” sposób to zrobić:

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) 

Jak widać, mamy, które powielane print po pętli . Paskudny!

Oto rozwiązanie przy użyciu groupby:

from itertools import groupby 

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

groupby trwa iterable (things) oraz przycisk funkcyjny. Sprawdza każdy element iteracji i stosuje funkcję klucza. Kiedy klucz zmienia wartość, tworzy się nowa grupa. Wynikiem jest iterator, który zwraca pary (key, group).

W tym przypadku użyjemy czeku na None jako naszej kluczowej funkcji. Dlatego potrzebujemy if key:, ponieważ będą grupy o rozmiarze 1 odpowiadające elementom None naszej listy. Pominiemy to.

Jak widać, groupby pozwala nam iteracyjne nad rzeczy naprawdę chcą iteracyjnego: grupy obiektów. Jest to bardziej naturalne dla naszego problemu, a kod upraszcza w rezultacie. Wygląda na to, że twój kod jest bardzo podobny do powyższego przykładu, z tym że twoja funkcja klucza sprawdzi różne właściwości obiektu (obj.id < 400 ...). Zostawię szczegóły implementacji do ciebie ...

1

Istnieje rozwiązanie, jeśli używasz iteratorów, next może dać specjalną wartość na końcu. Możesz więc użyć wskaźnika do sprawdzenia, czy bieżący obiekt jest prawdziwy, czy też zakończyłeś iterację.

spróbować czegoś takiego:

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

Oto sposób to zrobić z obiektem wartowniczego. Jest podobny do drugiej opcji, ale jaśniej myślę.

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