Блог

Регулярные Выражения. Regex Lookaround (Проверка окружения шаблона)

Более подробно эта тема рассматривается в курсе 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

<<< Назад к началу статьи "Регулярные Выражения. Предисловие"

Читать дальше >>

Регулярные Выражения. Lazy and Greedy Search (Ленивый и жадный поиск)

Более подробно эта тема рассматривается в курсе 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

<<< Назад к началу статьи "Регулярные Выражения. Предисловие"

Читать дальше >>

Регулярные Выражения. Group (Группа или Группировка)

Более подробно эта тема рассматривается в курсе 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 допустим ТОЛЬКО дефис и больше никакой другой символ. Аналогично, если в группе совпал пробел, то пробел должен быть и в ссылке на группу. То же самое и для бэкслеша.

<<< Назад к началу статьи "Регулярные Выражения. Предисловие"

Читать дальше >>

Регулярные Выражения. Metacharacters (Метасимволы)

Более подробно эта тема рассматривается в курсе 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]

  • . (dot) - точка в регулярных выражениях означает совсем не точку, а вообще любой символ (чтобы ввести именно точку, необходимо перед самой точкой поставить символ экранирования бэклсеш, то есть сделать вот так: \.) ;
  • \d - означает любой цифровой символ (аналог [0-9]) ;
  • \w - любой символ слова, т.е. большие и маленькие буквы, цифры и знак подчёркивания [A-Za-z0-9_] ;
  • \s - любой пробельный символ (перевод строки, знак табуляции и т.д.) [ \n\r\t\f] ;
  • \D - большие буквы означают инверсию. Поэтому, если \d означает любой ЦИФРОВОЙ символ, то \D будет означать любой НЕ цифровой символ ;
  • \W - любой НЕ символ слова (т.е. все символы, кроме [A-Za-z0-9_] ;
  • \S - любой НЕ пробельный символ ;

<<< Назад к началу статьи "Регулярные Выражения. Предисловие"

Читать дальше >>

Регулярные Выражения. Anchors (Якоря)

Более подробно эта тема рассматривается в курсе 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)

  • ^ - символ caret, означает начало строки ;
  • $ - символ конца строки ;
  • \b - символ границы слова (начало или конец слова) ;
  • \B - символ инверсии границы слова (только НЕ начало или НЕ конец слова) .

<<< Назад к началу статьи "Регулярные Выражения. Предисловие"

Читать дальше >>

Регулярные выражения. Character class (Класс символов)

Более подробно эта тема рассматривается в курсе 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 последовательности по два символа. И в результате получаем итоговый паттерн, удовлетворяющий заданному условию:
[0-9]{3}-[0-9]{2}-[0-9]{2}

Кстати, забегая вперёд, следует отметить, что есть один способ записать тоже самое:
\d{3}-\d{2}-\d{2}
В этом случае, для записи последовательности чисел используется специальный символ \d (бэкслеш-ди), называемый
Metacharacter (Метасимвол) .

<<< Назад к началу статьи "Регулярные Выражения. Предисловие"

Читать дальше >>

Регулярные Выражения. Quantification (Квантификация или количественная оценка)

Более подробно эта тема рассматривается в курсе Python Fundamentals

Квантификация отвечает за повторяемость указанной группы символов. Для этой цели в регулярных выражениях задействованы три специальных символа:

Symbols Their meaning:
? 0 or 1 character
+ 1 or > characters
* 0 or > characters

Эти спецсимволы ставятся после указанного символа или группы символов и означают необходимое число повторений этого символа или группы символов:

  • ? - выбираются все совпадения, где указанная группа символов повторяется 0 или 1 число раз;
  • + - выбираются все совпадения, где указанная группа символов повторяется 1 и более число раз;
  • * - выбираются все совпадения, где указанная группа символов повторяется 0 и более число раз.

Если нужно точное количество повторений, то в этом случае используются фигурные скобки:

Symbols Their meaning:
{n} exactly n times
{min, } nmin or > times
{,max} from 0 to nmax times
{min, max} from nmin to nmax times

В этом случае:

  • {n} - выбираются все совпадения, где указанная группа символов ровно n число раз (не больше и не меньше!);
  • {min, } - выбираются все совпадения, где указанная группа символов повторяется n_min и более число раз;
  • {,max} - выбираются все совпадения, где указанная группа символов повторяется от 0 до n_max число раз;
  • {min, max} - выбираются все совпадения, где указанная группа символов повторяется от n_min до n_max число раз.

<<< Назад к началу статьи "Регулярные Выражения. Предисловие"

Читать дальше >>

Регулярные Выражения. Предисловие

Более подробно эта тема рассматривается в курсе 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 (Группа или Группировка)

Только после того, как будут получены основные представления о регулярных выражений, есть смысл перейти к рассмотрению более сложных вопросов:

Lazy and Greedy Search (Ленивый и жадный поиск)

Regex Lookaround (Проверка окружения шаблона)

Читать дальше >>

Список тэгов