Регулярные выражения


В Open Data Science лаборатории прочитал лекцию про регулярные выражения в Python.

Регулярные выражения - швейцарский нож для обработки текста. Примеры, решаемые регулярными выражениями: * проверка текста на совпадение с маской * поиск текста по шаблону (извлечение всех адресов электронной почты из текста документа) * подготовка текста (замена и удаление спецсимволов) * анонимизация (замена телефонов, номеров кредитных карт, IP-адресов и email) * токенизация (разбиение на токены) * разбор строк по шаблонам * простое извлечение фактов (построение признаков)

Слайды можно скачать тут mlsysd3ods.pdf

Текстовая расшифровка (еще не вычитана):

Ага, презентацию видно же, да? Да, да, все хорошо. Ага, сколько-то я не знаю, сколько. Я не вижу, сколько отсюда, у меня не видно отсюда. Так вот, лекция про регулярное выражение. И что, собственно, такое регулярное выражение? Регулярное выражение – это такой швейцарский нож для текста, который позволяет делать кучу всяких прикольных вещей с текстом быстро и сравнительно легко. У них есть недостатки, что регулярное выражение вы раз написали, и фактически уже отредактировать не сможете, потому что никто, включая вас, не поймет, что же там написано. Но в целом они такие вот удобные. Что можно делать регулярными выражениями? Можно проверять строки на совпадение с шаблоном, можно искать по строку в строке, какую-то предобработку делать, то есть удаление спецсимволов, нормализацию текста, анонимизацию, удалять e-mail, инн, номера карточек, токенизацию, например, токенизация в PyTorch сделана через регулярное выражение, разбор строк по шаблонам, логи всякие там и так далее. И построение признаков для моделей тоже нормально делается через регулярки извлечения признаков из текста. Регулярки везде. Вот, например, это из PyTorch. Здесь у нас в NLTK токенизатор на регулярках построен. То есть везде, практически в любом инструменте, в котором обрабатывается текст, сидят внутри регулярки. Прежде чем переходить как раз к регулярным выражениям, разберем строки. Дело в том, что в регулярных выражениях очень много используются обратные кавычки, обратный слышим. Если использовать обычные питоновские строки, в глазах будет ребить, потому что каждый слэш надо будет отслэшивать еще одним слэшем. И есть хорошая привычка, если вы пишете регулярное выражение, то сразу использовать питоновские R-строки, где обратные слэши не разворачиваются. Пример регулярных выражений из Prodo, это я сегодня скопировал, скриншот сделал, вот какие они. Видно, что слэш там через одного. Поэтому дальше мы везде используем R-строки, в которых... Чем отличается R-строка от обычной, я на примере нарисовал. Там не надо два раза писать слэш, чтобы вставить один. Сама библиотека Re, она встроена в питоны, ее не надо устанавливать специально. С разными версиями питона идут разные версии Re. В частности, вот это, которое я рассматриваю, это для 3.9. В 3.10 есть небольшие доработки, но они не принципиальные. Все, что работает в 3.10, в 3.9 тоже работает. Для того, чтобы дальше работать, надо взять какой-нибудь текст. Я взял текст со страницы анонсов лаборатории. Выглядит он примерно вот так. В тексте он так, а внешне он выглядит вот так. Видим, что у нас тут есть дата анонса, список анонсов, заголовок. И давайте найдем все даты анонсов на странице. Мы берем наш текст, берем библиотеку RegExp и ищем регулярным выражением, пока не важно, что он означает, находим все даты анонсов. Вот они. Что тут происходит у нас? Касае де – это любая цифра. Четыре – это значит, что цифра должна быть повторена четыре раза. Дефис – означает дефис. Затем две цифры, повторенные два раза. И еще раз две цифры через дефис. Библиотека Ре проходит по тексту, ищет все такие совпадения и выводит их в виде списка. Внутри она строит конечный автомат, которым идет по тексту, но это пока не важно. Как я уже говорил, касае де – это класс символов, обозначающий любую цифру. И вот тут, в принципе, разобрано, как она выглядит. Вот таким регулярным выражением мы можем вытащить все ссылки с текста. То есть все, что начинается с открывающей угловой скобки, а пробел хреф, равно кавычка. И затем в скобках текст ссылки и кавычка закрывается. Получаем вот такой список. И как это регулярное выражение устроено? В квадратных скобках у нас крышечка, и за ней кавычка. В квадратных скобках у нас указывается класс символов. Есть предопределенные классы символов, такие как касае де, которые обозначают любые цифры, но мы можем определить другой класс символов в перечисливых квадратных скобках. То есть, например, если бы в кавычках были а, б, в, г, д, то это совпадало бы с любой буквой а, б, в, или г, или д, неважно. Если же символный класс начинается с кавычки, то это отрицание символного класса. То есть, крышечка, кавычка совпадает с любым символом, кроме кавычки. Плюс означает, что таких символов у нас один или больше. Таким образом мы строим выражение, которое вытаскивает все ссылки. Вот, например, мы объявляем символный класс МПА, и он совпадает с мама, папа, а также с М в слове мыли и АМ в слове рама. Как строятся символные классы? Буква или цифра обозначает саму себя. Точка обозначает любой символ. Если, тут название флага неправильно написано, если указан флаг dot all, то точка совпадает также с переводом строки, а по умолчанию она с переводом строки не совпадает. Открывающая крышечка совпадает с началом строки, причем если установлен флаг multiline, то с началом каждой строки. Доллар совпадает с концом строки, то есть если multiline, то с каждой строки. И есть специальные символы, они все строятся примерно одинаково. Маленькая буква означает какой-нибудь класс, например, маленькая буква S это пробельные символы. Up, новая строка, пробел, большая буква S латинская означает все, что не маленькая буква S, отрицание класса. Маленькая буква B обозначает пустой символ перед началом или перед концом слова. А большая буква B означает все, что не маленькая буква B. Это достаточно странный символ, но он позволяет лучше понять, как регулярки в принципе работают. То есть в слове мама мыла раму, символ касая маленькая буква B совпадает в 6 раз, то есть перед словом мама, после слова мама, перед словом мыла, после слова мыла, перед словом рама и после слова рама. А символ B большая совпадает везде, внутри слова мама 3 раза, внутри слова мыла 3 раза и внутри слова рама 3 раза. То есть там, где не в начале и не в конце слова. Для чего такое может быть странное и нужно? Вот, например, регулярное выражение, которое вытаскивает что-нибудь, начинается с маленькой буквы М, не находится в начале слова и некоторое количество непустых пробелов. То есть оно совпадает с Ма в слове мама, со вторым Ма причем, и с Му в слове раму. Как это получается? То есть у нас большая Б совпадает между М и А, но за ним идет А, стало быть, она правила не срабатывает. Регулярное выражение переходит к следующей букве и пытается применить ее заново. Между А и М у нас есть не начало слова, потом идет М, потом идет некоторое количество непустых символов, один или больше. И вот у нас Ма совпала. После того, как она совпала, она двигает указатель дальше и находит Му совпавшую. Ма и Му. Пока, в общем, ничего сложного. Если у нас многострочный текст, то нам удобно было бы отличать начало текста, конец текста, а также начало каждой строки и конец каждой строки. И вот тут, например, RefAndAll находит 5 символов в начале текста с косой А. С точкой он находит в начале текста, если у нас не установлен флаг Multiline. А если установлен, то он находит 5 символов в начале каждой строки. То есть перед каждым переводом строки у нас опять символы из текста вытаскиваются. Напомню, что сам по себе текст у нас выглядит вот так. Идем дальше. Еще символные классы, касая W маленькое, все, что может быть внутри слова, и W большое, все, что не может быть внутри слова. Внутри слова могут быть буквы или цифры. При этом имеет значение в какой кодировке буквы или цифры. То есть различаются регулярные выражения, которые работают для Unicode. По умолчанию в третьем питоне регулярные выражения работают с Unicode строками. А также регулярные выражения, которые работают только со скишными строками, то есть под множеством латин 1. И вот мы видим, что если в флаг стоит Unicode, который стоит по умолчанию, то у нас находят регулярные выражения три слова. Мама, мыло и форт. А если стоит флаг ASCII, то у нас находится только слово форт. Потому что с точки зрения регулярных выражений в ASCII кодировке мама и мыло не содержат символов, которые могут быть внутри слова. Есть еще флаг relocale, который говорит буквально следующее, что используйте текущую локаль. То есть не Unicode, не Antiche, а текущую. Но его не надо никогда использовать. Почему? Потому что он работает вовсе не так, как вы ожидаете. Во-первых, он не работает со строками, он работает с байтовыми последовательностями. Во-вторых, он игнорирует вашу локаль, которую вы пытаетесь установить, и в принципе ничем поведение его от ASCII не отличается, кроме того, что вы работаете с байтами. В большинстве случаев relocale вам не нужен и не пригодится. Мы можем задать символный класс, перечислив все цифры, но если эти цифры идут в алфавите подряд, не в алфавите, а в таблице кодировки, то мы можем задать их диапазоном. Например, от A до Z, это любой из символов в таблице ASCII от A до Z. Конструкция AZ, маленькая AZ, большая 09, это цифра или латинская буква. Косая D маленькая, тут у меня наехала картинка почему-то на презентацию, это диапазон от 0 до 9. Косая D большая, это диапазон, крышечка от 0 до 9. Есть еще метасимволы. Метасимволы это символы, которые имеют особенный смысл в регулярных выражениях. Это точка, крышечка, доллар, звездочка, их нужно наэкранировать косой. Внутри символных классов, то есть внутри прямоугольных скобочек, нужно наэкранировать только сами прямоугольные скобочки. Доллар внутри символного класса это просто символ доллара, а в регулярном выражении за пределами символного класса, за пределами квадратных кавычек, это конец строки. Если внутри квадратных скобок у нас крышечка, но она не первая, то это просто символ. А если она первая, то это признак того, что символный класс надо инвертировать. Дефис, если нам его нужно включить в символный класс, должен включаться последним символом, потому что иначе он рассматривается как указание на диапазон. Кроме того, мы можем указывать флаги, которые управляют тем, как наше регулярное выражение обрабатывается. Мы с ними уже сталкивались. У флагов обычно есть длинное и короткое написание. Короткое, если вы хотите сэкономить несколько байтов, пальцы печатать встали. ASCII заставляет рассматривать процессор регулярных выражений в ваш текст, как будто слова в нем могли быть только из латино-дим-кодировки. .ALL при установке этого флага точка начинает совпадать с переводом строки. Флаги можно комбинировать. Это бинарные маски, мы можем их через OR складывать. Если нам нужно несколько флагов добавить, мы можем их сложить. Видно, что если мы ищем комбинацию любой символ сколько угодно раз, то в нашем тексте сайта найдется 190 групп. Потому что они будут искать только внутри каждой из строк. То есть там 190 строк, и каждая из строк будет найдена отдельно. Если мы скажем, чтобы он совпадал с переносом строки тоже, то будет найдено две строки неожиданно. Не одна, а две. Шутка тут в чем. Второй строкой, которая совпадет в этом регулярном выражении, будет конец файла. End of file. У меня нет внятного объяснения, почему это именно так. Но в общем-то это надо верить, про это надо помнить. Ниже такой простой убедительный пример. Refined all, точка, звездочка со строкой из одного единственного символа. Перевод строки совпадает два раза. Один раз собственно вся строка, второй раз end of file в конце. Другие файлы. Ignore case, самый простой флаг, это игнорирует регистр по умолчанию, а не регистрозависимый. Multiline управляет тем, как работают крышечка и доллар. Срабатывают они в каждой строке или только в начале строки текста. Local мы договорились не использовать. Flag verbose, он позволяет писать регулярное выражение, как будто это у нас такая большая взрослая программа с комментариями. И пробелы в регулярном выражении игнорируются в таком случае. Если нужно вставлять пробелы, мы должны вставлять класс касая s, например. Есть способ указывать шаблон выбора, когда совпадает одно из нескольких регулярных выражений на выбор, какой повезет. Это вертикальный pipe, может быть использован внутри групп, они проверяются слева направо и проверяются до первого совпадения. Если у вас есть несколько регулярных выражений и совпасть может несколько, например, мама мыла или мама, то совпадет то, которое стоит первым в этом списке. То есть они слева направо проверяются. Это поведение совпадает с тем, как работает OR в языках программирования до первого true. Коротко замкнутое поведение. Символные классы могут несколько раз повторяться. То есть, например, здесь у нас число повторяется 4 раза, 2 раза, цифра повторяется 4, 2, 2 раза. Символ, который указывает, сколько раз повторять число, называется квантификатор. Квантификатор звездочка означает, что у нас 0 или больше повторений, плюс 1 или больше повторений, вопросительный знак 0 или 1 повторение. Ну и можно указывать точное количество повторений или некоторый диапазон повторений в круглых строках. В оригинальных регулярных выражениях, по-моему, были только звездочки и, может быть, вопросительные знаки или только звездочки. Те регулярные выражения, которые придумали математики. Но когда программисты до них дорвались и наделали синтаксического сахара, в общем, например, плюс из звездочки, плюс это в чистом виде синтаксический сахар, получается добавлением какого-нибудь символа слева к выражению со звездочкой. Вот, например, допустим, мама, вопросительный знак совпадает с мама и мам в слове маму. Регулярные выражения по умолчанию жадные. Жадность означает, что ищется максимально возможное совпадение. Например, регулярное выражение м, точка звездочка а, совпадет в слове мама мыла раму с фразой а под строкой мама мыла ра. Жадным быть очень быстро, это позволяет быстро считать эти самые регулярные выражения, но часто приводит к неожиданным результатам. Например, ниже регулярное выражение, которое ищет тег в угловых скобках, оно просто совпадает со всем файлом сразу. Начиная от первой угловой скобки и кончая до второй, до последней. Если же мы хотим вытаскивать теги по одному, то нам нужно использовать нежадные квантификаторы. Вопросительный знак, если он поставлен после другого квантификатора, означает нежадность. Нежадные квантификаторы пытаются совпасть с как можно меньшей строкой. Например, жадный квантификатор, точка звездочка совпадает со всем файлом, а нежадный, точка звездочка, вопрос, совпадает с каждым тегом по отдельности. Регулярное выражение – это конечные автоматы, достаточно сложные сами по себе, и они идут по символу слева направо по нашему тексту, отрабатывают. Если у них не получилось отработки, они возвращаются, передвигаются на один символ и идут дальше. Это бы все ничего, но мы легко можем написать регулярное выражение, которое будет работать больше, чем будет жить наша Вселенная. Вот, например, регулярное выражение вверху. Оно работает 24 миллисекунды. Оно на строке из трех А и одного С в конце. Если мы добавим еще один символ А, оно начнет работать в два раза медленнее. Если мы добавим 7 символов, чтобы у нас было 10 символов А, оно будет работать в 32 раза медленнее. А если мы сделаем строку длиной 26 символов, это регулярное выражение будет работать почти 3 секунды. И так в два раза медленнее с каждой дополнительной А. Почему так происходит? Регулярное выражение буквально описывает следующее. Начиная сначала с строки, у нас есть некоторая последовательность букв А, одна или больше, или буква Х. Таких комбинаций у нас ноль или больше, а в конце стоит Б. И регулярное выражение, аппарат регулярных выражений, он берет первую А, смотрит, о, клево, оно совпало. Затем берет вторую А, о, клево, оно совпало. Затем берет третью А, о, клево, оно совпало. А потом натыкается на букву С и говорит, черт, оно не совпадает ни с А, ни с Б. Ну, давай вернемся назад. Он возвращается назад и пробует комбинацию чуть-чуть другим способом. То есть он второй раз проходит. И так он делает на каждом символе. И с регулярными выражениями, которые не успевают отработать за сутки, я, например, в работе сталкиваюсь постоянно. Поэтому будьте просто осторожны. Там есть статья прекрасная, в которой разбирается это явление. Презентацию я скину. И объясняется, как не наступить на эти грабли. Особенно легко на них наступить, если там есть условная нежадная квантификатор. Почему-то с нежадными квантификаторами наступается веселее. Первое регулярное выражение, в котором есть три выражения со звездочкой, оно при определенных обстоятельствах будет ребекс-убийцей. До этого мы работали с find all, который проходит по всей нашей строке, находит все совпадения регулярного выражения, возвращает их в виде массива, списка. Иногда это не очень удобно, например, когда мы работаем с большим текстом, или нам нужно что-нибудь сложное сделать, или нам нужно куда-нибудь передать наше регулярное выражение. И мы можем вместо find all вызывать find iter, который возвращает итератор по тексту. И вместо значения, которое найдет наше регулярное выражение, мы итерируемся по объектам совпадения. SRE match. И что же, собственно говоря, это у нас за match? Давайте возьмем одно такое совпадение, то есть из итератора выдернем первое значение, и посмотрим, какие у него есть методы. Из всех методов объекта совпадения нас будут интересовать больше всего span и group. Span это какие символы совпались, по какой в исходной строке это регулярное выражение совпало. Group это что за текст совпал, group 0 это все регулярное выражение, а там бывают еще группы с другими номерами, мы их потом чуть-чуть рассмотрим. И мы видим, что мы можем взять индекс спана и просто из исходного текста выдернуть, и получим как раз то, что нам регулярное выражение нашло. То есть это регулярное выражение совпало на позиции 2769 по 2779. Кроме find all есть еще search, он предназначен для поиска подстроки в строке. Предположим, что нам нужно вернуть не все, не список подстрок, а вот одну подстроку найти. Он ищет первое совпадение и возвращает тот самый match объект. И re-match он примерно как re-search, но совпадает только с началом строки. Это похоже, как если бы мы написали re-search, но поставили в начале символ обратная косая А, что оно должно сначала строки совпадать. Мы видим, что с нашим текстом re-match косая D плюс не совпало ни разу, то есть ничего не вернуло, вернуло none. Регулярное выражение несовпавшее возвращает none, это очень удобно, его можно использовать в if-ах, например. То есть if re-match, если оно что-нибудь вернуло, то оно приводится к true. Если оно ничего не вернуло, вернуло none, то оно приводится к false. Таким образом мы можем проверять текст на совпадение с регулярным выражением. Вот, re-full-match совпадает только с полным текстом. И опять же, это может работать не так, как вы привыкли, потому что регулярное выражение работает с строкой, а вы смотрите на файл, вы не всегда замечаете, например, есть там в конце перевод строки или нет. То есть с нашим текстом сайта re-full-match, открывающая угловая скобка, восклицательный знак, точка, звездочка, закрывающая угловая скобка не совпадет. Это странно, потому что это HTML, и он на самом-то деле начинается с угловой скобки и кончается угловой скобкой. Если мы отрежем у текста страйпом спецсимволы, то есть пробелы, переводы строки и тому подобное, то он совпадет, как мы и ожидали. Стало немножко понятнее, добавим перевод строки к этому регулярному выражению, и вот оно совпало полностью. То есть с переводами строки надо быть внимательным, когда вы хотите проверить полное совпадение. Вообще они имеют свойство влазить где надо и где не надо. О группах захвата. Группа захвата это что-то, что в круглых кавычках. В круглых кавычках у автомата регулярных выражений есть такие коробочки нумерованные, и как только он встречает что-нибудь в круглых кавычках, он туда складывает и по запросу отдает. Это группы захвата. Группа захвата 0 это все совпавшее регулярное выражение, а дальше первая группа это первая группа кавычек, которая попалась, вторая группа это вторая группа кавычек попалась, третья и так далее. Вот тут видно, как мы разобрали нашу дату мероприятия на год, месяц и день группами. Группы могут быть вложены, тогда они разрешаются слева направо, то есть сначала совпадет первая внутренняя скобка, первая скобка, которая открылась, потом совпадет вторая скобка, которая открылась, потом совпадет третья скобка, которая открылась, в том порядке, в каком они открывались. То, что одна группа лежит в другой, им не мешает никак. Это удобно использовать для группировки выбора, то есть, например, мама или папа пробел мыло, совпадает с мамой мыло раму, ну и на самом деле и папа мыло раму тоже совпадет. Если группа есть объявлена в регулярном выражении, но ничего не захватило, в этой группе будет лежать нон. Например, у нас объявлена группа мама или папа мыло, мама совпадет, папа не совпадет. Соответственно, первая группа заполнена, вторая группа не заполнена. Вот такие вот дела. Ей иногда нужны скобки, чтобы сгруппировать выражение, но мы не хотели бы плодить множественные группы, потому что если расставишь везде скобок, они позахватываются, потом пытаться понять, что попало. Есть незахватывающие скобки. Незахватывающие скобки это вопросительный знак, двоеточие, и после этого регулярное выражение. Оно будет сгруппировано, но не будет сложено в эту коробочку аппарата регулярных выражений. Иногда в длинных регулярных выражениях хочется взять и выкусить кусок. Можно, конечно, скопировать его, куда-нибудь вставить, чтобы не забыть, где он стоял, но есть специальный синтексис, который позволяет часть регулярного выражения закомментировать. Это в скобках вопросительный знак и решетка. Аппарат регулярных выражений игнорирует тот кусок, который закомментирован. Мы можем скомпилировать наше регулярное выражение для того, чтобы оно быстрее работало, но обычно для скорости это не используют. Используют это для удобства. У нас есть какая-нибудь функция, которая принимает на вход регулярное выражение скомпилированное. Мы его скомпилировали, туда отправили, его там вызывают. В принципе, компиляция регулярного выражения занимает некоторое время, но машина регулярных выражений кэширует их. Современный Python кэширует 512 последних вычисленных регулярных выражений и 512 хватает почти всем. В результате компиляции вы получаете объект, компилированный шаблон, которым вы дальше можете пользоваться точно так же, как и не скомпилированным регулярным выражением, только быстрее. Вызывать у него метод search, вызывать метод findAll. То есть скомпилированное регулярное выражение не прибиндено к методу, это именно регулярное выражение скомпилированное вы можете с разными методами потом вызывать. Ну и его методы этого объекта скомпилированное регулярное выражение, в общем те же самые, то есть find, findIter, match и так далее. Специальный метод pattern позволяет получить исходники регулярного выражения, то есть из чего оно было скомпилировано. Если мы пишем какой-то сложный разборщик, иногда у нас получается засорение кэша, типа мы зашли в какую-нибудь ветку нашей программы и много-много регулярных выражений у нас там пролетело, мы весь кэш вытеснили и через это оно будет тупить. И мы можем явно очищать кэш регулярных выражений, если мы точно знаем, что те, которые мы туда запихали, больше нам не понадобятся. Зачем это может быть нужно? Как только регулярное выражение скомпилировали мы их или просто вызвали в коде, они попадают в кэш. Как только кэш заполняется, как только мы отработали 512 регулярных выражений, кэш сбрасывается весь, то есть не замещается, не сдвигается, а именно очищается и снова у нас пустой кэш. Это может быть неожиданно и неудобно. Регулярное выражение можно использовать для поиска и замены, например, для анонимизации текста или для чистки датасетов, то есть для удаления спецсимволов, часто применяют. Например, пример, где мы телефоны заменяем на плейсхолдер, например, для обучения языковой модели. В ReaSub мы можем передать строку, на которую заменять, а еще мы в ReaSub можем передать функцию. И тогда регулярное выражение, каждый раз, когда они найдут совпадение с нашим регулярным выражением, они вызовут нашу функцию и спросят ее, на что же, собственно говоря, заменять. То есть здесь мы, если телефон начинался с семерки, заменяем его на плейсхолдер фон 7, если с восьмерки, на фон 8. Функция ReaSplit позволяет токенизировать или резать текст на кусочки. Например, в данном случае мы режем текст по пробелам, знакам припинания и переводам строки. ReaEscape это такая страховочная функция, если вы принимаете какой-то вывод и ожидаете, что он может попасть в регулярное выражение и хотите помешать злоумышленнику, устроить у вас какое-нибудь безобразие, вы можете вызывать ReaEscape, он гарантированно эскейпит все символы, имеющие специальное значение для регулярных выражений. Мутный момент, что такое все символы, имеющие значение для регулярных выражений, это надо в документацию смотреть, потому что в каждой версии питона это разный набор символов. Обычно группа нумерованная, то есть первая совпавшая группа это группа номер 1, вторая совпавшая группа номер 2, третья номер 3. И когда вы пишете большие тяжелые регулярные выражения, во-первых, может быть неудобно, группа, которую вы хотели бы использовать первой, например, она у вас в регулярном выражении стоит третьей. А во-вторых, его неудобно поддерживать, то есть вы что-нибудь дописали, поставили еще скобки, вам надо везде переправлять индексы, что было первой группой, что будет второй и так далее. В питоне есть возможность задавать именованные группы. Именованная группа это группа, помеченная вопросительным знаком П и в угловых скобках имя группы. Вот здесь мы объявляем три именованные группы год, месяц, день и вытаскиваем эти группы. К ним можно также обращаться по порядку, но можно обращаться к ним по имени. Можно дернуть групп дикт и нам вернется словарь с этими группами. В регулярном выражении можно ссылаться на совпавшие ранее группы, например, по открытому тегу находить соответствующий ему закрытый. Можно ссылаться по номеру совпавшей группы или по ее имени. Например, здесь это регулярное выражение ищет первую пару тегов, которые у нас есть на сайте. Первая пара тегов это title. Причем мы можем как ссылаться на них, обратное касая один, то есть первая совпавшая группа так и мы можем эту группу именованную сделать группа тег и тогда группа тег совпавшая. Мы можем писать регулярное выражение, которое будет совпадать с некоторым текстом, за которым идет что-то, то есть условия, что мы совпадаем с текстом при условии, что за этим текстом идет некоторый другой текст, но с тем текстом, после него мы не совпадаем. Это удобно в некоторых случаях. Вот, например, мама мыла раму, мы говорим, мы совпадаем со словом мама при условии, что после него же идет слово мыло, но с именем мамы мы не совпадаем. Look-ahead может быть также негативным, то есть мы совпадаем со всеми словами, за которыми не идет что-то. Допустим, вот, мама мыла раму, девочка мыла песню, мама или девочка, что-нибудь, что не мыло. Есть look-behind, то есть точно так же, как предпросмотр, мы смотрим перед регулярным выражением, проверяем, чтобы совпало, можем смотреть, чтобы совпало за дорегулярного выражения. Какая тут есть проблема, вот если мы посмотрим, как я тут сделал совпадение с числом несколько классов цифры. Хорошо было бы здесь поставить косая D+, например, то есть несколько цифр подряд идущих, но look-behind так не работает, потому что это слишком сложно для конечного автомата и он нам запрещает это делать. Достаточно сложная вещь, последняя на самом деле сложная вещь в регулярных выражениях, которую я сегодня хотел обсудить, это паттерн выбора. Паттерн выбора это сложная конструкция при условии, что у нас ранее в регулярном выражении совпала именованная или нумерованная группа, мы ищем одно регулярное выражение, а если оно не совпало, мы ищем другое. Есть пример регулярного выражения, которое совпадает с e-mail в угловых скобках или с e-mail без угловых скобок, но не совпадет с e-mail, у которого одна угловая скобка. То есть сначала мы пытаемся совпасть с первой группой, которая содержит угловую скобку, и если она совпала, это первая группа, то мы ищем в конце закрывающую угловую скобку, а если она не совпала, мы в конце ищем конец строки. Читать их трудно, писать трудно, регулярные выражения такие. У регулярных выражений есть ряд важных ограничений. Во-первых, стандартная библиотека RE ставит лог на весь процесс. То есть вы запускаете регулярное выражение в thread, она блокирует весь процесс у вас. Затем thread вы не убьете, если в нем выполняется регулярное выражение, у вас GIL заблокировано, пока регулярное выражение работает. Если вы наткнулись на тайм-бомбу, то вам кранты. То есть действительно, средствами питона вы ничего с этим не сделаете. Есть библиотека REGEX, частично разработанная как раз, чтобы решить эту проблему. Там она не блокирует процесс. Потом в библиотеке RE не работает произвольная обложенность. То есть, например, написать регулярное выражение, которое проверяло баланс скобок, что у вас открывающих скобок столько же, сколько и закрывающих, на регулярных выражениях написать невозможно. Можно написать для двух скобок, для трех скобок, для четырех скобок, но в общем случае написать нельзя. И это просто ограничение конечных автоматов. В некоторых диалектах регулярных выражений это сделать можно, но в RE нельзя. Еще в регулярках вы очень легко натыкаетесь на экспоненциальную сложность, это я показывал, и очень трудно поддерживать, об этом легко догадаться, глядя в эту регулярку. Предположим, что вы ее написали, а потом кто-нибудь приходит, особенно если он несколько моложе и менее опытный, что тут написано, что они курили эти ребята. Причем, на самом деле, как только в проекте заводится одно регулярное выражение, очень скоро у вас их там тысячи, поэтому вы впускаете этого демона внутрь, и он начинает воротить все, что угодно. У нас, говорит, была одна проблема обработка текстов, мы решили решить регулярные выражения, то есть две проблемы, обработка текстов и регулярные выражения. Из полезного, документация по RE у Питона очень хорошо написана, библиотека REGEX, которая в теории совместима с библиотекой RE стандартной питоновской, она документирована очень скудно, но зато она крутая. Есть хороший видеоучебник, в котором рассказано все, что я не рассказал про регулярные выражения. Есть прекрасная книга Фридла, фактически библия регулярных выражений. Регулярные выражения ведь есть не только в Питоне, не столько. В Питоне PIRL-совместимые регулярные выражения, то есть настоящие пацанские регулярные выражения, они в PIRL-е, и там есть вещи, которые в Питоне мы и не мечтали, к счастью. Еще регулярные выражения работают в базах данных, например в MongoDB, с которыми мы много работаем, в MySQL. Хороший учебник по регуляркам в JavaScript, тут тоже по ссылке есть. Ну, наверное, и все. Есть ли какие-нибудь вопросы? Кто-нибудь не уснул? Про скорость, хочется узнать, как быстрее. Самый быстрый способ работать с регулярными выражениями, это не использовать регулярные выражения. Что это в этом смысле? Если вам нужно проверить, что текст начинается с какой-то строки буквально, используйте StartSweep. Если вам не нужен сложный конечный автомат, используйте StrReplace. Если вам нужна простая замена по шаблону, используйте языки шаблонизации. Если вам нужно писать сложные регулярные выражения, в которых вы хотите использовать полный фарш и не хотите, чтобы они у вас блокировали GIL, используйте библиотеку Regex. Это почти без вариантов. Мы, кстати, Regex использовали. Там есть такие вещи, например, как операции над множественными символами. Допустим, вы определили один символный класс и определили другой символный класс как вычитание из этого символного класса. Классы символные мы можем вычитать, складывать. Там есть предопределенный символный класс, например, все знаки пунктуации, и так далее. То есть весь этот фарш, который вы в реакторе гордите руками, он там есть готовый. Из недостатков это протекающая абстракция. То есть автор говорит, оно у меня полностью совместимо. На самом деле все надо проверять, будет оно полностью совместимо или нет. Еще это у нас не детерминированные конечные автоматы. Библиотеки, в которых реализованы детерминированные конечные автоматы, а также есть ревнивые регулярные выражения. Ревнивые регулярные выражения это когда оно совпадает, вот как только оно могло начать совпасть, оно с этого места и совпадает. Это делает его катастрофически быстрым, потому что там нет вот этого отката в принципе. Но в Питон-Ри оно не поддерживается, оно делается через дополнительный плючик, если память не изменяет, за квантификатором. Плюс, или звездочка плюс, это у нас такие ревнивые звездочки. Он совпадает с того места, где только мог начать совпасть. И есть библиотеки регулярных выражений, которые в принципе не являются детерминированными конечными автоматами, они не поддерживают откат. И они тоже фантастически быстрые. То есть в целом можно выглядеть Акурия как более современная? Да, не более современная. Регекс более современная, можно сказать. Там просто лог. На Ри просто так много всего завязано в мире Питона. Ри это то же самое для Питона, наверное, что для Data Science и NumPy. Интересно. Во-первых, обратная совместимость, во-вторых, мы же можем не иметь этой библиотеки регексов. Возможно, они не хотели от нее зависеть. Опять же, регулярные выражения, если мы смотрим, какие там стоят, они такие, скупая мужская и женская слеза. Но их, правда, тут много. Гигантский массив, вместо того, чтобы собрать их через ОР, они их сложили в массив и, честно, в цикле проверяли. Быстрее? Да, что так быстрее, быстрее было бы через ОР. Я думаю, что все-таки в Пайторче индусские корни, учитывая, что и сам автор индус, ему даже, наверное, это не обидно. Просто он местами плохо написан, местами прекрасно, местами плохо. И работает, и пусть работает. Регулярные очень быстрые, на самом-то деле. Когда я говорю, что они могут зависать, это пока вы 100-мегабайтные файлы текстовые не разбираете, и пока вы не пишете такие вот тайм-бомбы, у вас все будет хорошо. Яркий парсер, есть такая штука, она, к примеру, используется для парсинга. Она на Ре написана? Нет. Дело в том, что у нее своя реализация автомата, она не на Ре написана. Она быстрее, не знаете? Она достаточно быстрая, в принципе, Ярги парсер немножко по-другому работает, сам по себе. Ну, Наташа на них сделала, Наташа быстрая. Она удобно работает тем, что вы там эти газетиры делаете, предопределенные списки, словные классы. Чего тут просто не сделать. Ну и заклинить его сложнее Яркий парсер, он просто немножко другой. Это вот для тех, кто не знает, это хороший способ извлекать адреса бесплатно, без додаты. На самом-то деле, для разбора текста, именно если вам нужно разбирать не какой попал текст в какие-нибудь логи, а человеческий язык, Наташа, построенная вокруг Ярги парсера, это, наверное, самое первое, что надо смотреть. То есть, если вам нужно разбирать текст, в котором упоминаются люди, фамилии, имена, килограммы, ну, в общем, адреса, берите Наташу и не мучайтесь. Вот, наверное, так. Есть еще какие-то вопросы? Все и так все знают регулярное выражение. Я скину презентацию в чат. Вот, ну и, наверное, тогда, если вопросов больше нет, тогда, наверное, и все. Спасибо. Запись я тоже выгружу.