2.35. Основи шаблонів

Регулярний вираз — це невелика мова для опису рядків за формою, а не за точним вмістом. Використовуйте його, коли рядок збігається тоді й лише тоді, коли він відповідає шаблону, який можна описати, але не перерахувати – «послідовність цифр, за якою йде одиниця», «рядок, що починається з ERROR і закінчується числом», «будь-яке з цих розширень файлів, у будь-якому порядку, з необов’язковими префіксами v».

Використовуйте re лише тоді, коли звичайний рядковий метод не підходить.

Кожен із них є швидшим, легшим для читання та менш схильним до помилок, ніж еквівалентний регулярний вираз. Використовуйте регулярний вираз, коли важлива форма рядка, а не точний підрядок.

2.35.1. Чотири речі, які ви будете використовувати

Модуль MicroPython re надає чотири речі:

  • re.compile() – перетворити рядок шаблону на скомпільований об’єкт шаблону для повторного використання.

  • re.match() – спробувати шаблон на початку рядка. Шаблон прив’язаний до позиції 0.

  • re.search() – спробувати шаблон будь-де в рядку. Повертає перший збіг.

  • re.sub() – знайти кожен збіг і замінити його.

Помітні відсутності порівняно з CPython: немає re.findall, немає re.finditer, немає re.split на рівні модуля (скомпільовані шаблони мають метод split натомість), немає re.fullmatch, немає констант прапорів на кшталт re.IGNORECASE. Там, де б ви використовували одне з них у CPython, побудуйте еквівалент із re.search() у циклі.

2.35.2. Перший шаблон

Шаблон r'\d+' збігається з однією або більше цифрами:

>>> import re
>>> m = re.search(r'\d+', 'sensor reading 42 ok')
>>> m.group(0)
'42'

Кілька речей, на які варто звернути увагу:

  • Шаблон написаний як необроблений рядок (r'...'), щоб зворотна коса риска в \d потрапила до re, а не була оброблена як рядкова екрануюча послідовність Python. Завжди використовуйте необроблені рядки для шаблонів регулярних виразів.

  • re.search() повертає об’єкт збігу у разі успіху та None у разі невдачі. Завжди перевіряйте перед викликом match.group().

  • m.group(0) – повний текст, із яким збігся шаблон. Групи 1, 2, … з’являться пізніше, як тільки шаблон міститиме захоплюючі дужки.

Той самий шаблон із re.match() повертає None, бо рядок не починається з цифри:

>>> re.match(r'\d+', 'sensor reading 42 ok') is None
True
>>> re.match(r'\d+', '42 readings')
<match num=1>

2.35.3. Частини шаблону

Більшість корисних шаблонів будуються з невеликого набору елементів. Ті, що працюють у MicroPython:

Буквальні символи – будь-який символ, що не є спеціальним, збігається з собою. hello збігається з hello.

Спеціальні символи. ^ $ * + ? { } [ ] \ | ( ) – усі мають значення, описані нижче. Щоб збігти один із них буквально, екрануйте його зворотною косою рискою: \. збігається з буквальною крапкою.

Класи символів – скорочення для поширених наборів символів:

  • \d – будь-яка цифра від 0 до 9

  • \D – будь-який не-цифровий символ

  • \s – будь-який пробільний символ (пробіл, табуляція, символ нового рядка)

  • \S – будь-який непробільний символ

  • \w – будь-який символ «слова»: літери, цифри, підкреслення

  • \W – будь-який несловесний символ

  • . – будь-який символ, крім символу нового рядка

Квантифікатори – скільки разів попередній елемент повинен збігатися:

  • * – нуль або більше (жадібний)

  • + – один або більше (жадібний)

  • ? – нуль або один

  • {n} – рівно n разів

  • {m,n} – від m до n (включно)

Комбінування: \d{3}-\d{4} збігається з трьома цифрами, тире, чотирма цифрами. \s+ збігається з одним або більше пробільними символами. hello.*world збігається з hello, будь-чим (включно з нічим), потім world.

Примітка

Жадібний означає, що квантифікатор споживає якомога більше вхідних даних, поки решта шаблону ще збігається. Для рядка hello x world y world .* у hello.*world збігає найдовшу послідовність, після якої ще залишається world – він захоплює x world y, а не коротше x. Те саме стосується + і форми діапазону {m,n}: рушій бере найдовший збіг, а потім відступає лише тоді, коли решта шаблону зазнає невдачі.

2.35.4. Заміна

re.sub() знаходить кожен збіг і замінює його рядком. Заміна може посилатися на захоплені групи через \1, \2, … (розглядається разом із рештою синтаксису груп пізніше). Без груп re.sub – це просто пошук і заміна за регулярним виразом:

>>> re.sub(r'\s+', ' ', 'too    many   spaces')
'too many spaces'

>>> re.sub(r'\d+', 'N', 'log 12, log 345, log 6')
'log N, log N, log N'

Третій аргумент – рядок, над яким виконується операція; результат – новий рядок, у якому кожен збіг замінено.

2.35.5. Розбиття – лише для скомпільованого шаблону

Немає re.split на рівні модуля. Щоб розбити за регулярним виразом, спочатку скомпілюйте шаблон і викличте його метод split

>>> sep = re.compile(r'\s*,\s*')
>>> sep.split('a , b,c ,  d')
['a', 'b', 'c', 'd']

Необов’язковий другий аргумент обмежує кількість розбиттів:

>>> sep.split('a, b, c, d', 2)
['a', 'b', 'c, d']

2.35.6. Компіляція для повторного використання

Якщо один і той самий шаблон запускається багато разів – усередині циклу або в часто викликаній функції – скомпілюйте його один раз і повторно використовуйте скомпільований об’єкт:

digit_run = re.compile(r'\d+')

def first_number(line):
    m = digit_run.search(line)
    return int(m.group(0)) if m else None

Виклик pattern.match() та pattern.search() на скомпільованому об’єкті – те саме, що функції рівня модуля, але уникає вартості перекомпіляції при кожному виклику.

2.35.7. Шаблони, що нічому не збігаються

Три шаблони зокрема заводять розробників у глухий кут:

  • .* збігається з порожнім рядком. re.search(r'.*', s).group(0) повертає '' для будь-якого вхідного рядка.

  • Шаблон з неекранованим спеціальним символом є синтаксичною помилкою. re.compile(r'cost: $5') викидає ValueError, бо $ означає «кінець рядка». Використовуйте r'cost: \$5'.

  • Крапка . не збігається з символом нового рядка. Щоб збігати через символи нового рядка, напишіть шаблон так, щоб явно обробляти їх за допомогою [\s\S] або обробляйте по одному рядку за раз.

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