changed 4 years ago
Published Linked with GitHub

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

© Яценко Р.Н., 2019-2021

Учебный центр компьютерных технологий "Кит"

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

1. Введение в регулярные выражения (re)


Назначение

  • Проще говоря, регулярное выражение используется для поиска паттернов в указанной строке. Паттерном может быть все что угодно

  • Можно создавать паттерны соответствия электронной почте или мобильному номеру. Можно создать паттерны, которые ищут слова в строке, начинающиеся на “a” и заканчивающиеся на “z”


Где используются

  1. Проверка соответствия фрагментов текста некоторым критериям. Например, наличие символа обозначения валюты и последующих за ним цифр, проверка адреса электронной почты и т.д.

  2. Поиск подстрок, которые в том числе могут иметь несколько форм. Например, поиск 'pet.png', 'pet.jpg', 'pet.jpeg' или 'pet.svg', но не обнаруживались подстроки 'carpet.png' и подобные ей


  1. Замена всего, что совпадает с шаблоном, на указанную строку. Например, поиск подстроки 'устройство передвижения, движимое мускульной силой' и замена подстрокой 'велосипед'

  2. Разбиение строки по точкам совпадения с шаблоном. Например, разбиение строки по ':' или '='


Пример кода

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. Язык регулярных выражений


Определение

Регулярные выражения (англ. Regular Expressions)
это компактная форма записи (шаблон, маска или паттерн) представления о коллекции строк. Гибкость и мощь регулярных выражений обусловлена тем, что единственное регулярное выражение может представлять неограниченное число строк, подходящий под заданный шаблон

Строка-шаблон

  1. Флаги
  2. Символы и классы символов
  3. Квантификаторы
  4. Группировка и выбор
  5. Проверка границ (привязки)

Флаги

Флаги не входят непосредственно в регулярное выражение, однако расширяют его функции:

  • 'g' глобальный поиск (обрабатываются все совпадения с шаблоном поиска)
  • 'i' регистр букв не имеет значения (по умолчанию любой поиск регистрозависим)
  • 'm' многострочный поиск
  • и др.

Флаг указывается после паттерна, например: '/[0-9]/m'


Поиск символов и строк

Одним из наиболее простых случаев является поиск отдельных символов и строк

https://regex101.com/r/Y67E75/1


Экранирование специальных символов

Специальные символы (символы-джокеры должны экранироваться символом обратного слеша '\'. К ним относятся:

\ . ~ $ ? + * { } [ ] ( ) |

В пределах регулярных выражений можно также использовать большинство стандартных экранированных последовательностей языка 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. Найдите все «слова», написанные капсом (то есть строго заглавными), возможно внутри настоящих слов (аааБББввв)
  3. Найдите слова, в которых есть русская буква, а когда-нибудь за ней цифра
  4. Найдите все слова, начинающиеся с русской или латинской большой буквы (\b — граница слова)

  1. Найдите слова, которые начинаются на гласную (\b — граница слова)
  2. Найдите все натуральные числа, не находящиеся внутри или на границе слова
  3. Найдите строчки, в которых есть символ * (. — это точно не конец строки!)
  4. Найдите строчки, в которых есть открывающая и когда-нибудь потом закрывающая скобки
  5. Выделите одним махом весь кусок оглавления (в конце примера, вместе с тегами)
  6. Выделите одним махом только текстовую часть оглавления, без тегов

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

Примеры

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

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+', 'Где, скажите мне, мои очки??!')) # ['Где', 'скажите', 'мне', 'мои', 'очки', '']

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

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 соответствуют все юникодные символы с соответствующим качеством.
re.ASCII ускоряет работу, если все соответствия лежат внутри ASCII

Константа Назначение
re.IGNORECASE Не различать заглавные и маленькие буквы. Работает медленнее, но иногда удобно
re.MULTILINE Специальные символы ^ и $ соответствуют началу и концу каждой строки
re.DOTALL По умолчанию символ \n конца строки не подходит под точку. С этим флагом точка — вообще любой символ

Примеры

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)) # ['ОООО', 'ааааа', 'ЫЫЫЫ', 'яяяя']

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

Пример:

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() Подстрока, соответствующая шаблону
match = re.search(r'\w+', r'$$ What??') print(match[0]) # 'What'

Метод Описание
match.start() Индекс в исходной строке, начиная с которого идёт найденная подстрока
match.end() Индекс в исходной строке, который следует сразу за найденной подстрокой
match = re.search(r'\w+', r'$$ What??') print(match.start()) # 3 print(match.end()) # 7


4. Группировка и выбор


Группирующие скобки ()

  • Если в шаблоне регулярного выражения встречаются скобки (...) без ?:, то они становятся группирующими
  • В match-объекте, который возвращают re.search, re.fullmatch и re.finditer, по каждой такой группе можно получить ту же информацию, что и по всему шаблону:
    • часть подстроки, которая соответствует (...)
    • индексы начала и окончания в исходной строке

Пример с группами

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


Особенности групп с квантификатором

  • Если к группирующим скобкам применён квантификатор (то есть указано число повторений), то подгруппа в match-объекте будет создана только для последнего соответствия

  • Например, если бы в для строки
    '--- Опять45 ---'
    квантификаторы были снаружи от скобок
    '\s*([А-Яа-яЁё])+(\d)+\s*', то вывод был бы таким:
Найдена подстрока >   Опять45   < с позиции 3 до 16 
Группа букв >ь< с позиции 10 до 11 
Группа цифр >5< с позиции 12 до 13

Вложенные скобки

  • Внутри группирующих скобок могут быть и другие группирующие скобки.

  • В этом случае их нумерация производится в соответствии с номером появления открывающей скобки в шаблоне


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, ...

  • Например, если нужно даты из неудобного формата ММ/ДД/ГГГГ перевести в удобный ДД.ММ.ГГГГ, то можно использовать такую регулярку:
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вал вал, перевал, Перевалка

Шаблон Описание Пример Результат
\B Не граница слова: либо и слева, и справа буквы, либо и слева, и справа НЕ буквы \Bвал перевал, вал, Перевалка
    \Bвал\B перевал, вал, Перевалка


Сложные шаблоны (lookaround)

Шаблон Описание Пример Результат
(?=...) lookahead assertion, соответствует каждой позиции, сразу после которой начинается соответствие шаблону ... Isaac (?=Asimov) Isaac Asimov, Isaac other

Шаблон Описание Пример Результат
(?!...) negative lookahead assertion, соответствует каждой позиции, сразу после которой НЕ может начинаться шаблон ... Isaac (?!Asimov) Isaac Asimov, Isaac other

Шаблон Описание Пример Результат
(?<=...) positive lookbehind assertion, соответствует каждой позиции, которой может заканчиваться шаблон ...
Длина шаблона должна быть фиксированной
(?<=abc)def abcdef, bcdef

Шаблон Описание Пример Результат
(?<!...) negative lookbehind assertion, соответствует каждой позиции, которой НЕ может заканчиваться шаблон ... (?<!abc)def abcdef, bcdef


Примеры

Шаблон Комментарий Результат
(?<!\d)\d(?!\d) Цифра, окружённая не-цифрами Text ABC 123 A1B2C3!
(?<=#START#).*?(?=#END#) Текст от #START# до #END# text from #START# till #END#

Шаблон Комментарий Результат
\d+(?=_(?!_)) Цифра, после которой идёт ровно одно подчёркивание 12_34__56
^(?:(?!boo).)*?$ Строка, в которой нет boo (то есть нет такого символа, перед которым есть boo) a foo and
boo and zoo>br>and others

Шаблон Комментарий Результат
^(?:(?!boo)(?!foo).)*?$ Строка, в которой нет ни boo, ни foo a foo and
boo and zoo
and others

Спасибо за внимание!

© Яценко Р.Н., 2019-2021

Учебный центр компьютерных технологий "Кит"

Select a repo