Количество повторений, greedy vs non-greedy, владение
Квантификаторы указывают, сколько раз должен повторяться предыдущий элемент шаблона. Это ключевой инструмент для гибкого поиска.
| Квантификатор | Значение | Пример |
|---|---|---|
* | 0 или более | a* — '', 'a', 'aa', ... |
+ | 1 или более | a+ — 'a', 'aa', ... |
? | 0 или 1 | a? — '', 'a' |
{n} | ровно n | a{3} — 'aaa' |
{n,} | n или более | a{2,} — 'aa', 'aaa', ... |
{n,m} | от n до m | a{2,4} — 'aa', 'aaa', 'aaaa' |
import re
# Ноль или более букв 'a'
re.search(r'a*', 'bbb') # '' (пустая строка в начале)
re.search(r'a*', 'abb') # 'a'
re.search(r'a*', 'aaabb') # 'aaa'
# Практический пример: пробельные символы
re.search(r'\s*', 'hello') # '' (нет пробелов)
re.search(r'\s*', ' hello') # ' ' (два пробела)Важно:
*может соответствовать пустой строке, что иногда приводит к неожиданным результатам.
# Один или более букв 'a'
re.search(r'a+', 'bbb') # None (нет 'a')
re.search(r'a+', 'abb') # 'a'
re.search(r'a+', 'aaabb') # 'aaa'
# Практический пример: числа
re.search(r'\d+', 'abc123def') # '123'
re.findall(r'\d+', 'a1b23c456') # ['1', '23', '456']# Ноль или один символ 's' (множественное число)
re.search(r'cats?', 'cat') # 'cat'
re.search(r'cats?', 'cats') # 'cats'
# Опциональный дефис в телефоне
re.search(r'\d{3}-?\d{4}', '1234567') # '1234567'
re.search(r'\d{3}-?\d{4}', '123-4567') # '123-4567'# Ровно 3 цифры
re.search(r'\d{3}', 'abc123def') # '123'
# От 2 до 4 букв
re.search(r'[a-z]{2,4}', 'abcdef') # 'abcd' (жадно)
# 3 или более цифр
re.search(r'\d{3,}', '12345') # '12345'# Дата в формате YYYY-MM-DD
re.search(r'\d{4}-\d{2}-\d{2}', '2024-03-09') # '2024-03-09'
# Время в формате HH:MM
re.search(r'\d{2}:\d{2}', '14:30') # '14:30'
# HEX-цвет
re.search(r'#[0-9a-fA-F]{6}', '#FF5733') # '#FF5733'По умолчанию квантификаторы жадные — захватывают максимально возможное количество символов.
text = '<div>content</div>'
# Жадный: захватит всё от первого < до последнего >
re.search(r'<.*>', text) # '<div>content</div>'Добавьте ? после квантификатора для минимального захвата:
# Нежадный: захватит минимальное количество
re.search(r'<.*?>', text) # '<div>'
# Все теги по отдельности
re.findall(r'<.*?>', text) # ['<div>', '</div>']text = '"first" and "second"'
# Жадный: захватит всё между первой и последней кавычкой
re.search(r'".*"', text) # '"first" and "second"'
# Нежадный: захватит каждую цитату отдельно
re.findall(r'.*?"', text) # ['"', '"'] # кавычки
re.findall(r'"[^"]*"', text) # ['"first"', '"second"'] # правильно!В стандартном re нет possessive квантификаторов, но они есть в библиотеке regex:
# В библиотеке regex:
import regex
# Possessive: захватывает и не отдаёт при backtracking
regex.search(r'a++b', 'aaaaac') # None — быстрее, чем a+bПри неудачном совпадении движок возвращается назад (backtracking):
# Шаблон ищет 'a' один или более, затем 'b'
re.search(r'a+b', 'aaaaac') # None
# Процесс:
# 1. a+ захватывает 'aaaaa'
# 2. b несоответствует 'c'
# 3. Backtracking: a+ отдаёт одну 'a'
# 4. b несоответствует 'a'
# 5. Продолжается пока a+ не отдаст все 'a'
# 6. b несоответствует 'a', результат None# Минимум 8 символов
pattern = r'^.{8,}$'
bool(re.match(pattern, 'password')) # False — 8 символов, ок
bool(re.match(pattern, 'pass')) # False — меньше 8html = '<p>First</p><p>Second</p>'
# Жадный — неправильно
re.findall(r'<p>.*</p>', html) # ['<p>First</p><p>Second</p>']
# Нежадный — правильно
re.findall(r'<p>.*?</p>', html) # ['<p>First</p>', '<p>Second</p>']text = 'Email: user@example.com, admin@test.org'
# Простой паттерн
re.findall(r'\w+@\w+\.\w+', text) # ['user@example.com', 'admin@test.org']text = 'Hello world with extra spaces'
# Заменить множественные пробелы на один
re.sub(r'\s+', ' ', text) # 'Hello world with extra spaces'# Опасный паттерн: (a+)+ на длинной строке без 'b'
pattern = r'(a+)+b'
text = 'a' * 30 + 'c'
# re.search(pattern, text) # Будет выполняться очень долго!
# Решение: используйте possessive или atomic groups (в regex)
# Или упростите паттерн: a+btext = '<div>content</div>'
# Ошибка: жадный захват
re.search(r'<.*>', text) # '<div>content</div>'
# Правильно: нежадный
re.search(r'<.*?>', text) # '<div>'# * можетсоответствует пустую строку
re.findall(r'a*', 'bbb') # ['', '', '', ''] — 4 пустых совпадения!
# Используйте + для хотя бы одного символа
re.findall(r'a+', 'bbb') # [] — правильно* — 0 или более, + — 1 или более, ? — 0 или 1{n}, {n,}, {n,m} — точное количество повторений? для нежадного режима: *?, +?, ??, {n,m}?* можетсоответствует пустую строку, что иногда нежелательноВопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.