Опережающие и ретроспективные проверки

Проверяем, что стоит до или после совпадения, не захватывая это в результат.

Lookaround — проверка контекста: совпадение засчитывается, только если рядом есть (или нет) нужный текст, но сам этот текст в результат не входит.

Зачем нужны проверки

Иногда совпадение должно зависеть от окружения. «Число, за которым идёт слово руб» — но само «руб» в результат брать не нужно. Обычные группы тут не подходят: они захватили бы лишнее. Решение — проверки нулевой ширины (lookaround): они смотрят на контекст, но не «съедают» символы.

Опережающая проверка (lookahead)

  • (?=...)позитивный lookahead: дальше ДОЛЖЕН идти такой текст.
  • (?!...)негативный lookahead: дальше НЕ должно быть такого текста.
import re

text = "100 руб, 200 usd, 300 руб"
print(re.findall(r"\d+(?= руб)", text))

Вывод:

['100', '300']

Паттерн \d+(?= руб) совпал только с числами, за которыми идёт « руб». При этом само « руб» в результат не вошло — это и есть «нулевая ширина». Число 200 (usd) отсеялось.

Ретроспективная проверка (lookbehind)

  • (?<=...)позитивный lookbehind: перед совпадением ДОЛЖЕН быть такой текст.
  • (?<!...)негативный lookbehind: перед совпадением НЕ должно быть такого текста.
import re

text = "Цена $100, скидка $20, бонус 50"
print(re.findall(r"(?<=\$)\d+", text))

Вывод:

['100', '20']

Здесь (?<=\$)\d+ ловит числа, перед которыми стоит знак $. Доллар не входит в совпадение — мы получили чистые числа 100 и 20, а 50 (без доллара) отсеялось.

Практика: разделитель тысяч

Эффектный приём — расставить пробелы по разрядам в большом числе, комбинируя lookbehind и lookahead:

import re

print(re.sub(r"\B(?=(\d{3})+(?!\d))", " ", "1234567"))

Вывод:

1 234 567

Паттерн вставляет пробел в позициях, после которых число цифр кратно трём. Это классический пример мощи проверок: мы ничего не захватываем, а лишь помечаем нужные позиции.

Ограничение lookbehind

В Python lookbehind должен быть фиксированной длины: (?<=ab) можно, а (?<=a+) — нельзя (переменная длина даст ошибку). Lookahead такого ограничения не имеет. Это особенность реализации, о которой стоит помнить.

Итог

  • Lookaround проверяет контекст, не включая его в совпадение (нулевая ширина).
  • (?=...)/(?!...) — позитивный/негативный lookahead (что идёт после).
  • (?<=...)/(?<!...) — позитивный/негативный lookbehind (что идёт до).
  • В Python lookbehind обязан быть фиксированной длины.
Проверьте себя
1. Что делает позитивный lookahead (?= руб) в паттерне \d+(?= руб)?
AЗахватывает число вместе со словом руб
BЗасчитывает число только если за ним идёт « руб», но само « руб» не включает в совпадение
CИщет слово руб перед числом
DЗаменяет число на руб
2. Чем отличается lookbehind (?<=...) от lookahead (?=...)?
AНичем
BLookbehind проверяет текст ПЕРЕД позицией, lookahead — ПОСЛЕ
CLookbehind захватывает текст, lookahead — нет
DLookbehind работает только с цифрами
3. Какое ограничение у lookbehind в Python?
AОн не поддерживается вовсе
BОн должен быть фиксированной длины (нельзя (?<=a+))
CОн работает только с латиницей
DОн захватывает текст в группу
Поддержать проект