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 потрапить сюди і вас здивує:

  • Lookahead (?=...) та lookbehind (?<=...) – не реалізовані.

  • Іменовані групи (?P<name>...) та іменовані зворотні посилання (?P=name) – не реалізовані.

  • Константи прапорів на кшталт re.IGNORECASE, re.MULTILINE, re.DOTALL – не підтримуються. Самостійно побудуйте набір без урахування регістру або попередньо розбийте вхідні дані.

  • Методи match.groups(), match.span(), match.start() та match.end() залежать від рівня ROM, який жодна з поставлених плат OpenMV не вмикає. Код, що покладається на них, не запуститься на камері.

За наявності шаблонів, груп і прив’язок набір інструментів регулярних виразів на камері є достатньо малим, щоб вивчити його за одне заняття, і достатньо потужним, щоб виконати все, крім контекстно-залежного аналізу.