Жадность и лень, границы слов \b и якоря ^ $
Понимаем, почему квантор хватает «слишком много», и учимся привязывать паттерн к нужным местам.
Жадный квантор берёт максимум символов; добавление
?делает его ленивым — он берёт минимум.
Жадность по умолчанию
Кванторы * + {} по умолчанию жадные: они захватывают как можно больше символов. Это часто удивляет новичков. Попробуем вытащить тег из HTML:
import re
print(re.findall(r"<.+>", "<b>текст</b>"))
Вывод:
['<b>текст</b>']
Мы хотели получить <b>, а получили всю строку! Жадная .+ схватила всё от первой < до последней >, включая текст и закрывающий тег.
Ленивый режим: добавляем ?
Чтобы квантор брал минимум, после него ставят ?. Теперь .+? остановится на первой же подходящей >:
import re
print(re.findall(r"<.+?>", "<b>текст</b>"))
Вывод:
['<b>', '</b>']
Вот теперь — два отдельных тега. Запомните пару: .+ — жадная (макс), .+? — ленивая (мин). То же касается *?, ??, {2,5}?.
Якоря ^ и $
Якоря не совпадают с символами — они привязывают паттерн к позиции. ^ — начало строки, $ — конец. Связка ^...$ требует, чтобы паттерн описывал строку целиком:
import re
print(bool(re.search(r"^\d+$", "12345"))) # строка целиком из цифр
print(bool(re.search(r"^\d+$", "12a45"))) # есть буква — нет
Вывод:
True False
Это типовой способ валидации: ^...$ гарантирует, что вся строка соответствует шаблону, а не только её часть.
Граница слова \b
Граница слова \b — это позиция между символом-словом (\w) и не-словом (или краем строки). Она помогает искать целые слова, а не куски внутри других слов:
import re
print(re.findall(r"cat", "cat category scatter"))
print(re.findall(r"\bcat\b", "cat category scatter"))
Вывод:
['cat', 'cat', 'cat'] ['cat']
Без границ cat нашёлся и в «category», и в «scatter». С \bcat\b — только отдельное слово «cat». Это спасает от ложных совпадений внутри слов.
Итог
- Кванторы жадные по умолчанию: берут максимум. Добавьте
?для ленивого режима (минимум). ^— начало строки,$— конец; связка^...$проверяет строку целиком.\b— граница слова;\bслово\bищет целое слово, а не подстроку.