Все мы знаем, что Python  —  довольно элегантный язык программирования, но, увы, у всех есть свои слабые стороны. Иногда Python выглядит не так аккуратно, как ему следовало бы. К примеру, нам нужно выйти из вложенного цикла как на примере ниже:

for a in list_a:
for b in list_b:
if condition(a,b):
break

Ключевое слово break позволяет выйти только из внутреннего цикла. Но можно ли напрямую сразу выйти из двух вложенных циклов? Есть ли для этого какие-нибудь ключевые слова или уловки? К сожалению, в Python нет встроенных средств для выполнения нужной операции.

Конечно, сравнивать языки друг с другом не стоит, но вот в PHP такое действие довольно просто можно провернуть:

foreach ($a_list as $a)
{
foreach ($b_list as $b)
{
if (condition($a, $b))
{
break 2; //break out of 2 loops
}
}
}

В PHP ключевое слово break принимает число в качестве аргумента, которое определяет, из скольки вложенных циклов нужно выйти. Значением по умолчанию установлено 1, что как раз позволяет выйти из крайнего цикла. Это довольно лаконичное и разумное решение, PHP выглядит привлекательнее с точки зрения написания кода.

Правда, это не значит, что всем пора бежать изучать PHP. Python все-таки очень гибкий язык, так что того же результата можно достичь многими другими способами.

В статье представлено 5 способов выхода из вложенного цикла в Python. В конце статьи также указано, как можно избежать проблемы вложенности в циклах.

1. Переменная-флаг

Первое решение довольно простое и эффективное. Чтобы выйти из цикла, надо объявить переменную и использовать ее как flag. Рассмотрим решение на примере ниже:

# добавьте переменную-флаг
break_out_flag = False
for i in range(5):
for j in range(5):
if j == 2 and i == 0:
break_out_flag = True
break
if break_out_flag:
break

Переменная break_out_flag выступает в качестве пометки, чтобы показать программе, когда следует выходить из внешнего цикла. Работает, но выглядит не очень красиво, так как приходится создавать новую переменную только ради решения такой маленькой проблемы. Можно обойтись и без этого. Рассмотрим другие варианты.

2. Вызов исключения

Если мы не можем использовать ключевое слово break как положено, почему бы тогда не выразить его действие другим способом? В Python существует возможность обработки исключений, поэтому из вложенного цикла можно выйти таким образом:

# вызовите исключение
try:
for i in range(5):
for j in range(5):
if j == 2 and i == 0:
raise StopIteration
except StopIteration:
pass

Код выше показывает, что break можно выразить как исключение и таким образом выйти из цикла.

3. Повторная проверка условия

Поскольку выполнение одного условия приводит к прерыванию цикла, проверка того же условия также кажется приемлемым решением. Вот как это выглядит:

# проверьте то же условие второй раз
for i in range(5):
for j in range(5):
if j == 2 and i == 0:
break
if j == 2 and i == 0:
break

Этот способ выполняет свою задачу, но выглядит он не очень эффективно, ведь проверять одно и то же условие много раз займет много времени и ресурсов.

4. Конструкция for-else

В Python есть особая конструкция for-else. Она не очень популярна, а некоторые совсем о ней не знают. Обычно, если пишут else, то только после if. А вот когда дело доходит до выхода из вложенных циклов, то такой необычный синтаксис может помочь в решении задачи:

# используйте конструкцию For-Else
for i in range(5):
for j in range(5):
if j == 2 and i == 0:
break
else: # можно вызывать, только если во внутреннем цикле нет break
continue
break

Этот метод берет все лучшее от синтаксиса for-else, так как код в блоке else сработает только в том случае, если внутренний цикл закончится без выхода. Если вы еще не знакомы с конструкцией for-else, то ознакомьтесь с кодом ниже  —  это конструкция, которая объясняет поведение конструкции:

# то же самое, что и конструкция for-else
for i in range(5):
for j in range(5):
if j == 2 and i == 0:
break
if not (j == 2 and i == 0):
continue
break

Если вкратце, то этот способ работает, но требует использования необычного синтаксиса.

5. Дополнительная функция

Если поместить вложенный цикл в функцию, то проблема выхода из цикла решается довольно просто, ведь вместо break можно использовать уже return.

# напишите для этого функцию
def check_sth():
for i in range(5):
for j in range(5):
if j == 2 and i == 0:
return
check_sth() # запустите функцию, где нужно

Это решение выглядит более понятно, тут нет переменных-флагов, нет try-except, for-else и ненужных проверок условий. Функции в Python настраиваются очень гибко, так что если вложенный цикл будет присутствовать по одному разу в каждой функции, то можно вызвать ее сразу во внешней функции:

def out_func():
# какой-то код
def check_sth():
for i in range(5):
for j in range(5):
if j == 2 and i == 0:
return
# какой-то код
check_sth() # запуск функции
# какой-то код

Правда, создание вложенной функции для двух циклов тоже выглядит не особо разумно.

Cовет: избегайте вложенных циклов

Но на деле, если не существует действительно удобных решений для выхода из вложенных циклов, тогда почему бы не постараться избегать их? Применяя вспомогательную функцию, можно избежать вложенных циклов:

# как избежать вложенных циклов
import itertools
for i, j in itertools.product(range(5), range(5)):
if j == 2 and i == 0:
break

Этот пример показывает, как с помощью функции itertools.product() можно обойтись без вложенности. Это простой способ вычисления декартова произведения на основе входных итераций. К несчастью, этим методом не получится избежать любых вложенных циклов. К примеру, он не сработает, если вам необходимо будет обработать бесконечные потоки данных в циклах.

Перед тем как функция itertools.product() начнет свою работу, ей необходимо получить на вход все переменные, сохраняя в памяти массивы значений для генерации результата. Таким образом, функция полезна только при конечном количестве входных данных. И всё же лучше следовать совету не прибегать к вложенным циклам, ведь это улучшает читаемость кода.

Заключение

У нас есть как минимум 5 возможных путей выхода из вложенных циклов в Python. Ни один из них не является настолько же удобным, как в PHP, но, по крайней мере, они все же существуют. К счастью, нам не обязательно использовать вложенные циклы, так как мы можем преобразовать их в простые циклы благодаря функции itertools.product().

Читайте также:

Читайте нас в TelegramVK и Яндекс.Дзен


Перевод статьи Yang Zhou: 5 Ways To Break Out of Nested Loops in Python

Предыдущая статьяКак научиться задавать вопросы, проектировать системы и выявлять ошибки?
Следующая статьяРаспределенное МО с Dask и Kubernetes на GCP