2.36. Группы и якоря¶
Шаблон может не только сообщать «эта строка соответствует» — он способен разбирать совпавшие части и передавать каждую из них приложению по имени. Круглые скобки вокруг части шаблона делают её захватывающей группой; объект совпадения затем предоставляет каждую группу как отдельную подстроку.
2.36.1. Захватывающие группы¶
Заключите любую часть шаблона в (...), чтобы захватить то, чему она соответствовала:
>>> import re
>>> m = re.search(r'temp (\d+) at (\d+)s', 'temp 42 at 137s ok')
>>> m.group(0)
'temp 42 at 137s'
>>> m.group(1)
'42'
>>> m.group(2)
'137'
Группа 0 — это всегда всё совпадение целиком.
Группы 1, 2, … — это захваченные подстроки, нумеруемые слева направо по их открывающей скобке.
Вызов
match.group()с индексом, выходящим за пределы последней группы, вызываетIndexError.
Распространённый приём — «сопоставить известную структуру, захватив переменные части как целые числа»:
def parse_temp(line):
m = re.search(r'temp (\d+) at (\d+)s', line)
if not m:
return None
return int(m.group(1)), int(m.group(2))
2.36.2. Незахватывающие группы¶
Круглые скобки также группируют подвыражение, чтобы квантификатор мог применяться ко всей группе. Это единственное назначение группировки в r'(ab)+' — «один или более ab». То, что ab появляется как группа 1, — это побочный эффект.
Чтобы сгруппировать без захвата, используйте (?:...):
>>> re.search(r'(?:ab)+', 'xababy').group(0)
'abab'
Незахватывающие группы позволяют сохранять номера групп упорядоченными, когда шаблон использует группировку для структуры, но не нуждается в извлечении каждой части по отдельности.
2.36.3. Якоря¶
Якоря не сопоставляются с символом — они сопоставляются с позицией.
^— начало строки.$— конец строки.
Именно якоря заставляют re.match() и re.search() вести себя по-разному. re.match(p, s) — это то же самое, что re.search('^' + p, s): оно вынуждает шаблон начинаться с позиции 0. Добавление $ в конец шаблона затем заставляет шаблон соответствовать всей строке и ничему более:
>>> re.search(r'^\d+$', '12345')
<match num=1>
>>> re.search(r'^\d+$', '12345 ok') is None
True
^ и $ в MicroPython re всегда означают начало и конец всей строки, переданной в re.search(). Здесь нет флага re.MULTILINE, который заставил бы их совпадать на каждом встроенном переводе строки, а $ также не совпадает с позицией перед завершающим \n — это должен быть абсолютный конец входных данных. Чтобы получить построчное поведение, сначала разделите входные данные по переводам строк и запустите шаблон на каждой строке.
2.36.4. Наборы символов¶
Квадратные скобки определяют явный набор символов. Совпадение поглощает ровно один символ из набора.
[abc]— один изa,b,c.[a-z]— один символ в диапазонеa-z(включительно).[a-zA-Z0-9]— буквы или цифры. Три объединённых диапазона.[^abc]— не один изa,b,c.^отрицает, только когда он является первым символом внутри скобок.
Примеры:
>>> re.search(r'[A-F0-9]{6}', 'colour #1a2b3c rest').group(0)
'1A2B3C'
>>> re.search(r'[A-F0-9]{6}', 'colour #1a2b3c rest') is None
True
Первый вызов на практике возвращает None, потому что литеральный текст записан в нижнем регистре. В MicroPython re нет флага re.IGNORECASE — чтобы выполнять сопоставление без учёта регистра, впишите оба варианта регистра в набор:
>>> re.search(r'[A-Fa-f0-9]{6}', 'colour #1a2b3c rest').group(0)
'1a2b3c'
Сокращения для классов (\d, \s, \w и их отрицания) можно использовать и внутри [...]: [\w-] означает «символы слова или литеральный дефис».
2.36.5. Жадные и ленивые квантификаторы¶
Квантификаторы *, +, ? и {m,n} по умолчанию жадные — они сопоставляются с как можно большим количеством символов, насколько это ещё позволяет остальная часть шаблона. Часто это именно то, что нужно; иногда нет:
>>> re.search(r'<(.+)>', 'a <b> <c> d').group(1)
'b> <c'
Жадный .+ захватил всё вплоть до последнего >. Добавление ? делает квантификатор ленивым — он сопоставляется с как можно меньшим количеством:
>>> re.search(r'<(.+?)>', 'a <b> <c> d').group(1)
'b'
Ленивая форма останавливается на первом >. Ленивые квантификаторы постоянно возникают при извлечении сбалансированных разделителей из строки.
2.36.6. Обратные ссылки в подстановке¶
re.sub() может ссылаться на захваченные группы в строке замены через \1, \2, … Подстановка переписывает каждое совпадение, используя захваченные части:
>>> re.sub(r'(\d+)\.(\d+)', r'\2.\1', 'swap 12.34 and 5.6')
'swap 34.12 and 6.5'
Каждое совпадение захватывает два числа, и замена меняет их местами. \g<1> — альтернативный синтаксис для того же самого — полезен, когда следующий символ в замене является цифрой (r'\g<1>0' для добавления литерального нуля к группе 1, а не для чтения «группы 10»).
2.36.7. Что недоступно¶
Напоминание о том, что MicroPython re не поддерживает — на случай, если сюда попадёт шаблон из CPython и удивит вас:
Опережающая проверка
(?=...)и ретроспективная проверка(?<=...)— не реализованы.Именованные группы
(?P<name>...)и именованные обратные ссылки(?P=name)— не реализованы.Константы флагов вроде
re.IGNORECASE,re.MULTILINE,re.DOTALL— не учитываются. Создавайте набор без учёта регистра или предварительно разделяйте входные данные самостоятельно.Методы
match.groups(),match.span(),match.start()иmatch.end()ограничены уровнем ROM, который не включён ни на одной поставляемой плате OpenMV. Код, опирающийся на них, не будет работать на камере.
С шаблонами, группами и якорями набор инструментов регулярных выражений на камере достаточно мал, чтобы освоить его за один присест, и достаточно богат, чтобы делать всё, кроме контекстно-зависимого разбора.