## ++Тема 9++<br>Регулярные выражения (c) Яценко Р.Н., 2019-2021 [Учебный центр компьютерных технологий "Кит"](http://kit.kh.ua/) <img src="https://i.imgur.com/Kh901c1.png" style="width: 150px; position: fixed; top: 10px; right: 10px; border: 0; box-shadow: none;"> --- ## 1. Введение в регулярные выражения (re) ---- ### Назначение - Проще говоря, регулярное выражение используется для поиска паттернов в указанной строке. Паттерном может быть все что угодно - Можно создавать паттерны соответствия электронной почте или мобильному номеру. Можно создать паттерны, которые ищут слова в строке, начинающиеся на “a” и заканчивающиеся на “z” ---- ### Где используются 1. Проверка соответствия фрагментов текста некоторым критериям. Например, наличие символа обозначения валюты и последующих за ним цифр, проверка адреса электронной почты и т.д. 2. Поиск подстрок, которые в том числе могут иметь несколько форм. Например, поиск `'pet.png'`, `'pet.jpg'`, `'pet.jpeg'` или `'pet.svg'`, но не обнаруживались подстроки `'carpet.png'` и подобные ей ---- 3. Замена всего, что совпадает с шаблоном, на указанную строку. Например, поиск подстроки `'устройство передвижения, движимое мускульной силой'` и замена подстрокой `'велосипед'` 4. Разбиение строки по точкам совпадения с шаблоном. Например, разбиение строки по `':'` или `'='` ---- ### Пример кода ```python= import re pattern = r"times" string = "It was the best of times, it was the worst of times." print(len(re.findall(pattern,string))) # 2 ``` ---- ### Примеры выражений Для написания и отладки регулярных выражений удобно использовать онлайн-сервисы, например https://regex101.com, которые в том числе содержат библиотеку готовых выражений и могут генерировать код для различных языков программирования https://regex101.com/r/HDScOu/1 --- ## 2. Язык регулярных выражений ---- ### Определение [Регулярные выражения](https://ru.wikipedia.org/wiki/%D0%A0%D0%B5%D0%B3%D1%83%D0%BB%D1%8F%D1%80%D0%BD%D1%8B%D0%B5_%D0%B2%D1%8B%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F) (_англ._ Regular Expressions) : это компактная форма записи (шаблон, маска или паттерн) представления о коллекции строк. Гибкость и мощь регулярных выражений обусловлена тем, что единственное регулярное выражение может представлять неограниченное число строк, подходящий под заданный шаблон ---- ### Строка-шаблон 1. Флаги 2. Символы и классы символов 3. Квантификаторы 4. Группировка и выбор 5. Проверка границ (привязки) ---- ### Флаги Флаги не входят непосредственно в регулярное выражение, однако расширяют его функции: * `'g'` -- глобальный поиск (обрабатываются все совпадения с шаблоном поиска) * `'i'` -- регистр букв не имеет значения (по умолчанию любой поиск регистрозависим) * `'m'` -- многострочный поиск * и др. Флаг указывается после паттерна, например: `'/[0-9]/m'` ---- ### Поиск символов и строк Одним из наиболее простых случаев является поиск отдельных символов и строк ![](https://i.imgur.com/vJCGdXM.png) https://regex101.com/r/Y67E75/1 ---- ### Экранирование специальных символов Специальные символы ([символы-джокеры](https://ru.wikipedia.org/wiki/%D0%A1%D0%B8%D0%BC%D0%B2%D0%BE%D0%BB%D1%8B-%D0%B4%D0%B6%D0%BE%D0%BA%D0%B5%D1%80%D1%8B) должны экранироваться символом обратного слеша `'\'`. К ним относятся: ``` \ . ~ $ ? + * { } [ ] ( ) | ``` В пределах регулярных выражений можно также использовать большинство стандартных экранированных последовательностей языка Python, например, `'\n'`, `'\t'` и др. ---- ### Поиск множества символов Для поиска не конкретной последовательности символов, а некоторого их множества предназначены классы символов [группа_символов] : Соответствует любому одиночному символу, входящему в группа_символов. По умолчанию при сопоставлении учитывается регистр ---- *Пример:* - выражение `'[аеёиоуэюя]'` позволяет найти все гласные буквы в строке - выражение `'п[ое]л'` даст совпадение для слов `'пел'` и `'пол'`, но не найдет слова `'поел'` или `'пил'` ---- [^группа_символов] : Соответствует любому одиночному символу, НЕ входящему в `группа_символов`. По умолчанию при сопоставлении учитывается регистр. *Пример:* * выражение `'[^аеёиоуэюя]'` обнаружит все символы в тексте, кроме гласных букв ---- [первый-последний] : Диапазон символов: соответствует одному символу в диапазоне от `первый` до `последний` *Пример:* * выражение `'[0-9]'` найдет все цифры в тексте ---- `.` (точка) : Специальный знак `'.'` соответствует какому-либо одному знаку, кроме `'\n'`. Для поиска точки необходимо использовать экранирование: `'\.'` *Пример:* * выражение `'м.л'` найдет в тексте `'мел'`, `'мул'` и т.д. ---- `\w` : Соответствует любому алфавитно-цифровому знаку *Пример:* * выражение `'\w1'` найдет в тексте `'11'` или `'я1'` `\W` : Соответствует любому символу, НЕ являющимся алфавитно-цифровым знаком ---- `\s` : Соответствует любому пробельному символу (пробел, табуляция и др.) `\S` : Соответствует любому знаку, НЕ являющемуся пробельным ---- `\d` : Соответствует любой десятичной цифре *Пример:* * выражение `'\d-й'` найдет в тексте `'1-й'` или `'2-й'` `\D` : Соответствует любому символу, НЕ являющемуся десятичной цифрой ---- ### Квантификаторы Зачастую требуется не только найти слово по символам или группе символов, но и указать количество возможных повторений, для чего в регулярных выражениях используются квантификаторы Квантификаторы записываются после символа/строки/множества ---- ### Виды квантификаторов Квантификатор|Описание -|- `'*'`|Ноль или более совпадений `'+'`|Одно или больше совпадений `'?'`|Ноль или одно совпадение `'{m}'`|Совпадение ровно `m` раз `'{m,}'`|Совпадение `m` и более раз `'{m, n}'`|Совпадение от `m` до `n` раз ---- ### Жадность и лень Каждый квантификатор может быть: * жадный (_англ._ greedy): находит как можно больше подходящих символов * ленивый (_англ._ lazy): находит как можно меньше подходящих символов По умолчанию, все квантификаторы являются жадными; для включения «ленивого» режима необходимо поставить знак `'?'` после квантификатора ---- ### Разница между жадным и ленивым режимом https://regex101.com/r/grLIWd/1 https://regex101.com/r/grLIWd/2 ---- ### Задания https://regex101.com/r/aGn8QC/2 1. Найдите все натуральные числа (возможно, окружённые буквами) 2. Найдите все «слова», написанные капсом (то есть строго заглавными), возможно внутри настоящих слов (ааа<u><font color="#007d5b">БББ</font></u>ввв) 3. Найдите слова, в которых есть русская буква, а когда-нибудь за ней цифра 4. Найдите все слова, начинающиеся с русской или латинской большой буквы (`\b` — граница слова) ---- 5. Найдите слова, которые начинаются на гласную (`\b` — граница слова) 6. Найдите все натуральные числа, не находящиеся внутри или на границе слова 7. Найдите строчки, в которых есть символ `*` (`.` — это точно не конец строки!) 8. Найдите строчки, в которых есть открывающая и когда-нибудь потом закрывающая скобки 9. Выделите одним махом весь кусок оглавления (в конце примера, вместе с тегами) 10. Выделите одним махом только текстовую часть оглавления, без тегов --- ## 3. Модуль re ---- ### "Сырые" строки - В коде регулярные выражения обычно записываются в виде «сырых» строк (с префиксом `r`, подавляющих экранирование). - Так, при поиске переноса строки удобнее указать `r"\n"` вместо `"\\n"`, избежав необходимости дублировать экранирующий символ ---- ### Способы работы 1. Если предполагается однократная проверка, достаточно использовать функции модуля `re`. При этом во время вызова функции, произойдет компиляция регулярного выражения и дальнейший поиск соответствий 2. Если проверка будет осуществляется не один раз, эффективным будет предварительно один раз скомпилировать выражение (получив специальный объект класса `re.regex`), а затем использовать его методы ---- ### Функции модуля re Функция|Описание -|- `re.search(pattern,` `string)`|Найти в строке `string` первую строчку, подходящую под шаблон `pattern` `re.fullmatch(pattern,` `string)`|Проверить, подходит ли строка `string` под шаблон `pattern` ---- Функция|Описание -|- `re.split(pattern,` `string,` `maxsplit=0)`|Аналог `str.split()`, только разделение происходит по подстрокам, подходящим под шаблон `pattern` `re.findall(pattern,` `string)`|Найти в строке `string` все непересекающиеся шаблоны `pattern` ---- Функция|Описание -|- `re.finditer(pattern,` `string)`|Итератор всем непересекающимся шаблонам `pattern` в строке `string` (выдаются `match`-объекты) `re.sub(pattern,` `repl,` `string,` `count=0)`|Заменить в строке `string` все непересекающиеся шаблоны `pattern` на `repl` ---- ### Примеры ``` python= import re match = re.search(r'\d\d\D\d\d', r'Телефон 123-12-12') print(match[0] if match else 'Not found') # 23-12 match = re.search(r'\d\d\D\d\d', r'Телефон 1231212') print(match[0] if match else 'Not found') # Not found ``` ---- ``` python= match = re.fullmatch(r'\d\d\D\d\d', r'12-12') print('YES' if match else 'NO') # YES match = re.fullmatch(r'\d\d\D\d\d', r'Т. 12-12') print('YES' if match else 'NO') # NO print(re.split(r'\W+', 'Где, скажите мне, мои очки??!')) # ['Где', 'скажите', 'мне', 'мои', 'очки', ''] ``` ---- ``` python= print(re.findall(r'\d\d\.\d\d\.\d{4}', r'Эта строка написана 19.01.2018, а могла бы и 01.09.2017')) # ['19.01.2018', '01.09.2017'] for m in re.finditer(r'\d\d\.\d\d\.\d{4}', r'Эта строка написана 19.01.2018, а могла бы и 01.09.2017'): print('Дата', m[0], 'начинается с позиции', m.start()) # Дата 19.01.2018 начинается с позиции 20 # Дата 01.09.2017 начинается с позиции 45 ``` ---- ``` python= print(re.sub(r'\d\d\.\d\d\.\d{4}', r'DD.MM.YYYY', r'Эта строка написана 19.01.2018, а могла бы и 01.09.2017')) # Эта строка написана DD.MM.YYYY, а могла бы и DD.MM.YYYY ``` ---- ### Дополнительные флаги Константа|Назначение -|- `re.ASCII`| По умолчанию `\w`, `\W`, `\b`, `\B`, `\d`, `\D`, `\s`, `\S` соответствуют все юникодные символы с соответствующим качеством.<br> `re.ASCII` ускоряет работу, если все соответствия лежат внутри ASCII ---- Константа|Назначение -|- `re.IGNORECASE`| Не различать заглавные и маленькие буквы. Работает медленнее, но иногда удобно `re.MULTILINE`| Специальные символы `^` и `$` соответствуют началу и концу каждой строки `re.DOTALL`| По умолчанию символ `\n` конца строки не подходит под точку. С этим флагом точка — вообще любой символ ---- ### Примеры ``` python= print(re.findall(r'\d+', '12 + ٦٧')) # ['12', '٦٧'] print(re.findall(r'\w+', 'Hello, мир!')) # ['Hello', 'мир'] print(re.findall(r'\d+', '12 + ٦٧', flags=re.ASCII)) # ['12'] print(re.findall(r'\w+', 'Hello, мир!', flags=re.ASCII)) # ['Hello'] print(re.findall(r'[уеыаоэяию]+', 'ОООО ааааа ррррр ЫЫЫЫ яяяя')) # ['ааааа', 'яяяя'] print(re.findall(r'[уеыаоэяию]+', 'ОООО ааааа ррррр ЫЫЫЫ яяяя', flags=re.IGNORECASE)) # ['ОООО', 'ааааа', 'ЫЫЫЫ', 'яяяя'] ``` ---- ``` python= text = r""" Торт с вишней1 вишней2 """ print(re.findall(r'Торт.с', text)) # [] print(re.findall(r'Торт.с', text, flags=re.DOTALL)) # ['Торт\nс'] print(re.findall(r'виш\w+', text, flags=re.MULTILINE)) # ['вишней1', 'вишней2'] print(re.findall(r'^виш\w+', text, flags=re.MULTILINE)) # ['вишней2'] ``` ---- ### Компиляция регулярок `re.compile(pattern, flags=0)` : Компилирует регулярное выражение `pattern`, используя флаги `flags` (например, `re.MULTILINE`) и возвращает объект класса `re.regex` *Пример:* ```python= import re regex = re.compile("^.+?(\d)") print(regex.search("Привет1 Привет12")[0]) ``` ---- ### Match-объекты - Если функции `re.search`, `re.fullmatch` не находят соответствие шаблону в строке, то они возвращают `None`, функция `re.finditer` не выдаёт ничего - Если соответствие найдено, то возвращается `match`-объект. Эта штука содержит в себе кучу полезной информации о соответствии шаблону ---- ### Атрибуты match Метод | Описание -|- `match[0]`, `match.group()`|Подстрока, соответствующая шаблону ```python= match = re.search(r'\w+', r'$$ What??') print(match[0]) # 'What' ``` ---- Метод | Описание -|- `match.start()`|Индекс в исходной строке, начиная с которого идёт найденная подстрока `match.end()`|Индекс в исходной строке, который следует сразу за найденной подстрокой ```python= match = re.search(r'\w+', r'$$ What??') print(match.start()) # 3 print(match.end()) # 7 ``` ---- ![](https://i.imgur.com/QH7cMbY.png) --- ## 4. Группировка и выбор ---- ### Группирующие скобки (...) - Если в шаблоне регулярного выражения встречаются скобки `(...)` без `?:`, то они становятся _группирующими_ - В match-объекте, который возвращают `re.search`, `re.fullmatch` и `re.finditer`, по каждой такой группе можно получить ту же информацию, что и по всему шаблону: - часть подстроки, которая соответствует `(...)` - индексы начала и окончания в исходной строке ---- ### Пример с группами ```python= import re pattern = r'\s*([А-Яа-яЁё]+)(\d+)\s*' string = r'--- Опять45 ---' match = re.search(pattern, string) print(f'Найдена строка >{match[0]}< с позиции {match.start(0)}', f'до {match.end(0)}') print(f'Группа букв >{match[1]}< с позиции {match.start(1)}', f'до {match.end(1)}') print(f'Группа цифр >{match[2]}< с позиции {match.start(2)}', f'до {match.end(2)}') ``` ``` Найдена строка > Опять45 < с позиции 3 до 16 Группа букв >Опять< с позиции 6 до 11 Группа цифр >45< с позиции 11 до 13 ``` ---- ![](https://i.imgur.com/gzhZSyf.png) ![](https://i.imgur.com/OY5Oa7p.png) ---- ### Особенности групп с квантификатором - Если к группирующим скобкам применён квантификатор (то есть указано число повторений), то подгруппа в `match`-объекте будет создана только для последнего соответствия ---- - Например, если бы в для строки <br>`'--- Опять45 ---'` <br>квантификаторы были снаружи от скобок <br>`'\s*([А-Яа-яЁё])+(\d)+\s*'`, то вывод был бы таким: ``` Найдена подстрока > Опять45 < с позиции 3 до 16 Группа букв >ь< с позиции 10 до 11 Группа цифр >5< с позиции 12 до 13 ``` ---- ### Вложенные скобки - Внутри группирующих скобок могут быть и другие группирующие скобки. - В этом случае их нумерация производится в соответствии с номером появления открывающей скобки в шаблоне ---- ```python= import re pattern = r'((\d)(\d))((\d)(\d))' string = r'123456789' match = re.search(pattern, string) print(f'Найдена строка >{match[0]}< с позиции {match.start(0)}', f'до {match.end(0)}') for i in range(1, 7): print(f'Группа №{i} >{match[i]}< с позиции {match.start(i)}', f'до {match.end(i)}') ``` ``` Найдена строка >1234< с позиции 0 до 4 Группа №1 >12< с позиции 0 до 2 Группа №2 >1< с позиции 0 до 1 Группа №3 >2< с позиции 1 до 2 Группа №4 >34< с позиции 2 до 4 Группа №5 >3< с позиции 2 до 3 Группа №6 >4< с позиции 3 до 4 ``` ---- ### Использование групп при заменах - Использование групп добавляет замене (`re.sub`) очень удобную возможность: в шаблоне для замены можно ссылаться на соответствующую группу при помощи `\1, \2, \3, ...` ---- - Например, если нужно даты из неудобного формата `ММ/ДД/ГГГГ` перевести в удобный `ДД.ММ.ГГГГ`, то можно использовать такую регулярку: ```python= import re text = """ We arrive on 03/25/2018. So you are welcome after 04/01/2018. """ print(re.sub(r'(\d\d)/(\d\d)/(\d{4})', r'\2.\1.\3', text)) ``` ``` We arrive on 25.03.2018. So you are welcome after 01.04.2018. ``` ---- ### Невыделяемая группа `(?:часть_выражения)` : Определяет невыделяемую группу (без захвата) *Пример:* * выражение `'(?:\w+)\s'` выполнит в тексте поиск слов, отделенных пробельным символом; при этом слово НЕ будет захвачено в отдельную группу ---- ### Выбор Операция выбора позволяет захватить одно из нескольких выражений в качестве результата поиска `выражение1|выражение2|выражение3` : Соответствует любому элементу, разделенному вертикальной чертой `'|'` ---- ### Пример * выражение `'красн(?:ый|оватый|енький)'` найдет в тексте слова `'красный'`, `'красноватый'`, `'красненький'`; при этом окончание не будет захвачено в группу --- ## 5. Проверка границ (привязки) ---- ### Простые шаблоны Шаблон | Описание | Пример | Результат -|-|-|- `^`|Начало всего текста или начало строчки текста, если `flag=re.MULTILINE`|`^Привет`| `$`|Конец всего текста или конец строчки текста, если `flag=re.MULTILINE`|`Будь здоров!$`| ---- Шаблон | Описание | Пример | Результат -|-|-|- `\A`|Строго начало всего текста| | `\Z`|Строго конец всего текста | | `\b`|Начало или конец слова (слева пусто или не-буква, справа буква и наоборот)|`\bвал`|<u>вал</u>, перевал, Перевалка ---- Шаблон | Описание | Пример | Результат -|-|-|-- `\B`|Не граница слова: либо и слева, и справа буквы, либо и слева, и справа НЕ буквы|`\Bвал`| пере<u>вал</u>, вал, Пере<u>вал</u>ка| &nbsp;|&nbsp;|`\Bвал\B`|перевал, вал, Пере<u>вал</u>ка ---- ![](https://i.imgur.com/wwq2M6D.png) ---- ### Сложные шаблоны (_lookaround_) Шаблон|Описание|Пример|Результат -|-|-|- `(?=...)`|_lookahead assertion_, соответствует каждой позиции, сразу после которой начинается соответствие шаблону `...`|`Isaac (?=Asimov)`|<u>Isaac </u>Asimov, Isaac other ---- Шаблон|Описание|Пример|Результат -|-|-|- `(?!...)`|_negative lookahead assertion_, соответствует каждой позиции, сразу после которой НЕ может начинаться шаблон `...`|`Isaac (?!Asimov)`|Isaac Asimov, <u>Isaac </u>other ---- Шаблон|Описание|Пример|Результат -|-|-|- `(?<=...)`|_positive lookbehind assertion_, соответствует каждой позиции, которой может заканчиваться шаблон `...`<br>Длина шаблона должна быть фиксированной|`(?<=abc)def`|abc<u>def</u>, bcdef ---- Шаблон|Описание|Пример|Результат -|-|-|- `(?<!...)`|_negative lookbehind assertion_, соответствует каждой позиции, которой НЕ может заканчиваться шаблон `...`|`(?<!abc)def`|abcdef, bc<u>def</u> ---- ![](https://i.imgur.com/7GQu8Zm.png) ---- ### Примеры Шаблон | Комментарий | Результат -|-|- `(?<!\d)\d(?!\d)`|Цифра, окружённая не-цифрами|Text ABC 123 A<u>1</u>B<u>2</u>C<u>3</u>! `(?<=#START#).*?(?=#END#)`|Текст от `#START#` до `#END#`|text from #START#<u> till </u>#END# ---- Шаблон | Комментарий | Результат -|-|- `\d+(?=_(?!_))`|Цифра, после которой идёт ровно одно подчёркивание|<u>12</u>_34__56 `^(?:(?!boo).)*?$`|Строка, в которой нет boo (то есть нет такого символа, перед которым есть boo)| <u>a foo and</u><br>boo and zoo>br><u>and others</u> ---- Шаблон | Комментарий | Результат -|-|- `^(?:(?!boo)(?!foo).)*?$`|Строка, в которой нет ни boo, ни foo|a foo and<br>boo and zoo<br><u>and others</u> --- ## Спасибо за внимание! ![](https://i.imgur.com/gKDsna4.png) (c) Яценко Р.Н., 2019-2021 [Учебный центр компьютерных технологий "Кит"](http://kit.kh.ua/)
{"metaMigratedAt":"2023-06-16T00:07:04.181Z","metaMigratedFrom":"YAML","title":"Тема 9. Регулярные выражения","breaks":false,"slideOptions":"{\"allottedMinutes\":80,\"transition\":\"slide\",\"theme\":\"beige\",\"slideNumber\":\"c\",\"spotlight\":{\"enabled\":true}}","contributors":"[{\"id\":\"93a8c43f-1b5b-4461-9101-89b183ccbc1c\",\"add\":21220,\"del\":2263}]","description":"© Яценко Р.Н., 2019-2021"}
    716 views