Опережение и ретроспектива, положительные и отрицательные_assertions
Lookahead и lookbehind — это утверждения (assertions), которые проверяют наличие шаблона вперёд или назад без захвата текста. Мощный инструмент для сложных условий поиска.
Assertions проверяют условие в позиции текста, но не захватывают символы:
^ — начало строки$ — конец строки\b — граница слова(?=...) — положительный lookahead(?!...) — отрицательный lookahead(?<=...) — положительный lookbehind(?<!...) — отрицательный lookbehindLookahead проверяет, что следует после текущей позиции.
Проверяет, что впереди есть указанный шаблон:
import re
# Найти число, за которым следует 'руб'
re.search(r'\d+(?=руб)', 'Цена: 100руб') # '100'
re.search(r'\d+(?=руб)', 'Цена: 100$') # None
# Без lookahead захватит и 'руб'
re.search(r'\d+руб', 'Цена: 100руб') # '100руб'Проверяет, что впереди НЕТ указанного шаблона:
# Найти число, за которым НЕ следует 'руб'
re.search(r'\d+(?!руб)', 'Цена: 100$') # '100'
re.search(r'\d+(?!руб)', 'Цена: 100руб') # None
# Проверка сложного пароля (без последовательных одинаковых символов)
re.search(r'^(?!.*(\w)\1).{8,}$', 'abc12345') # None — есть '11'
re.search(r'^(?!.*(\w)\1).{8,}$', 'abcdefgh') # MatchLookbehind проверяет, что было перед текущей позицией.
Проверяет, что перед позицией есть указанный шаблон:
# Найти число, перед которым стоит '$'
re.search(r'(?<=\$)\d+', 'Price: $100') # '100'
re.search(r'(?<=\$)\d+', 'Price: 100$') # None
# Без lookbehind захватит и '$'
re.search(r'\$\d+', 'Price: $100') # '$100'Проверяет, что перед позицией НЕТ указанного шаблона:
# Найти число, перед которым НЕ стоит '$'
re.search(r'(?<!\$)\d+', 'Price: 100') # '100'
re.search(r'(?<!\$)\d+', 'Price: $100') # None
# Найти слово без префикса 'un'
re.search(r'(?<!un)\w+', 'happy') # 'happy'
re.search(r'(?<!un)\w+', 'unhappy') # 'happy' — найдёт 'happy' после 'un'Можно использовать оба утверждения одновременно:
# Найти число между '$' и 'руб'
re.search(r'(?<=\$)\d+(?=руб)', '$100руб') # '100'
# Извлечение значения из строки формата
text = 'Цена: $150руб, Скидка: $50руб'
re.findall(r'(?<=\$)\d+(?=руб)', text) # ['150', '50']# Минимум 8 символов, хотя бы одна буква и одна цифра
pattern = r'^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$'
# (?=.*[A-Za-z]) — есть хотя бы одна буква
# (?=.*\d) — есть хотя бы одна цифра
# [A-Za-z\d]{8,} — только буквы и цифры, минимум 8
bool(re.match(pattern, 'password1')) # True
bool(re.match(pattern, 'password')) # False — нет цифры
bool(re.match(pattern, 'pass1')) # False — меньше 8text = '"hello" and "world"'
# Без lookbehind/lookahead
re.findall(r'"([^"]*)"', text) # ['hello', 'world'] — с группой
# С lookbehind и lookahead
re.findall(r'(?<=")[^"]*(?=")', text) # ['hello', 'world'] — без группыtext = 'The cat sat on the cat mat'
# 'cat' не после 'the '
re.findall(r'(?<!the )cat', text, re.IGNORECASE) # ['cat'] — второй 'cat'
# 'cat' не перед ' mat'
re.findall(r'cat(?! mat)', text, re.IGNORECASE) # ['cat'] — первый 'cat'# Email не должен начинаться или заканчиваться на точку
pattern = r'^(?!\.)[\w.-]+[\w-](?<!\.)@[\w.-]+\.[a-zA-Z]{2,}$'
# (?!\.) — не начинается с точки
# (?<!\.) — не заканчивается на точку перед @
bool(re.match(pattern, 'user@example.com')) # True
bool(re.match(pattern, '.user@example.com')) # False
bool(re.match(pattern, 'user.@example.com')) # Falsetext = 'name: John, age: 30, city: Moscow'
# Значение после 'age: '
re.search(r'(?<=age: )\d+', text) # '30'
# Все значения после ': '
re.findall(r'(?<=: )[^,]+', text) # ['John', '30', 'Moscow']В стандартном re lookbehind требует фиксированной длины:
# Работает: фиксированная длина
re.search(r'(?<=\$)\d+', '$100') # '100'
# Ошибка: переменная длина
re.search(r'(?<=\${1,3})\d+', '$$$100') # error: look-behind requires fixed-width pattern
# Решение: использовать альтернативу
re.search(r'\${1,3}(\d+)', '$$$100') # '100' — с группойПримечание: В Python 3.11+ и библиотеке
regexэто ограничение снято.
# Найти число перед 'руб' или '$'
re.findall(r'\d+(?=руб|\$)', 'Цена: 100руб или $200') # ['100', '200']
# Найти слово перед '!' или '?'
re.findall(r'\w+(?=[!?])', 'Hello! How are you?') # ['Hello', 'you']Можно комбинировать несколько assertions:
# Пароль: минимум 8 символов, буква, цифра, спецсимвол
pattern = r'^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$'
# (?=.*[A-Za-z]) — есть буква
# (?=.*\d) — есть цифра
# (?=.*[@$!%*?&]) — есть спецсимвол
bool(re.match(pattern, 'Pass123!')) # True
bool(re.match(pattern, 'Password')) # False — нет цифры и спецсимволаAssertions не захватывают текст, что может запутать:
# Lookahead не захватывает 'руб'
match = re.search(r'\d+(?=руб)', '100руб')
match.group() # '100' — только число
# Для захвата используйте группу
match = re.search(r'(\d+)руб', '100руб')
match.group(1) # '100'# Ошибка в re: переменная длина
re.search(r'(?<=\${1,3})\d+', '$$$100') # error
# Решение: фиксированная длина или альтернатива
re.search(r'(?<=\$)\d+', '$$$100') # '$$100' — не то
re.search(r'\${1,3}(\d+)', '$$$100') # '100' — правильно# Ошибка: lookahead после цифр проверяет то, что после цифр
re.search(r'\d+(?=руб)', 'руб100') # None
# Правильно: lookahead должен быть после позиции проверки
re.search(r'(?<=руб)\d+', 'руб100') # '100'# Ошибка: без ^ проверка в любом месте строки
re.search(r'(?=.*\d).{8,}', 'abc12345extra') # Match — но может быть не то
# Правильно: с ^ для проверки всей строки
re.search(r'^(?=.*\d).{8,}$', 'abc12345') # Match(?=...) и (?!...) проверяет текст вперёд без захвата(?<=...) и (?<!...) проверяет текст назад без захвата=) требуют наличия шаблона, отрицательные (!) — отсутствияre lookbehind требует фиксированной длины шаблонаВопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.