Python: przejście do następnej iteracji w zewnętrznej pętli

135

Chciałem wiedzieć, czy są jakieś wbudowane sposoby kontynuowania następnej iteracji w zewnętrznej pętli w Pythonie. Weźmy na przykład pod uwagę kod:

for ii in range(200):
    for jj in range(200, 400):
        ...block0...
        if something:
            continue
    ...block1...

Chcę, aby ta instrukcja kontynuowała wyjście z pętli jj i przejście do następnego elementu w pętli ii. Mogę zaimplementować tę logikę w inny sposób (ustawiając zmienną flagową), ale czy istnieje łatwy sposób, aby to zrobić, czy to jest jak proszenie o zbyt wiele?

Sahas
źródło
11
W rzeczywistości istnieje działająca instrukcja goto dla Pythona: entrian.com/goto . Został wydany jako żart primaaprilisowy :-), ale ma zadziałać.
codeape
3
Och, proszę, nie używaj tego żartu goto! Jest to niezwykle sprytne, ale później będziesz smutny, jeśli umieścisz go w swoim kodzie.
Ned Batchelder

Odpowiedzi:

71
for i in ...:
    for j in ...:
        for k in ...:
            if something:
                # continue loop i

W ogólnym przypadku, gdy masz wiele poziomów zapętlenia i break nie działa dla Ciebie (ponieważ chcesz kontynuować jedną z górnych pętli, a nie tę tuż nad bieżącą), możesz wykonać jedną z następujących czynności

Refaktoryzuj pętle, z których chcesz uciec, do funkcji

def inner():
    for j in ...:
        for k in ...:
            if something:
                return


for i in ...:
    inner()

Wadą jest to, że może być konieczne przekazanie tej nowej funkcji pewnych zmiennych, które wcześniej znajdowały się w zakresie. Możesz po prostu przekazać je jako parametry, uczynić z nich zmienne instancji w obiekcie (utwórz nowy obiekt tylko dla tej funkcji, jeśli ma to sens) lub zmienne globalne, pojedyncze elementy, cokolwiek (ehm, ehm).

Lub możesz zdefiniować innerjako funkcję zagnieżdżoną i pozwolić jej po prostu przechwytywać to, czego potrzebuje (może być wolniejsze?)

for i in ...:
    def inner():
        for j in ...:
            for k in ...:
                if something:
                    return
    inner()

Użyj wyjątków

Filozoficznie, do tego służą wyjątki, przerywanie przepływu programu przez strukturalne bloki programistyczne (jeśli, na, podczas), gdy jest to konieczne.

Zaletą jest to, że nie musisz dzielić jednego fragmentu kodu na wiele części. Jest to dobre, jeśli jest to jakieś obliczenie, które projektujesz podczas pisania w Pythonie. Wprowadzenie abstrakcji na tak wczesnym etapie może Cię spowolnić.

Wadą tego podejścia jest to, że autorzy interpreterów / kompilatorów zwykle zakładają, że wyjątki są wyjątkowe i odpowiednio je optymalizują.

class ContinueI(Exception):
    pass


continue_i = ContinueI()

for i in ...:
    try:
        for j in ...:
            for k in ...:
                if something:
                    raise continue_i
    except ContinueI:
        continue

Utwórz dla tego specjalną klasę wyjątków, aby nie ryzykować przypadkowego wyciszenia innego wyjątku.

Coś zupełnie innego

Jestem pewien, że istnieją jeszcze inne rozwiązania.

user7610
źródło
Nie mogę uwierzyć, że nie pomyślałem o przeniesieniu mojej drugiej pętli na inną metodę. Dostaję powolny
pmccallum
1
Według mnie używanie wyjątków to dobry sposób na osiągnięcie tego. Zgadzam się z @ user7610 - „filozoficznie, po to są wyjątki”.
Renato Byrro
Dobra stara zmienna boolowska i instrukcje if?
MrR
OP szuka alternatywnych rozwiązań. „Mogę zaimplementować tę logikę w inny sposób (ustawiając zmienną flagową) [...]”
user7610
149
for ii in range(200):
    for jj in range(200, 400):
        ...block0...
        if something:
            break
    else:
        ...block1...

Break przerwie pętlę wewnętrzną, a blok 1 nie zostanie wykonany (będzie działać tylko wtedy, gdy pętla wewnętrzna zostanie normalnie zakończona).

culebrón
źródło
1
Cześć, czy są jakieś inne takie opcje? Ponieważ chcę zrobić kolejną pętlę for w bloku 1 i w ten sposób mój kod byłby głęboki o 3 poziomy. Dziwna sytuacja.
Sahas
3
Dla mnie brzmi to tak, jakbyś próbował zrobić coś z pętlami for, które najlepiej byłoby podejść w inny sposób ...
Kimvais,
Tak. Dlatego nie użyłem struktury for..else. Teraz nadal potrzebowałbym pętli, ale użyję zmiennych flag, aby zmienić kontrolę.
Sahas
3
for...elsejest często użyteczną konstrukcją, chociaż może być myląca. Pamiętaj tylko, że elsew tym kontekście oznacza to „bez przerwy”.
asmeurer
Wydaje się, że jest to ograniczone tylko do dwóch warstw pętli. Muszę zaktualizować kod, który ma trzy zagnieżdżone pętle, a nowe wymaganie klienta oznacza, że ​​w pewnych okolicznościach najbardziej wewnętrzna pętla musi kontynuować następną iterację najbardziej zewnętrznej pętli. Zakładam, że twoja sugestia nie miałaby zastosowania do tego scenariusza.
kasperd
42

W innych językach możesz oznaczyć pętlę i przerwać ją. Propozycja ulepszenia Pythona (PEP) 3136 zasugerowała dodanie ich do Pythona, ale Guido odrzucił to :

Jednak odrzucam to, ponieważ kod tak skomplikowany, aby wymagał tej funkcji, jest bardzo rzadki. W większości przypadków istnieją obejścia, które generują czysty kod, na przykład za pomocą funkcji „return”. Chociaż jestem pewien, że istnieją pewne (rzadkie) rzeczywiste przypadki, w których przejrzystość kodu ucierpi z powodu refaktoryzacji, która umożliwia użycie zwrotu, jest to równoważone dwoma problemami:

  1. Złożoność dodana do języka na stałe. Dotyczy to nie tylko wszystkich implementacji Pythona, ale także każdego narzędzia do analizy źródła, a także oczywiście całej dokumentacji języka.

  2. Oczekuję, że ta funkcja będzie bardziej nadużywana, niż będzie prawidłowo używana, co doprowadzi do spadku czytelności kodu netto (mierzonego w całym kodzie napisanym odtąd w Pythonie). Leniwi programiści są wszędzie i zanim się zorientujesz, masz niesamowity bałagan w rękach niezrozumiałego kodu.

Więc jeśli na to liczyłeś, nie masz szczęścia, ale spójrz na jedną z pozostałych odpowiedzi, ponieważ są tam dobre opcje.

Dave Webb
źródło
4
Ciekawy. Zgadzam się z Guido tutaj. Chociaż byłoby to miłe w niektórych przypadkach, byłoby to nadużywane. Innym powodem, dla którego go nie implementujemy, jest to, że w tej chwili przenoszenie kodu między C i Pythonem jest całkiem proste. Gdy Python zacznie wybierać funkcje, których brakuje innym językom, staje się to trudniejsze. Weźmy na przykład fakt, że możesz mieć instrukcję else w pętli for w Pythonie ... to sprawia, że ​​kod jest mniej przenośny do innych języków.
eric.frederich
2
Wszyscy
witajcie
4
Jest to bardziej ponura dyskusja niż dobry kontrargument, ale wydaje mi się, że zachowanie for-elsejest bardziej skomplikowane, trudniejsze do odczytania i prawdopodobnie bardziej nadużywane (jeśli nie zwykły błąd) niż byłyby nazwane pętle. Myślę, że użyłbym innego słowa kluczowego niż else- może coś takiego resume, jak byłoby dobre? Jesteś breakw pętli i resumejest zaraz po niej?
ArtOfWarfare,
5
To mnie smuci. Nie mogę uwierzyć, że jednocześnie kocham i nienawidzę Pythona. Tak piękna, ale taka wtf.
jlh
5
@jlh Głównie wtf dla mnie. Czasami myślę, że chce być inny nie dla uzasadnionego celu, ale po prostu być inny. To jest dobry przykład. Dość często spotykam się z koniecznością zrywania zewnętrznych pętli.
Rikaelus
14

Myślę, że mógłbyś zrobić coś takiego:

for ii in range(200):
    restart = False
    for jj in range(200, 400):
        ...block0...
        if something:
            restart = True
            break
    if restart:
        continue
    ...block1...
asmeurer
źródło
4
-1: OP wyraźnie stwierdził, że wiedzieli, że mogą zrobić coś takiego, a to po prostu wygląda jak bardziej niechlujna wersja zaakceptowanej odpowiedzi (która wyprzedza twoją o 8 miesięcy, więc nie mogło być tak, że po prostu przegapiłeś zaakceptowaną odpowiedź odpowiedź).
ArtOfWarfare,
10
Odpowiedź nie jest akceptowana jaśniejsze jeśli nigdy wcześniej nie widział for, elseprzed (a myślę, że większość ludzi, którzy nie pamiętają od szczytu głowy, jak to działa).
asmeurer
3

Myślę, że jednym z najłatwiejszych sposobów osiągnięcia tego jest zastąpienie „kontynuuj” wyrażeniem „przerwa”, tj

for ii in range(200):
 for jj in range(200, 400):
    ...block0...
    if something:
        break
 ...block1...       

Na przykład tutaj jest prosty kod, aby zobaczyć, jak dokładnie to działa:

for i in range(10):
    print("doing outer loop")
    print("i=",i)
    for p in range(10):
        print("doing inner loop")
        print("p=",p)
        if p==3:
            print("breaking from inner loop")
            break
    print("doing some code in outer loop")
Khelina Fedorchuk
źródło
2

Innym sposobem radzenia sobie z tego rodzaju problemem jest użycie Exception ().

for ii in range(200):
    try:
        for jj in range(200, 400):
            ...block0...
            if something:
                raise Exception()
    except Exception:
        continue
    ...block1...

Na przykład:

for n in range(1,4):
    for m in range(1,4):
        print n,'-',m

wynik:

    1-1
    1-2
    1-3
    2-1
    2-2
    2-3
    3-1
    3-2
    3-3

Zakładając, że chcemy przeskoczyć do zewnętrznej pętli n z pętli m, jeśli m = 3:

for n in range(1,4):
    try:
        for m in range(1,4):
            if m == 3:
                raise Exception()            
            print n,'-',m
    except Exception:
        continue

wynik:

    1-1
    1-2
    2-1
    2-2
    3-1
    3-2

Link referencyjny: http://www.programming-idioms.org/idiom/42/continue-outer-loop/1264/python

Patrick
źródło
1

Chcemy coś znaleźć, a następnie zatrzymać wewnętrzną iterację. Używam systemu flagowego.

for l in f:
    flag = True
    for e in r:
        if flag==False:continue
        if somecondition:
            do_something()
            flag=False
Esther
źródło
Nie wiem, dlaczego odrzucono Twoje rozwiązanie; ktoś wysłał w zasadzie dokładnie to samo i dostał 10 głosów „za”
Locane,
Nie mam szczęścia z przepełnieniem stosu.
Esther
1
Może dlatego, że w zasadzie jest już tam opublikowana ta sama rzecz, myślę, że ... I False:continuechodzi o ... nietypowe formatowanie. Jak to często bywa w systemach „naturalnych”, gdzie wykładnicza jest normą, wystarczy kilka razy mieć szczęście na SO, aby zgromadzić znaczną ilość punktów reputacji. W każdym razie moje „najlepsze” odpowiedzi są zazwyczaj najmniej popularne.
user7610
0

Właśnie zrobiłem coś takiego. Moim rozwiązaniem było zastąpienie wnętrza pętli funkcją rozumienia listy.

for ii in range(200):
    done = any([op(ii, jj) for jj in range(200, 400)])
    ...block0...
    if done:
        continue
    ...block1...

gdzie op jest operatorem boolowskim działającym na kombinacji ii i jj. W moim przypadku, jeśli którakolwiek z operacji zakończyła się pomyślnie.

To naprawdę nie różni się zbytnio od dzielenia kodu na funkcję, ale pomyślałem, że użycie operatora „any” do wykonania operacji logicznej OR na liście wartości logicznych i wykonanie logiki w jednej linii było interesujące. Unika również wywołania funkcji.

cagem12
źródło