Поиск: 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:
try:
'молоко'.index('соль')
except ValueError as e:
print('Ошибка:', e)
Ошибка: substring not found
text = 'заказ не найден'
pos = text.find('товар')
print(pos)
print(text[pos:])
-1 н
Поиск не нашёл «товар», вернул -1 — и срез text[-1:] послушно вырезал последний символ строки: букву «н». Ни ошибки, ни пустой строки — тихий баг, который легко пропустить. Правильный шаблон — сначала проверка:
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 и правда быстрее.
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 s | True / False |
| Где первое вхождение? | s.find('x') | Индекс или -1 |
| Где последнее вхождение? | s.rfind('x') | Индекс или -1 |
| Сколько вхождений? | s.count('x') | Число |
| Начинается ли с? | s.startswith('x') | True / False |
| Заканчивается ли на? | s.endswith('x') | True / False |
| Искать с позиции N | s.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 'скотч')
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 нужен второй аргумент с позицией?
Переменная order = 'ORD-2026-0042' уже задана. Выведи три строки: начинается ли номер с префикса «ORD-» (True/False), позицию первого дефиса и номер без префикса и года — то есть часть после последнего дефиса.
Как проверить, что строка содержит подстроку в 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-канал или свой блог — так о проекте узнают новые читатели.
Что дальше
Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.
Что читать дальше
Строки · Урок 7
replace: замена подстрок
Заменить одно слово на другое во всём тексте, убрать мусор из середины строки, вычистить телефон до одних цифр — всё это делает один метод: replace.
Строки · Урок 9
split: разобрать строку
Одна строка данных редко живёт одна: CSV-запись, строка лога, поля формы. Метод split режет строку по разделителю и возвращает список частей — разбираем все варианты, от запятой до splitlines.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
BeautifulSoup / Scrapy · Урок 3
Поиск элементов: find, find_all и CSS-селекторы в BeautifulSoup
Два метода find и find_all плюс семейство select закрывают большинство задач поиска. Разбираем их отличия, ловушки с class_ и attrs и применяем к каталогу товаров.
beautifulsoup find find_allfind и find_all в beautifulsoup разница
Словари · Урок 7
in: проверка ключа в словаре
Оператор in отвечает на вопрос «есть ли такой ключ»: проверка перед доступом вместо KeyError, not in, грабли с проверкой значений и причина скорости поиска.
проверить есть ли ключ в словареоператор in python словарь
Строки · Урок 15
count: считаем вхождения в строке
count отвечает на вопрос «сколько?»: вхождений подстроки, символов в слове, слов в отзыве. Разбираем непересекающийся подсчёт, диапазон поиска и ловушку строки 'ааа'.
python количество вхождений подстрокиpython посчитать символы в строке