Более подробно эта тема рассматривается в курсе Python Fundamentals
В теме анкоров (или якорей) была рассмотрена возможность использования паттерна (шаблона) относительно начала или конца строки, или начала или конца слова.
С помощью regex lookaround, тоже самое можно сделать в любой части строки или слова. Причём в конечном итоге, и что ОЧЕНЬ важно, символы на которые ссылается lookaround НЕ БУДУТ добавлены в итоговый результат.
Анализ символов может быть как непосредственно перед шаблоном - метод lookahead(смотреть вперёд), так и за шаблоном lookbehind (посмотреть назад).
Кстати, в русскоязычной печати и блогосфере lookahead иногда называется “опережающая проверка” и lookbehind - “ретроспективная проверка”. Однако пользоваться аутентичными английскими терминами lookahead и lookbehind в данном случае будет более логично, и, что самое главное, более понятно.
Итак в чём же здесь суть? Попробуем разобрать это на примере.
Задача:
Допустим, нам необходимо найти имена, которые начинаются с _ и выбрать эти имена, отбросив нижнее подчёркивание.
Пример:
_id name list _name
На первый взгляд, самым простым решением будет: _\w+
Но ведь в задаче сказано, что имена нужны без подчёркиваний!
Другим относительно приемлемым решением будет выделить имя после нижнего подчёркивания в группу: _(\w+) И в Match information мы видим, что хотя и match не совпало с нужным нам результатом, но появились дополнительные группы с правильным ответом.
Однако, для того, чтобы получить правильный ответ в match, нам надо использовать lookaround. И, в частности, в этом конкретном случае - lookbehind (взгляд назад, поскольку поисковая машина проверяет символы слева-направо, поэтому переход в начало слова будет с точки зрения поискового алгоритма возвратом назад).
Для этого условие начала нижнего подчёркивания мы перенесём в конструкцию lookbehind:
(?<=_)\w+
Как видим, теперь нижнее подчёркивание вообще пропало из выделения, но зато в match появилось именно то, что нам нужно!
Это был пример positive lookbehind, когда наличие указанного элемента (или группы элементов) - истина.
Предположим, что условия изменились и теперь нам нужны слова, которые НЕ начинаются с нижнего подчёркивания.
В этом случае знак = мы меняем на !:
(?<!_)\w+
Как видим, это не дало ожидаемого результата: все слова выделились и стали валидными. Это произошло из-за того, что знак нижнего подчёркивания входит в \w, поэтому, для выполнения условия, выделение сместилось на знак влево. Изменим \w на [a-z]:
(?<!_)[a-z]+
Теперь выделение сместилось на символ вправо!
Чтобы исправить ситуацию добавим знак границы слова \b:
(?<:!_)\b[a-z]+
Теперь, благодаря negative lookbehind, всё работает как надо!
Аналогичную проверку можно выполнить для знака нижнего подчёркивания в КОНЦЕ слова. Для этого сначала изменим исходный текст:
Изменённый пример к предыдущей задаче:
id_ name list name_
Сначала попробуем выделить все слова: [a-z]+
Далее, с помощью positive lookahead (позитивного взгляда вперёд или более благозвучной, но менее понятной “позитивной упреждающей проверки”) оставим только те, что заканчиваются на нижнее подчёркивание:
[a-z]+(?=_)
Теперь, c помощью negative lookahead (негативный взгляд вперёд), попробуем наоборот, оставить только слова БЕЗ нижнего подчёркивания. И для этого знак (=) поменяем на (!).
Как видим, это помогло лишь отчасти: слова с подчёркиванием тоже пока выделены, правда, в них теперь выделен на один символ меньше. Чтобы их совсем убрать, жёстко ограничим конец слов границей \b:
[a-z]+\b(?!_)
Ура, получилось!
И ещё раз выведем выведем вместе все четыре рассмотренных варианта lookaround:
Pattern | Type of Lookaround |
---|---|
(?<=_)\w+ | positive lookbehind |
(?<!_)\w+ | negative lookbehind |
[a-z]+(?=_) | positive lookahead |
[a-z]+(?!_) | negative lookahead |
Более подробно эта тема рассматривается в курсе Python Fundamentals
Если мы что-то ищем в строке с помощью регулярного выражения, то по-умолчанию производится широкий или “жадный” (greedy) поиск.
Рассмотрим пример. Допустим, мы хотим получить в тексте слова, ограниченные двойными кавычками:
Without "economic freedom", there can be no other "freedom".
Margaret Thatcher.
По идее, нам нужен паттерн, который начинается с первой кавычки, далее показывает любой символ (точка) и далее идёт квантификатор (плюс), который вмещает в себя один или более символов. И заканчивается всё это снова кавычками:
r ='\".+\"'
Однако, на деле оказывается, что это регулярное выражение выделяет значительно больше, чем предполагалось:
"economic freedom", there can be no other "freedom"
Почему? Потому что так работает алгоритм поиска совпадения в регулярном выражении.
Если не указано иное (то есть по умолчанию) алгоритм поиска работает от первого совпадения до конца текста. А затем, идёт посимвольно назад, до выполнения первого совпадения по условию поиска.
Поэтому, если мы хотим выделить два варианта, то нам нужен ленивый (lazy) поиск, где варианты перебираются до первого совпадения:
"economic freedom", "freedom"
Для этого после квантификатора + необходимо поставить знак вопроса ?:
r ='\".+?\"'
Это относится не только к знаку “плюс”, но и к другим квантификаторам:
Symbols | Their meaning: |
---|---|
+ | 1 or > characters |
* | 0 or > characters |
{min, } | nmin or > times |
{,max} | from 0 to nmax times |
{min, max} | from nmin to nmax times |
Более подробно эта тема рассматривается в курсе Python Fundamentals
Часть паттерна, окружённая круглыми скобками считается Группой.
Рассмотрим пример. Из текста необходимо извлечь телефонные номера. Важное ограничение: валидными считаются номер в формате 000 00 00 c блоками цифр, разделёнными одновременно либо двумя пробелами, либо двумя дефисами, либо двумя бэкслешами. Смешанное разделение в любой комбинации считается невалидным. Пример текста:
111 22 33-aad 232-15-11 ff 15 15 555-22-78 223/99-37 223/25/987 123-35 26 258-85-45
Исходя из того, что мы уже знаем, единственным вариантом решением будет объединение всех трёх вариантов с помощью ИЛИ (|):
\d{3}-\d{2}-\d{2}|\d{3} \d{2} \d{2}|\d{3}\/\d{2}\/\d{2}
Однако, если мы выделим разделители в отдельную группу (-| |\/) в начале шаблона, то в случае, если эта группа встречается в шаблоне несколько раз, мы можем не вставлять в это место всю группу целиком, а всего лишь сослаться на неё с помощью бэкслеш+порядковый номер группы (в нашем случае это будет \1, поскольку в шаблоне присутствует только одна группа):
\d{3}(-| |\/)\d{2}\1\d{2}
Важно ещё раз отметить, что ссылка на группу \1 означает, допустимость только того символа, что встретился в группе. Иными словами, если в самой группе совпал дефис, то в ссылке на группу \1 допустим ТОЛЬКО дефис и больше никакой другой символ. Аналогично, если в группе совпал пробел, то пробел должен быть и в ссылке на группу. То же самое и для бэкслеша.
Более подробно эта тема рассматривается в курсе Python Fundamentals
В предыдущих примерах мы уже познакомились с несколькими метасимволами (то есть символами, объединяющими определённую группу символов). Теперь самое время свести их в одну таблицу и заодно рассказать о тех, которые нам ещё не встречались.
Symbols | Their meaning: |
---|---|
. (dot) | Any character except \n |
\d | Any digit |
\w | Any word character: [A-Za-z0-9_] |
\s | Any whitespace characters: [ \n\r\t\f] |
\D | Any NON-digit |
\W | Any NON-word character: [A-Za-z0-9_] |
\S | Any NON-whitespace characters: [ \n\r\t\f] |
Более подробно эта тема рассматривается в курсе Python Fundamentals
Бывают ситуации, когда нам надо не просто найти совпадение символов в тексте, но также быть уверенными в том, что эта найденная комбинация символов выполняет определённые условия относительно другой части текста. Например, при валидации пароля, важно не просто получить совпадение символов пароля My_Pa$$worD, но также быть уверенными в том, что пароль полностью занимает всю строку от начала и до конца.
Например, все эти три строки содержат комбинацию символов, удовлетворяющих паттерну My_Pa$$worD:
My_Pa$$worD___
My_Pa$$worD
aaa_asdf_ My_Pa$$worDllfff
Однако единственно правильным вариантом будет только пароль во второй строке.
Для выполнение этого условия в начало и конец паттерна пароля добавить символы (якоря) начала ^ и конца строки $: ^My_Pa\$\$worD$
Таблица анкоров: <\p>
Symbols | Their meaning: |
---|---|
^ | Start of line |
$ | End of line |
\b | Boundary of word (start or end of word) |
\B | Inverse of \b (non-start or non-end of word) |
Более подробно эта тема рассматривается в курсе Python Fundamentals
Очевидно, что все показанные ранее примеры поиска точного совпадения слов или символов на практике случаются крайне редко (имеются в виду совпадения, показанные в предыдущем разделе Quantification (Квантификация или количественная оценка)).
Значительно чаще требуется более сложный поиск, например поиск текста, удовлетворяющего определённым критериям. Например, поиск телефонных номеров в формате 000-00-00.
Очевидно, что в начале нашего паттерна, соответствующего поиску указанного формата телефонного номера, потребуется указать комбинацию чисел от 0 до 9, повторяющуюся ровно три раза. Ни больше и не меньше.
В том случае, когда на месте указанного символа может быть любой символ из выбранного диапазона, удобнее всего использовать Character class (Класс символов).
Например, если нам нужна любая буква из набора a, b, c или d, то класс символов мы запишем как [abcd]. И поскольку указанные символы расположены в таблице кодировки по соседству, то запись [abcd] можно сократить до вида [a-d].
Пример класса символов [0-9] - означает, что нам подойдёт любой символ от 0 до 9. И если мы теперь добавим {3}, то это будет означать, что нам нужна группа из трёх чисел подряд: [0-9]{3}.
Далее добавляем дефис и ещё 2 последовательности по два символа. И в результате получаем итоговый паттерн, удовлетворяющий заданному условию:
Кстати, забегая вперёд, следует отметить, что есть один способ записать тоже самое:
\d{3}-\d{2}-\d{2}
В этом случае, для записи последовательности чисел используется специальный символ \d (бэкслеш-ди), называемый Metacharacter (Метасимвол) .
Более подробно эта тема рассматривается в курсе Python Fundamentals
Квантификация отвечает за повторяемость указанной группы символов. Для этой цели в регулярных выражениях задействованы три специальных символа:
Symbols | Their meaning: |
---|---|
? | 0 or 1 character |
+ | 1 or > characters |
* | 0 or > characters |
Эти спецсимволы ставятся после указанного символа или группы символов и означают необходимое число повторений этого символа или группы символов:
Если нужно точное количество повторений, то в этом случае используются фигурные скобки:
Symbols | Their meaning: |
---|---|
{n} | exactly n times |
{min, } | nmin or > times |
{,max} | from 0 to nmax times |
{min, max} | from nmin to nmax times |
В этом случае:
Более подробно эта тема рассматривается в курсе Python Fundamentals
В языке Python регулярные выражения значительно менее популярны, чем в языке JavaScript. Есть достаточно большая группа программистов, которые сознательно избегают их использования. Видимо поэтому, только в самой первой версии Django (одного из самых популярных фреймворков Python) для выделения частей url использовались регулярные выражения. Во второй и последующих версиях Django регулярные выражения больше не используются и эта задача решается стандартными средствами языка Python.
Регулярное выражение - это последовательность символов, определяющая шаблон поиска.
Главный минус регулярных выражений - это сложный и не всегда логичный макроязык. Он включает в себя много деталей, которые, в случае нечастого применения на практике, очень быстро забываются. К тому же, само регулярное выражение крайне сложно в отладке.
Главный плюс: возможность получить компактное и изящное решение в тех случаях, когда любой иной вариант решения будет громоздким, сложным и весьма затратным по времени.
Соответственно, как уже говорилось выше, и сторонников, и противников использования регулярных выражение в Python-коде достаточно много. Поскольку и те, и другие имеют немало аргументов в свою пользу.
Прежде всего для работы с регулярными выражениями нужен инструмент.
Удобный сайт для работы с регулярными выражениями: https://regex101.com/
В простейшем случае, мы может указать точный текст, который должен быть выделен, например is, что означает "надо показать все is в тексте". Либо указать несколько интересующих нас вариантов, разделённых вертикальной чертой: is|the|and, что означает "надо показывать все is, the и and в тексте".
Однако, если же нас интересует более сложный поиск, то прежде всего нам следует познакомиться с основами правилами создания шаблонов регулярных выражений.
Quantification (Квантификация или количественная оценка)
Только после того, как будут получены основные представления о регулярных выражений, есть смысл перейти к рассмотрению более сложных вопросов:
Character class (Класс символов)
Anchors (Якоря)
Metacharacters (Метасимволы)
Регулярные Выражения. Group (Группа или Группировка)
Regex Lookaround (Проверка окружения шаблона)