Перейти к содержанию

412 уроков, 25 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 8 из 20 Начальный 20 мин 120 XP

Поиск: find, rfind и in

Поиск в строке отвечает на три разных вопроса: «есть ли?», «где?» и «с чего начинается?». Разбираем in, find, rfind, startswith и endswith — и опасное значение -1, которое притворяется индексом.

Редакция Питоники

Половина работы со строками — это поиск: пришло ли письмо с нужной темой, есть ли код ошибки в строке лога, чем кончается имя файла. В Python под эти вопросы отвечают разные инструменты: оператор in говорит «есть ли подстрока», метод find — «на какой позиции», rfind — «на какой позиции с конца», а startswith и endswith проверяют начало и конец. На вопрос «сколько раз встретилась» отвечает count из урока 15 — сейчас же про позицию и наличие.

Сценарий: разбор строки лога

Строка лога почти всегда устроена одинаково: время, уровень, код, текст сообщения. Читать её глазами удобно, а вот вытащить код программно — задача регулярная: по коду ошибки считают, какие сбои повторяются. Методы поиска как раз для этого.

достаём код ошибки из лога
log = '2026-03-14 ERROR код 500 не найден'
pos = log.find('код')
print(pos)
print(log[pos + 4:pos + 7])
Вывод
17
500

Find вернул позицию слова «код», а срез от него на три символа — сам номер ошибки. Сдвиг на четыре — это длина слова плюс пробел после него. Такая связка «найди и вырежи» — рабочая лошадка разбора текста, и дальше в разделе она встретится ещё не раз.

find: где первое вхождение

Метод s.find(подстрока) возвращает индекс — номер позиции, с которой подстрока начинается в строке. Нумерация та же, что у срезов из урока 2: с нуля. Найдём код ошибки в строке лога:

позиция подстроки в логе
log = 'Ошибка в модуле оплаты: код 500'
pos = log.find('оплаты')
print(pos)
print(log[pos:pos + 6])
Вывод
16
оплаты

find вернул 16 — именно с этой позиции начинается слово «оплаты». А срез log[pos:pos + 6] вырезал само слово: длина «оплаты» — шесть символов. Это классическая связка «найди и вырежи», она ещё вернётся в конце урока.

первое вхождение и промах
print('молоко'.find('ко'))
print('молоко'.find('мол'))
print('молоко'.find('соль'))
Вывод
4
0
-1

Три случая из одной строки: «ко» нашлось на позиции 4, «мол» — с самого начала, то есть с нуля. А «соль» в слове «молоко» нет, и find вернул -1. Минус единица — не номер «ошибки», а честный ответ: такой подстроки в строке нет. Запомните это значение — оно ещё сыграет с вами злую шутку.

У find есть брат-близнец index(): он ищет так же, но при неудаче не возвращает -1, а бросает исключение ValueError:

index не прощает промах
try:
    'молоко'.index('соль')
except ValueError as e:
    print('Ошибка:', e)
Вывод
Ошибка: substring not found
питфолл вживую: срез по промаху
text = 'заказ не найден'
pos = text.find('товар')
print(pos)
print(text[pos:])
Вывод
-1
н

Поиск не нашёл «товар», вернул -1 — и срез text[-1:] послушно вырезал последний символ строки: букву «н». Ни ошибки, ни пустой строки — тихий баг, который легко пропустить. Правильный шаблон — сначала проверка:

безопасная связка find и среза
text = 'заказ не найден'
pos = text.find('товар')
if pos != -1:
    print(text[pos:])
else:
    print('Подстроки нет')
Вывод
Подстроки нет

rfind: где последнее вхождение

find ищет слева направо и останавливается на первом совпадении. rfind идёт с той же строки, но возвращает последнее вхождение. Хрестоматийный случай — точка в имени файла: их может быть несколько, а расширение определяется последней:

первая и последняя точка
filename = 'report.2026.txt'
print(filename.find('.'))
print(filename.rfind('.'))
print(filename[filename.rfind('.'):])
Вывод
6
11
.txt

Первая точка — на позиции 6, последняя — на 11, и именно она отрезает расширение: .txt. Если искать через find, при имени вроде archive.tar.gz получится неверный «хвост» — этот же приём через rfind надёжен при любом числе точек.

in: проверка без позиции

Когда позиция не нужна, а нужно только «да или нет», пишут оператор in — он читается почти как человеческая речь и возвращает True или False:

есть ли товар в заказе
order = 'молоко, хлеб, сыр'
print('хлеб' in order)
print('кефир' in order)
print('кефир' not in order)
Вывод
True
False
True

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

подстрока прячется внутри слова
print('кот' in 'скотч')
Вывод
True

«кот» действительно сидит внутри «скотч» — со второй буквы. Если для задачи важны именно слова, строку сначала разбирают на слова методом split из урока 9 и проверяют вхождение уже в списке.

Find не проверяет границы слова

Тот же принцип касается и find: он ищет символы подряд, не глядя, где начинается и где заканчивается слово. Поэтому «WORD» находится внутри «PASSWORD» — и это не ошибка метода, а его буквальное поведение.

совпадение внутри слова
text = 'PASSWORD'
print(text.find('WORD'))
print(text.find('PASS'))
Вывод
4
0

startswith и endswith: начало и конец

Два метода проверяют префикс и суффикс: s.startswith(префикс) — начинается ли строка с него, s.endswith(суффикс) — кончается ли. Идеальны для номеров заказов и расширений файлов:

проверка префикса и суффикса
order_id = 'ORD-2026-0042'
filename = 'отчёт.xlsx'
print(order_id.startswith('ORD-'))
print(filename.endswith('.xlsx'))
print(filename.endswith('.docx'))
Вывод
True
True
False
картинки или нет
for name in ['фото.jpg', 'схема.png', 'док.pdf']:
    print(name, name.endswith(('.jpg', '.png')))
Вывод
фото.jpg True
схема.png True
док.pdf False

Найти и вырезать: find плюс срез

Самая частая связка урока: найти границы и вырезать середину. В письме тема магазина часто помечена квадратными скобками — достанем её:

вырезаем метку между скобками
email = 'Тема: [Склад] Отчёт за март'
start = email.find('[')
end = email.find(']')
print(email[start + 1:end])
Вывод
Склад

find вернул позиции скобок, а срез email[start + 1:end] забрал всё между ними: левая скобка не нужна, поэтому + 1, правая не входит в срез по своему правилу. Всё вместе — locate-and-slice, приём, который решает добрую половину задач «вытащи кусочек из строки».

Второй аргумент find: искать дальше

У find есть необязательный второй аргумент — позиция, с которой искать. Он нужен, чтобы перебрать все вхождения: нашли одно, спросили следующее, начали искать с позиции на единицу больше найденной. Так получают все совпадения без count из урока 15.

перебор всех вхождений
s = 'аба'
i = s.find('а')
while i != -1:
    print(i)
    i = s.find('а', i + 1)
Вывод
0
2

Цикл остановился на -1 — совпадений больше нет. Обратите внимание на i + 1: если начать следующий поиск с той же позиции, метод найдёт то же самое вхождение и цикл зациклится. Именно на этом many застревают в первый раз.

Как работает поиск

Поиск подстроки в Python — простой линейный алгоритм: движок идёт по строке слева направо и в каждой позиции пытается совместить первый символ подстроки с текущим символом строки. Первое полное совпадение — и есть ответ.

Отсюда два практических следствия. Первое: поиск большой подстроки в большой строке стоит примерно как произведение их длин — на файле в несколько мегабайт искать тысячу раз подряд — дорого, и такой код лучше не писать. Второе: Python ищет первое вхождение, поэтому если в строке код ошибки встречается несколько раз, find покажет только первое — остальные придётся искать самому, как в примере выше.

Карточка инструментов поиска

ИнструментОтвечает на вопросВозвращает
in«подстрока есть?»True или False
s.find(под)«где первое вхождение?»индекс или -1
s.rfind(под)«где последнее вхождение?»индекс или -1
s.index(под)«где первое?» (строго)индекс или ValueError
s.startswith(п)«начинается с префикса?»True или False
s.endswith(с)«кончается суффиксом?»True или False

Дальше по разделу: разобрать строку на куски поможет split из урока 9, посчитать вхождения — count из урока 15, а собрать строку из частей обратно — join из урока 10.

Мини-практика: все коды ошибок из строки

В строке лога встретилось два кода ошибки, и нужно вытащить оба. Найдите первое вхождение слова «код», вырежите три символа после него, а затем продолжите поиск с найденной позиции.

перебор кодов в логе
log = '2026-03-14 ERROR код 500, повтор: код 404'
i = log.find('код ')
while i != -1:
    print(log[i + 4:i + 7])
    i = log.find('код ', i + 1)
Вывод
500
404

Два кода напечатаны, цикл завершился. Отступление от первого вхождения видно сразу: когда в строке код один, достаточно одного find и одного среза, как в примере в начале урока. Перебор нужен именно тогда, когда вхождений заранее неизвестно сколько.

Как найти подстроку в Python: find, rfind и in

Методы поиска отвечают на три разных вопроса, и их регулярно путают. find — «где начинается первое вхождение?». rfind — «где начинается последнее вхождение?». in — «есть ли вообще?». Разница между find и in не в логике, а в цене: find просматривает строку и возвращает индекс, in останавливается на первом совпадении и отвечает да или нет. Для проверки существования in и правда быстрее.

find, rfind и count на одной строке
text = 'кот и кот и кот'
print(text.find('кот'))
print(text.rfind('кот'))
print(text.find('кот', 5))
print(text.count('кот'))
Вывод
0
12
6
3

Третий вызов — find с начальным индексом: искать дальше, чем с шестого символа. Это базовый приём «найти все вхождения», который потом используют в цикле. И заодно объясняется расхождение с count: count считает непересекающиеся вхождения и после каждого совпадения продолжает поиск за его концом — про это подробно в уроке про count.

Разбор строк по найденной позиции

Вот где find раскрывается по-настоящему — в разборе составных строк. Адрес, почта, строка лога: каждое поле найдено по своему маркеру, а дальше вырезано по индексам. Такой разбор читается гораздо понятнее, чем цепочка split с разделителем, который в данных то есть, то нет.

почта: домен по позиции @
email = 'anna@example.ru'
print(email.find('@'))
print(email[email.find('@') + 1:])
print(email.endswith('.ru'))
print(email.startswith('anna'))
Вывод
4
example.ru
True
True

Разбор здесь такой: находим @, отбрасываем его и всё, что до него, — получаем домен. startswith и endswith отвечают на вопрос «а с краёв ли это?» и работают быстрее, чем find(...) == 0: они не создают срез и не возвращают индекс. Для проверки «заканчивается ли на .ru» это ровно то, что нужно.

путь: имя файла и расширение
url = 'https://site.ru/docs/guide?page=2'
print(url.find('/'))
print(url.find('?') + 1)
print(url.rfind('/'))
print(url[url.find('/') + 1:])
Вывод
6
27
20
/site.ru/docs/guide?page=2

Здесь find применяется трижды, и это нормально: один и тот же метод для трёх разных маркеров. Последняя строка показывает приём, который стоит запомнить — «всё после первого слэша»: url[url.find('/') + 1:]. Слева от find стоит -1, потому что индекс начинается с нуля, а мы хотим символ ПОСЛЕ найденного. Нечитаемо? Да. Но коротко, и работает.

ВопросМетодВозвращает
Есть ли подстрока?'x' in sTrue / False
Где первое вхождение?s.find('x')Индекс или -1
Где последнее вхождение?s.rfind('x')Индекс или -1
Сколько вхождений?s.count('x')Число
Начинается ли с?s.startswith('x')True / False
Заканчивается ли на?s.endswith('x')True / False
Искать с позиции Ns.find('x', N)Индекс или -1

Одна оговорка про in: он нечувствителен к типу левого операнда, но чувствителен к регистру. 'Error' in log не найдёт 'error' — и в логах это половина проблем. Рецепт: искать в приведённой копии строки, s.lower().find('error'), и помнить, что индекс тогда относится к копии, а не к оригиналу. Для коротких строк это не страшно, для длинных логов — уже да.

И ещё одно, что стоит знать про поиск: он не умеет искать по регулярному выражению. find ищет буквальную подстроку. Если нужно найти «слово, начинающееся с Error», или номер в конце строки, или дату в любом формате — нужен re. Это курс регулярных выражений, и он стоит рядом с базовой работой со строками. Но начинать стоит всё же с find: в большинстве задач «есть ли подстрока» он и нужен.

in отвечает на вопрос «есть ли?», find — «где?», rfind — «где в последний раз?». А минус единица в ответе find — это не сбой, а честное «не нашлось».

Что выведет код?

Сначала предскажи ответ в голове — это главный навык программиста.

print('молоко'.find('оло'))
s = 'аа-бб-вв'
print(s.rfind('-'))
print('кот' in 'скотч', 'код' in 'скотч')
Проверь себя
0 / 8

1. Что вернёт выражение 'который час'.find('час')?

2. Что вернёт метод find, если подстроки в строке нет?

3. s = 'заказ не найден', pos = s.find('товар'). Что вернёт s[pos:]?

4. Как надёжно получить расширение файла из имени report.2026.txt?

5. Чем оператор in отличается от метода find?

6. Что вернёт 'кот' in 'скотч'?

7. Что вернёт 'PASSWORD'.find('WORD')?

8. Зачем find нужен второй аргумент с позицией?

Карточки терминов
Запомнено: 0 / 9
Практика

Переменная order = 'ORD-2026-0042' уже задана. Выведи три строки: начинается ли номер с префикса «ORD-» (True/False), позицию первого дефиса и номер без префикса и года — то есть часть после последнего дефиса.

practice.py
Вопросы и ответы по уроку

Как проверить, что строка содержит подстроку в Python?

Оператором in: если 'ошибка' in log — выполнится ветка. Он возвращает True или False и читается как обычная фраза. Для подсчёта числа вхождений есть метод count, а для позиции — find.

Урок 15: count

Чем find отличается от index в Python?

Оба возвращают индекс первого вхождения подстроки. Разница в поведении при промахе: find возвращает -1, а index бросает исключение ValueError: substring not found. find удобен в проверках, index — когда отсутствие подстроки считается ошибкой.

Урок 2: срезы строк

Почему find возвращает -1 и когда это опасно?

Число -1 выбрано потому, что оно не может быть настоящим индексом вхождения — так сигнал «не нашлось» не путается с позицией ноль. Опасность в том, что -1 является валидным отрицательным индексом: срез s[pos:] при pos = -1 вернёт последний символ строки. Поэтому перед срезом всегда проверяют pos != -1.

Как найти последнее вхождение подстроки?

Методом rfind: он ищет справа и возвращает индекс последнего вхождения. Классический случай — расширение файла: name[name.rfind('.'):] отрежет хвост после последней точки даже в имени вроде archive.tar.gz.

Урок 8: find, rfind и in

Как найти все вхождения подстроки в строке?

Циклом с find и вторым аргументом: нашли позицию, обработали её и продолжили поиск с этой позиции плюс один. Метод count сразу скажет, сколько вхождений, но не покажет, где они — для этого нужен перебор.

Урок 15: count

Почему find возвращает -1, а не None?

Минус единица — целое число, и его удобно сравнивать: pos != -1. None здесь был бы неоднозначен: он означал бы и «не нашлось», и «значение не задано». Более строгий родственник index при промахе бросает ValueError.

Понравился урок? Сошлитесь на него

«Половина работы со строками — это поиск: пришло ли письмо с нужной темой, есть ли код ошибки в строке лога, чем кончается имя файла.»

Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Что дальше

Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.