Паттерн «первый подходящий»: поиск в цикле Python
Три способа найти первый подходящий элемент: цикл с break, next с генератором и ветка «не нашлось» — с дефолтом вместо падения.
Редакция Питоники
Половина поисковых задач — не «найди все», а «найди первый»: первый свободный слот на парковке, первый файл с нужным именем, первый незаблокированный пользователь. Если нужен только первый подходящий элемент, цикл обязан останавливаться на нём: перебирать дальше незачем. Разберём три способа: полный перебор и его расточительность, цикл с break и переменной found, функцию next с генератором — и что делать, когда ничего не нашлось.
Цена лишнего перебора
Прежде чем искать способы, стоит понять, сколько стоит лишний перебор. Проверка одного элемента — это сравнение плюс ветвление; на списке из миллиона элементов полный обход это миллион сравнений, а нужный ответ может лежать в первом десятке.
nums = [3, 8, 15, 22, 31]
checked = 0
for n in nums:
checked += 1
if n > 10:
print('нашли', n, 'за', checked, 'шага')
break
нашли 15 за 3 шага
Три проверки вместо пяти — на пяти элементах разница не видна. На настоящем файле вроде лога на сто тысяч строк она становится ощутимой: полный перебор читает файл до конца, а next с генератором останавливается на первой подходящей строке. Разница в скорости, а в случае с файлом — ещё и в том, что остальной файл вообще не читается.
Задача: первый элемент больше порога
temps = [21, 18, 25, 19, 27, 16]
for t in temps:
if t > 24:
print('подошло:', t)
подошло: 25 подошло: 27
Цикл честно отработал и нашёл оба подходящих значения — но задача была про одно. Двадцать семь проверялись уже после того, как двадцать пять закрыли вопрос: для задачи «первый» это потраченное впустую время, а на списке из миллиона элементов — миллион проверок вместо одной. Нужен способ остановиться.
Цикл с break: переменная found
temps = [21, 18, 25, 19, 27, 16]
found = None
for t in temps:
if t > 24:
found = t
break
print(found)
25
Переменная found стартует со значением None — «пока ничего не нашли». Как только условие выполнилось, в found ложится элемент и break рвёт цикл: двадцать семь в перебор не попали вовсе. Каркас из трёх частей — стартовое None, условие, break — узнаваем в любом проекте: это канонический поиск первого совпадения, break разбирали подробно в своём уроке.
temps = [21, 18, 23, 19]
found = None
for t in temps:
if t > 24:
found = t
break
print(found)
print(found is None)
None True
Ни одно значение не перешагнуло порог: break не случился, и found осталась None. Сравнение found is None — канонический способ проверить, состоялся ли поиск. None здесь не случайность, а честный ответ: специальное значение «ничего», которое легко отличить от настоящих данных. А блок else у цикла из урока про поиск без флагов умеет оформлять ветку «не нашлось» и без переменной-результата.
next: то же самое в одну строку
temps = [21, 18, 25, 19, 27, 16]
found = next(t for t in temps if t > 24)
print(found)
cold = next((t for t in temps if t < 15), 'ни одной')
print(cold)
25 ни одной
next принимает генератор — записанную в одну строку проверку «каждый t, который больше 24» — и возвращает первый подходящий элемент, останавливая перебор ровно как break. Второй вызов показывает главный навык: у next есть второй аргумент, значение по умолчанию. Холодных дней в списке нет, и вместо падения next вернул заготовку «ни одной». Обратите внимание на скобки: генератор с дефолтом берётся в свои круглые скобки — next((...), значение).
ages = {'Анна': 15, 'Борис': 21, 'Вера': 19}
adult = next((name for name, age in ages.items() if age >= 18), None)
print(adult)
Борис
Генератор бегает не только по спискам: обход словаря по items() отдаёт пары «имя — возраст», распаковка разбирает каждую на две переменные, а проверка отбирает первого совершеннолетнего. Перебор идёт в порядке вставки, поэтому ответ Борис: он стоит в словаре раньше Веры, у которой тоже подходящий возраст.
files = ['отчёт.docx', 'смета.txt', 'договор.pdf', 'заметки.txt']
first_txt = next((f for f in files if f.endswith('.txt')), None)
print(first_txt)
смета.txt
Метод endswith проверяет расширение, и next отдаёт первую смету — даже не глядя на файлы после неё. Условие внутри генератора может быть каким угодно сложным: вхождение подстроки, длина, принадлежность списку. Меняются данные и условие, каркас не трогается.
temps = [21, 18, 23]
for t in temps:
if t > 24:
print('нашли:', t)
break
else:
print('ничего не нашли')
ничего не нашли
Блок else у цикла выполняется, только если break не случился, — то есть когда перебор дошёл до конца и совпадений не нашлось. Три способа решают одну задачу: выбирайте по вкусу читателя вашего кода.
Найти не значение, а его номер
Часто нужен не сам элемент, а его позиция: какой по счёту оказался подходящий, чтобы потом взять соседний или вырезать из списка. Для этого к next добавляется enumerate — он даёт генератор пар «номер — элемент».
nums = [4, 7, 11, 15]
idx = next((i for i, n in enumerate(nums) if n % 3 == 0), -1)
print('индекс:', idx)
print('значение:', nums[idx])
индекс: 3 значение: 15
Пятнадцать стоит под индексом 3 — это первое число, кратное трём. Дефолт -1 выбран не случайно: минус единица заведомо не может быть настоящим индексом, поэтому по ней сразу видно, что совпадения не было. None тут был бы слабее: None в списке индекса не живёт, но проверить его пришлось бы отдельно.
Условие из нескольких правил
Условие внутри генератора не обязано быть простым. Когда правил два, их соединяют через and — и тогда поиск идёт по элементам, удовлетворяющим обоим сразу.
data = [('id1', 10), ('id2', 3), ('id3', 7)]
winner = next((i for i, n in data if n > 5), None)
print(winner)
id1
id1 проходит: десять больше пяти. id2 отпадает по значению, id3 не подходит — но до него перебор не дошёл. Тот же генератор легко превращается в «найти все подходящие»: замените next на включение, и вы получите список. Разница между «первый» и «все» — один вызов.
nums = [3, 8, 15, 22, 31]
print(next((n for n in nums if n > 10), None))
print([n for n in nums if n > 10])
15 [15, 22, 31]
Одна и та же проверка в двух обёртках: next отдаёт первый результат и останавливается, включение собирает все совпадения. Второе стоит дороже: перебор проходит до конца. Если нужны оба ответа — считайте один раз, сохранив результаты в список, и берите первый элемент из него.
Когда next — лишний
У next есть встроенные соседи, и для части задач они справляются лучше. min и max умеют искать по ключу, а list.index — искать позицию. Знать про них полезно: иногда строка с next длиннее, чем встроенный метод.
pairs = [('a', 5), ('b', 2), ('c', 9)]
print(min(pairs, key=lambda p: p[1]))
print(max(pairs, key=lambda p: p[1]))
('b', 2)
('c', 9)min с ключом находит пару с минимальным вторым элементом — b с двойкой. Но у min есть важное отличие от next: он не останавливается на первом совпадении, а обходит всё и сравнивает. При равных значениях min вернёт первое, max — последнее, и в этом тоже разница: для «первого максимума» next честнее.
| Способ | Останавливается на первом | Если не нашлось |
|---|---|---|
| Цикл с break и found | Да | Что положите сами, обычно None |
| next с генератором | Да | Дефолт, а без него StopIteration |
| Цикл с else | Да | Ветка else выполняется |
all_warm = [t for t in temps if t > 24] # все совпадения: [25, 27]
first_warm = all_warm[0] # первый, но перебор уже прошёл целиком
Сначала предскажи ответ в голове — это главный навык программиста.
nums = [7, 3, 8, 4]
first = None
for n in nums:
if n % 2 == 0:
first = n
break
print(first)
words = ['кот', 'пёс', 'ёж']
found = next((w for w in words if len(w) == 2), 'нет')
print(found)
print(next((n for n in [1, 3, 5] if n > 10), -1))
1. Зачем в поиске первого совпадения нужен break?
2. Что вернёт next с генератором, если подходящих элементов нет, но передан дефолт?
3. Зачем в каркасе поиска переменную found стартуют со значением None?
4. Почему next((...), значение) требует скобок вокруг генератора?
5. Чем next с генератором отличается от спискового включения с [0]?
6. Как найти индекс первого элемента, кратного трём?
7. Почему для индекса дефолтом выбирают -1, а не None?
8. Чем next отличается от max с ключом?
В списке emails = ['a@mail.ru', 'b@site.org', 'c@mail.ru'] найди первый адрес на домене site.org и выведи его; если такого нет — выведи строку нет. Используй next с дефолтом.
Как найти первый подходящий элемент в списке Python?
Три способа: цикл с break и переменной found, функция next с генератором next(x for x in xs if x > 0) и списковое включение, если нужны все совпадения. Для одиночного поиска next экономит перебор: он останавливается на первом.
Урок 6: breakЧто произойдёт, если next не найдёт подходящий элемент?
Без дефолта — исключение StopIteration: вернуть нечего. С дефолтом next((...), значение) вернёт запасное значение. В поисковом коде дефолт задают всегда — это страховка от падения на пустых данных.
Чем None отличается от пустой строки в результате поиска?
None — явный знак «ничего не нашли», а пустая строка может быть настоящим значением: пустой комментарий, пустое имя. Поэтому результат поиска объявляют как None и проверяют found is None.
Как оформить ветку «не нашлось» в том же цикле?
Блоком else у цикла: он выполнится, только если break не случился, то есть совпадений не было вообще. Разбор приёма — в уроке про else у циклов.
Урок 9: else у цикловКак найти индекс первого подходящего элемента списка?
Через enumerate внутри генератора: next((i for i, n in enumerate(nums) if n > 10), -1). enumerate даёт пары «номер — элемент», поэтому условие проверяет значение, а next возвращает номер. Дефолт -1 выбран как заведомо невозможный индекс.
Урок 4: enumerateПочему next без дефолта — плохая идея в рабочем коде?
Потому что на пустых или изменившихся данных он бросит StopIteration, и программа упадёт вместо того, чтобы обработать отсутствие совпадения. Дефолт превращает «не нашлось» в обычное значение, с которым дальше работают: None, пустая строка или -1 в зависимости от типа задачи.
Когда полный перебор всё-таки лучше next?
Когда элементов немного и важнее читаемость: any(условие for ...) скажет «есть ли» одной строкой, а next с дефолтом выглядит сложнее. Оптимизировать стоит там, где данных много или цикл выполняется часто — лог-парсер, поток событий.
Понравился урок? Сошлитесь на него
«Если нужен только первый подходящий элемент, цикл обязан останавливаться на нём: перебирать дальше незачем.»
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что дальше
Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.
Что читать дальше
Циклы · Урок 6
Оператор break в Python: выход из цикла
break выходит из цикла немедленно: нашли нужное — перебирать остальное незачем. Разбираем break в for и while, поиск первого совпадения и мёртвый код после выхода.
Циклы · Урок 9
else у циклов в Python: поиск без флагов
У циклов for и while есть собственный else: он срабатывает, если перебор дошёл до конца без break. Поиск «нашли или нет» — в один заход и без флагов.
Циклы · Урок 15
any и all в Python: проверка без ручного цикла
any и all отвечают на вопросы «есть ли хоть один» и «все ли до единого» одной строкой — вместо цикла с булевым флагом.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
json · Урок 11
Фильтрация данных: найти нужное в JSON
Включение с условием для отбора, next с default для первого подходящего, any и all для вопросов ко всей коллекции — работаем с выгрузкой заказов.
python next первый элемент по условиюpython найти объект в json
BeautifulSoup / Scrapy · Урок 3
Поиск элементов: find, find_all и CSS-селекторы в BeautifulSoup
Два метода find и find_all плюс семейство select закрывают большинство задач поиска. Разбираем их отличия, ловушки с class_ и attrs и применяем к каталогу товаров.
beautifulsoup найти элемент по классупоиск элементов html python
Циклы · Урок 4
enumerate в Python: индекс и значение вместе
Нужен номер элемента вместе с самим элементом? enumerate отдаёт циклу пары «индекс — значение»: без ручного счётчика и с нумерацией с единицы через start=1.
нумерация в цикле pythonpython номер элемента в списке