Регулярные выражения (RegEx) — это специальная последовательность символов, которая использует шаблон поиска для поиска строки или набора строк. Он может определять наличие или отсутствие текста путем сопоставления с определенным шаблоном, а также может разбивать шаблон на один или несколько подшаблонов. Python предоставляет модуль re, который поддерживает использование регулярных выражений в Python. Его основная функция — предлагать поиск, где он принимает регулярное выражение и строку. Он либо возвращает первое совпадение, либо ничего.

Сырые строки / raw strings

Чтобы строка стала «сырой», перед ней необходимо поставить символ r в любом регистре:

common_string = 'C:\file.txt'
raw_string = r'C:\file.txt'

В такой строке отключается экранирование. Это значит, что обратный слеш считается самостоятельным символом. Основное применение сырых строк – работа с регулярными выражениями.

Можно использовать несколько префиксов сразу. Например, вот так:

raw_f_string = rf'C:\file.txt'
f_raw_string = fr'C:\file.txt'

Экранирование в регулярных выражениях

В регулярных выражениях также есть экранирование. Каждое регулярное выражение, которое используется в коде, компилируется интерпретатором, и заменяет управляющие последовательности на другие символы. Если нам нужно использовать какой-то спецсимвол как простой текст, то достаточно поставить перед ним . Это работает со всеми спецсимволами!

Полезные строки

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

<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ !"#$%&\'()*+,-./0123456789:;

С помощью неё можно наглядно увидеть как работает какой-либо спецсимвол или шаблон. Для тестирования примитивных выражений будет очень полезна.

Синтаксис регулярных выражений

У регулярных выражений существует свой необычный синтаксис. Мы пройдём все темы по порядку. Начнём с текста как регулярного выражения.

Текст как регулярное выражение

Начнём с самых лёгких регулярных выражений - текстовых. Простая строка r”Привет” может быть использована как регулярное выражение:

r"Привет"

Использование квадратных скобок

1) Если мы хотим заменить несколько символов, мы можем использовать такой синтаксис с квадратными скобками:

r'[cr1]' #Найдёт c, r, и 1
r'[cr]at' #Найдёт слова cat и rat
r'[12]7[56]' #Найдёт 175, 176, 275, 276

От перестановки символов смысл не меняется:

r'[cr1]' #Найдёт c, r, и 1
r'[rc1]' #Найдёт c, r, и 1
r'[1cr]' #Найдёт c, r, и 1
r'[1rc]' #Найдёт c, r, и 1
r'[c1r]' #Найдёт c, r, и 1
r'[r1c]' #Найдёт c, r, и 1
#Все регулярные выражения сверху выдают один и тот же результат

2) Если мы хотим исключить какие-либо символы, мы можем использовать скобки так:

r'[^12]' #Найдёт всё, кроме 1 и 2
r'[^12]7' #Найдёт всё, кроме 17 и 27

Достаточно перед символами, которые мы хотим исключить, прописать символ ^.

3) Регулярное выражение в скобках можно сократить вот так:

r'[0-9]' #То же самое, что и [0123456789]
r'[a-z]' #То же самое, что и [abcdefghijklmnopqrstuvwxyz]
r'[A-Z]' #То же самое, что и [ABCDEFGHIJKLMNOPQRSTUVWXYZ]
r'[а-я]' #То же самое, что и [абвгдежзийклмнопрстуфхцчшщъыьэюя]
r'[А-Я]' #То же самое, что и [АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ]

Можно получать неполный алфавит, или не все цифры:

r'[4-7]' #То же самое, что и [4567]
r'[x-z]' #То же самое, что и [xyz]
r'[B-D]' #То же самое, что и [BCD]
r'[а-ж]' #То же самое, что и [абвгдеж]
r'[П-Т]' #То же самое, что и [ПРСТ]
r'[6-D]' #То же самое, что и [6789ABCD]

А также совмещать синтаксис:

r'[4-7qwerty]' #То же самое, что и [qwerty4567]
r'[23x-z1]' #То же самое, что и [xyz123]
r'[B-DF]' #То же самое, что и [BCDF]

4) Исключение символов можно тоже сократить:

r'[^0-9]' #То же самое, что и [^0123456789]
r'[^a-z]' #То же самое, что и [^abcdefghijklmnopqrstuvwxyz]
r'[^A-Z]' #То же самое, что и [^ABCDEFGHIJKLMNOPQRSTUVWXYZ]
r'[^а-я]' #То же самое, что и [^абвгдежзийклмнопрстуфхцчшщъыьэюя]
r'[^А-Я]' #То же самое, что и [^АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ]

Можно исключать неполный алфавит, или не все цифры:

r'[^4-7]' #То же самое, что и [^4567]
r'[^x-z]' #То же самое, что и [^xyz]
r'[^B-D]' #То же самое, что и [^BCD]
r'[^а-ж]' #То же самое, что и [^абвгдеж]
r'[^П-Т]' #То же самое, что и [^ПРСТ]
r'[^6-D]' #То же самое, что и [^6789ABCD]

Ну и совмещать:

r'[^4-7qwerty]' #То же самое, что и [^qwerty4567]
r'[^23x-z1]' #То же самое, что и [^xyz123]
r'[^B-DF]' #То же самое, что и [^BCDF]

5) Можно использовать столько сокращений, сколько мы захотим:

r'[a-zA-Z0-9]' #То же самое, что [abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]
r'[^э-я1-3]' #То же самое, что и [^эюя123]

Обратите внимание, что шаблон [а-яА-Я] не захватывает буквы ё и Ё.

Шаблоны

В регулярных выражения можно использовать специальные шаблоны, которые могут соответствовать как одному, так и нескольким символам.

ШаблонСоответствие
\nНовая строка
.Любой символ, кроме символа новой строки. Если flag=re.DOTALL - любой символ.
\sЛюбой символ пробела, табуляции или новой строки.
\SЛюбой символ, кроме пробела, табуляции или новой строки.
\dЛюбая цифра. Эквивалентно [0-9]
\DЛюбой символ, кроме цифр. Эквивалентно [^0-9]
\wЛюбая буква, цифра, или _.
\WЛюбой символ, кроме букв, цифр, и _.
\bСимвол между символом, совпадающим с \w, и символом, не совпадающим с \w в любом порядке.
\BСимвол между двумя символами, совпадающими с \w или \W.
\AНачало всего текста
\ZКонец всего текста
^Начало всего текста или начало строчки текста, если flag=re.MULTILINE
$Конец всего текста или конец строчки текста, если flag=re.MULTILINE
\rcarriage return или CR, символ Юникода U+2185.
\tTab символ
\0null, символ Юникода U+2400.
\vВертикальный пробел в Юникоде
\xYY8-битный символ с заданным шестнадцатеричным значением. [Таблица юникода](https://unicode-table.com/en/). Например \x2A находит символ *.
\ddd8-битный символ с заданным восьмеричным значением. [Таблица UTF-8](https://www.utf8-chartable.de/unicode-utf8-table.pl?utf8=oct)
[\b]Символ backspace или BS. В скобках, т.к. \b уже занято другим спецсимволом.
\fСимвол разрыва страницы.

Жадные квантификаторы

Очень часто, когда нужно использовать несколько шаблонов подряд, приходится их писать друг за другом. Например, если мы хотим найти 3 цифры подряд мы напишем:

r'\d\d\d'

Но теперь, мы можем сократить эту запись вот так, используя квантификаторы:

r'\d{3}'

Что же такое квантификатор?

Квантификатор - конструкция, которая позволяет указывать количество повторений.

КвантификаторИспользование
{n}Ровно n повторений
{m,n}От m до n повторений.
{m,}Не менее m повторений
{,n}Не более n повторений
?Ноль или одно повторение. То же, что и {0,1}
*Ноль или более повторений. То же, что и {0,}
+Одно или более повторений. То же, что и {1,}

Интересные факты о квантификаторах:

  • В каждом квантификаторе учитываются и начало, и конец отрезка.
  • Каждый квантификатор по умолчанию - жадный. Жадные квантификаторы - такие квантификаторы, которые пытаются захватить как можно больше символов.

Ленивые квантификаторы

Все квантификаторы - по умолчанию жадные. Они пытаются захватить максимальное количество символов.

{m,n}
{,n}
{m,}
*
+
?

Если после квантификатора поставить знак ?, то можно его сделать ленивым. Тогда он будет захватывать минимальное количество символов.

Ленивые квантификаторы

{m,n}? - от m до n
{,n}? - до n
{m,}? - от m
*? - от 0
+? - от 1
?? - от 0 до 1
Каждый из этих квантификаторов будет пытаться захватить как можно меньше символов.

Обратите внимение, что жадность меняет работу всех квантификаторов, кроме квантификатора {n}. Но это и логично, так как в любом случае он будет искать нужную последовательность n раз, независимо от его жадности. Квантификатор {n} будет равносилен квантификатору {n}?, правда в последнем нет никакого смысла.

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

Если шаблон регулярного выражения обернуть в круглые скобки - мы сгруппируем его. Такие группы создаются для получения дополнительной информации о них.

Позже мы к ним снова вернёмся и пройдём их работу в самом Python. Но сейчас нужно изучить сам синтаксис.

СинтаксисИспользование
(regex)Обыкновенная скобочная группа. Захватывает символы, о которых можно будет получить более подробную информацию.
(?P\<name\>regex)Обычная скобочная группа, но вместо номера ей будет присвоено имя name.
(?P\<name\>regex)(?P=name)Скобочная группа с именем позволяет к ней обращаться и искать такой же текст, который она захватила.

Эти же группы в реальных примерах:

  • 1) Обычная группа ([a-z]{4})
  • 2) Та же группа, только уже именованная (?P[a-z]{4}). Теперь мы можем получить её в Python не по номеру, а по имени.
  • 3) Снова именованная группа, но мы получаем текст, который она захватила ранее благодаря синтаксису (?P[a-z]{4})(?P=name)

Ссылки на нумерованные группы

Ссылаться можно не только на именованные группы, но и на обычные. Для этого достаточно использовать синтаксис \1, \2, \3, … и так далее. Цифра после слеша означает номер группы, к которой вы обращаетесь.

Повторяем регулярное выражение, но уже без именованных групп ([a-z]{4})\1

Скобочные выражения

Если в группе, после открывающейся круглой скобки поставить специальные символы - можно создать особое скобочное выражение, которое добавит новую функциональность регулярным выражениям.

В чём же разница между скобочными выражениями, и группирующими скобками?

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

Non-capturing group, Comment group

Comment group

(?#) - скобочное выражение, позволяющее написать комментарий в регулярном выражении

Non-capturing group

(?:) - скобочное выражение, которое группирует регулярное выражение, но не захватывает его как скобочная группа. Так и называется - Non-capturing group, т.е. группа без захвата.

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

(?:\d.){2} равносильно \d.\d.

Если бы мы использовали обычные группирующие скобки, они бы захватили эти символы отдельно.

Если вы используете группу, и не обращаетесь к ней по её имени/номеру - используйте вместо неё non-capturing group, так как обычные группы изменяют поведение методов в регулярных выражениях.

Lookahead и Lookbehind

НазваниеСинтаксисИспользованиеПримерПрименяем к тексту
Positive Lookahead(?=)Проверяет стоит ли переданное выражение после шаблона. Не захватывает никаких символов.2(?= 3)1 2 3 6 2 8
Negative Lookahead(?!)Проверяет что переданное выражение не стоит после шаблона. Не захватывает никаких символов.2(?! 3)1 2 3 6 2 8
Positive Lookbehind(?<=)Проверяет стоит ли переданное выражение перед шаблоном. Не захватывает никаких символов.(?<=6 )21 2 3 6 2 8
Negative Lookbehind(?<!)Проверяет что переданное выражение не стоит перед шаблоном. Не захватывает никаких символов.(?<!6 )21 2 3 6 2 8

Условие (?(n)yes|no)

Есть интересное скобочное выражение, которое позволяет добавлять условия в регулярные выражения. Если у группы n нашлись совпадения - возвращается шаблон до или. В противном случае возвращается шаблон после или.

Ограничение Lookbehind

Обратите внимание, что все выражения в lookbehind должны быть фиксированной ширины, иначе вы получите ошибку re.error: look-behind requires fixed-width pattern.

Движок регулярных выражений в Python не может работать с выражениями неопределённой длины в Lookbehind из-за технических особенностей.

# Вызовут ошибку:

r'(?<=test{0,})regex'
r'(?<=g?)regex'
r'(?<!Python+)regex'

# Длина вхождений выражений в Lookbehind может быть разной
# Поэтому появится ошибка



# Ошибки не будет:

r'(?<=test)regex'
r'(?<=g{21})regex'
r'(?<!Pytho[mn])regex'

# Длина вхождений выражений в Lookbehind фиксированная
# Всё выполнится без ошибок

Операция или

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

Например, выражение:

r'Привет|Пока'

найдёт все слова Привет и Пока в тексте.

  • Оператор или в скобочных выражениях и группах:

    Если использовать оператор или в скобочных выражениях или группах, то он не затронет символы извне.

  • Оператор или в lookbehind

    Обратите внимание, что в lookbehind можно использовать или, только если все шаблоны одинаковой длины. То есть такие выражения будут правильными:

(?<=hi!|bye)
(?<![abcdef]|\d)
(?<=\w|\W|\s)

Такое использование lookbehind вызовет ошибку re.error: look-behind requires fixed-width pattern:

(?<=hi!|long_text)
(?<![abcdef]|\d{4})
(?<=\w\s|\W)

Для того, чтобы обойти такое исключение, нужно использовать или в non-capturing group:

(?:(?<=hi!)|(?<=long_text))
(?:(?<![abcdef])|(?<=\d{4}))
(?:(?<=\w\s)|(?<=\W))

В lookahead можно спокойно ставить условия с шаблонами разной длины, ошибок не будет.

Импортируем модуль «re»

Для начала работы нам нужно импортировать модуль.

Модуль «re» устанавливать не нужно, он устанавливается вместе с самим Python. Нам лишь нужно импортировать его в проект.

Для этого в начале строки пропишем:

import re

Объект Match

В следующих четырёх методах, которые мы изучим, используется объект Match. Объект Match выглядит примерно так:

<re.Match object; span=(0, 6), match='Привет'>

У объекта Match есть несколько интересных методов:

  • group
  • groups
  • groupdict
  • start, end
  • span

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

С группами мы будем работать позже, поэтому пока будем вызывать методы без аргументов или с аргументом 0. Нулевая группа - полная строка, которую захватило регулярное выражение.

При вызове метода с номером группы, который больше, чем количество существующих групп, возникнет ошибка: IndexError: no such group.

  • Метод group возвращает группу по её номеру:
group(__group=0)
  • Метод groups возвращает кортеж со всеми группами:
groups()
  • Метод groupdict возвращает словарь, в котором ключи - имена групп, а значения - строки, которые захватила группа:
groupdict()
  • Методы start и end возвращают индексы начала и конца совпадения с регулярным выражением группы, номер которой был передан в метод:
start(__group=0), end(__group=0)
print(result.start()) # 0
print(result.end())   # 6
  • Метод span возвращает кортеж с индексом начала и конца подстроки группы, номер которой был передан в метод. Он работает аналогично методам start, end, но возвращает пару чисел:
print(result.span()) # (0, 6)

Match re.match

re.match(pattern, string, flags=0) – ищет совпадения в тексте, в начале строки.

Параметры:

  • pattern - регулярное выражение
  • string - строка, к которой нужно применить регулярное выражение
  • flags - флаги
  • Возвращаемое значение:

  • Объект Match, если совпадение было найдено
  • None, если нету совпадений

Примеры использования:

import re

pattern = 'Привет'
string = 'Привет, как твои дела? Привет, нормально, учу регулярные выражения.'
result = re.match(pattern, string)

print(result)
# В данном примере будет выведено <re.Match object; span=(0, 6), match='Привет'>

Обратите внимание, что re.match ищет ПЕРВОЕ совпадение В НАЧАЛЕ СТРОКИ. В примере выше re.match находит только первое вхождение pattern в string.

Если строка будет такая:

import re

pattern = 'Привет'
string = 'Как твои дела? Привет, нормально, учу регулярные выражения.'
result = re.match(pattern, string)

print(result)
# В данном примере будет выведено None

Match re.search

re.search(pattern, string, flags=0) - похож на re.match, но ищет совпадения не только в начале строки.

Параметры:

  • pattern - регулярное выражение
  • string - строка, к которой нужно применить регулярное выражение
  • flags - флаги

Возвращаемое значение:

  • Объект Match, если совпадение было найдено
  • None, если нету совпадений

Примеры использования:

Eсли мы попытаемся найти Привет:

import re

pattern = 'Привет'
string = 'Привет, как твои дела? Привет, нормально, учу регулярные выражения.'
result = re.search(pattern, string)

print(result)
# В данном примере будет выведено <re.Match object; span=(0, 6), match='Привет'>

Вернётся то же самое, что и при re.match, но если строка будет такой:

import re

pattern = 'Привет'
string = 'Как твои дела? Привет, нормально, учу регулярные выражения.'
result = re.search(pattern, string)

print(result)
# В данном примере будет выведено <re.Match object; span=(15, 21), match='Привет'>

Если бы в этом примере мы использовали re.match, то он бы возвратил None.

Match re.fullmatch

re.fullmatch(pattern, string, flags=0) - определяет соответствие строки переданному шаблону. Если вся строка соответствует шаблону - выводит объект Match, иначе - None.

Параметры:

  • pattern - регулярное выражение
  • string - строка, к которой нужно применить регулярное выражение
  • flags - флаги, пройдём позже

Возвращаемое значение:

  • Объект Match, если вся строка соответствует шаблону
  • None, если строка не соответствует шаблону

Примеры использования:

import re

print(re.fullmatch('\d', '111')) #None
print(re.fullmatch('\d', '1')) #<re.Match object; span=(0, 1), match='1'>

Match re.finditer

re.finditer(pattern, string, flags=0) - ищет все вхождения pattern в строке string и возвращает итератор Match объектов.

Параметры:

  • pattern - регулярное выражение
  • string - строка, к которой нужно применить регулярное выражение
  • flags - флаги, пройдём позже

Возвращаемое значение:

  • Итератор Match объектов

Примеры использования:

import re

pattern = 'Привет'
string = 'Привет, как твои дела? Привет, нормально, учу регулярные выражения.'
result = re.finditer(pattern, string, 0)
for i in result:
    print(i)
# В данном примере будет выведено:
#<re.Match object; span=(0, 6), match='Привет'>
#<re.Match object; span=(23, 29), match='Привет'>

re.findall

re.findall(pattern, string, flags=0) - возвращает список всех найденных совпадений. У метода findall нет ограничений на поиск в начале или конце строки. Если мы будем искать «Привет» в нашей строке, он вернет все вхождения «Привет». Для поиска рекомендуется использовать именно findall, так как он может работать и как re.search, и как re.match.

Параметры:

  • pattern - регулярное выражение
  • string - строка, к которой нужно применить регулярное выражение
  • flags - флаги, пройдём позже

Возвращаемое значение:

  • Список совпадений, если они есть
  • Пустой список, если совпадений нет

Примеры использования:

import re

pattern = 'Привет'
string = 'Привет, как твои дела? Привет, нормально, учу регулярные выражения.'
result = re.findall(pattern, string)

print(result)
# В данном примере будет выведено ['Привет', 'Привет']

re.split

re.split(pattern, string, maxsplit=0, flags=0) – разбивает строки по заданному паттерну и возвращает разбитые строки.

Параметры:

  • pattern - регулярное выражение
  • string - строка, к которой нужно применить регулярное выражение
  • maxsplit - максимальное количество делений строки
  • flags - флаги, пройдём позже

Возвращаемое значение:

  • Если совпадения есть - список частей разделённой строки.
  • [string], если совпадений нет

Примеры использования:

import re

pattern = 'Привет'
string = 'Привет, как твои дела? Привет, нормально, учу регулярные выражения.'
result = re.split(pattern, string)

print(result)
# В данном примере будет выведено ['', ', как твои дела? ', ', нормально, учу регулярные выражения.']

re.sub

re.sub(pattern, replace, string, count=0, flags=0) – заменяет символы по заданному паттерну на заданные символы и возвращает исправленную строку.

Параметры:

  • pattern - регулярное выражение
  • replace - то, на что нужно заменить найденное вхождение
  • string - строка, к которой нужно применить регулярное выражение
  • count - необязательный аргумент, максимальное число вхождений, подлежащих замене. Если этот параметр опущен или равен нулю, то произойдет замена всех вхождений.
  • flags - флаги, пройдём позже

Возвращаемое значение:

  • Если совпадения есть - изменённая строка
  • string, если совпадений нет

Примеры использования:

import re

pattern = 'Привет'
replace = 'Пока'
string = 'Привет, как твои дела? Привет, нормально, учу регулярные выражения.'
result = re.sub(pattern, replace, string)

print(result)
# В данном примере будет выведено Пока, как твои дела? Пока, нормально, учу регулярные выражения.

re.subn

re.subn(pattern, replace, string, count=0, flags=0) – выполняет ту же операцию, что и функция sub, но возвращает кортеж.

Параметры:

  • pattern - регулярное выражение
  • replace - то, на что нужно заменить найденное вхождение
  • string - строка, к которой нужно применить регулярное выражение
  • count - необязательный аргумент, максимальное число вхождений, подлежащих замене. Если этот параметр опущен или равен нулю, то произойдет замена всех вхождений.
  • flags - флаги, пройдём позже

Возвращаемое значение:

Кортеж (new_string, number_of_subs), где

  • new_string - новая строка, или старая, если не было совершено замен.
  • number_of_subs - количество сделанных замен

Примеры использования:

import re

pattern = 'Привет'
replace = 'Пока'
string = 'Привет, как твои дела? Привет, нормально, учу регулярные выражения.'
result = re.subn(pattern, replace, string)

print(result)
# Выведет ('Пока, как твои дела? Пока, нормально, учу регулярные выражения.', 2)

re.escape

re.escape(pattern) - экранирует специальные символы в pattern. Полезно, если нужно использовать полученную строку как регулярное выражение, но в ней могут содержаться спецсимволы.

Если в метод передавать не сырую строку, а обычную - некоторые символы могут экранироваться и “потеряться”. В итоге вы получите немного не ту строку для регулярного выражения, которую вы ожидали.

Это и логично, ведь если мы передаём строку в метод re.escape, то мы ожидаем, что она может содержать экранируемые последовательности или спецсимволы из регулярных выражений.

Параметры:

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

Возвращаемое значение:

  • Строка, с экранированными спецсимволами

Примеры использования:

Примеры использования:

import re

print(re.escape(r'https://stepik.org/lesson/694442/step/1?unit=694231'))
# Выводит https\:\/\/stepik\.org\/lesson\/694442\/step\/1\?unit\=694231

Группы в Match объектах

Если в регулярном выражении есть группирующие скобки, то мы можем получить информацию о этих группах в Match объектах.

В группах в Match объектах есть одна особенность - всегда существует нулевая группа. Нулевая группа это всё, что захватило регулярное выражение.

Остальные группы нумеруются как обычно - с единицы.

Давайте снова вспомним методы Match объектов, но уже более подробно разберём группы:

  • group
  • groups
  • groupdict
  • start, end
  • span

group(__group=0)

Метод group возвращает группу по её номеру или имени. По умолчанию это нулевая группа, т.е. всё, что захватило регулярное выражение. Получить группу можно разными способами:

import re

res = re.search(r'(?P<name>[a-z]{4})(?P=name)', 'ggwpggwp')

print(res['name'])       # ggwp 1)Получаем группу по имени
print(res.group('name')) # ggwp 2)Получаем эту же группу по имени с помощью метода group
print(res[1])            # ggwp 3)Получаем эту же группу по номеру
print(res.group(1))      # ggwp 4)Получаем эту же группу по номеру с помощью метода group

Обратите внимание, что ссылки на группы не считаются группами, ведь это просто текст, который захватила указанная группа.

groups()

Метод groups возвращает кортеж со всеми группами, кроме нулевой:

import re

res = re.search(r'(?P<name>[a-z]{4})(?P=name)', 'ggwpggwp')
print(res.groups()) # ('ggwp',)
# В данном шаблоне только одна группа, в кортеже она соответственно тоже одна

groupdict()

Метод groupdict возвращает словарь, в котором ключи - имена групп, а значения - строки, которые захватила группа.

import re

res = re.search(r'(?P<name>[a-z]{4})([a-z]{4})', 'ggwpggwp')

print(res.groupdict()) # {'name': 'ggwp'}
# В словаре одна группа, хоть в шаблоне их 2
# Всё из-за того, что этот метод выводит только именованные группы

start(__group=0), end(__group=0)

Методы start и end возвращают индексы начала и конца совпадения группы, которая была передана в метод:

import re

res = re.search(r'(?P<name>[a-z]{4})([a-z]{4})', 'ggwpggwp')

print(res.start('name')) # 0
print(res.start(1))      # 0
print(res.start(2))      # 4

span(__group=0)

Метод span возвращает кортеж с индексом начала и конца подстроки группы, которая была передана в метод. Он работает аналогично методам start, end, но возвращает пару чисел:

import re

res = re.search(r'(?P<name>[a-z]{4})([a-z]{4})', 'ggwpggwp')

print(res.span('name')) # (0, 4)
print(res.span(1))      # (0, 4)
print(res.span(2))      # (4, 8)

Группы и re.findall

Если в регулярном выражении будут скобочные группы, то вместо списка со всеми соответствиями вернётся список с кортежами соответствующих групп.

import re

res = re.findall(r'(\d{2})-(\d{2})', '95-2459-234239-4923')
print(res) # [('95', '24'), ('59', '23'), ('39', '49')]

Группы и re.split

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

import re

res1 = re.split(r'\s[+*=]\s', '2 + 2 * 2 = 6')
# ['2', '2', '2', '6']
# Если в шаблоне нету групп, re.split работает так же, как и str.split

res2 = re.split(r'(\s)([+*=])(\s)', '2 + 2 * 2 = 6')
# ['2', ' ', '+', ' ', '2', ' ', '*', ' ', '2', ' ', '=', ' ', '6']
# Если использовать группы, то между каждыми разделёнными строками будут значения из групп

res3 = re.split(r'\s([+*=])\s', '2 + 2 * 2 = 6')
# ['2', '+', '2', '*', '2', '=', '6']
# Сначала не очень понятно, зачем использовать группы с re.split. 
# Но если убрать ненужные группы из второго примера, то всё становится ясно

Это может понадобиться, если нужно разделить строки, и оставить между ними разделитель.

Группы в re.sub и re.subn

Группы в re.sub и re.subn ничего не дают, но их можно использовать в заменах!

import re

string = "Ненавижу людей, которые пишут дату в формате mm/dd/yyyy. Ну кто пишет 02/22/2022 или 07/13/2022?"
print(re.sub(r'(\d{2}).(\d{2}).(\d{4})', r'\2.\1.\3', string))
# Ненавижу людей, которые пишут дату в формате mm/dd/yyyy. Ну кто пишет 22.02.2022 или 13.07.2022?

Функции в re.sub и re.subn

Вместо строки, на которую нужно заменить вхождение, в re.sub и re.subn можно передать функцию, которая будет генерировать ту самую строку. В функцию передаётся Match объект, и теперь мы можем получать доступ к группам, а также как-либо изменять и обрабатывать эти данные.

Например, нам нужно найти все слова и заменить их на их же длину. Давайте сделаем это с помощью функций!

import re


def func(m):
    return str(len(m[0]))


res_func = re.sub(r'[a-zA-Z]{1,}', func, 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.')
res_lambda = re.sub(r'[a-zA-Z]{1,}', lambda m: str(len(m[0])),
                    'Lorem Ipsum is simply dummy text of the printing and typesetting industry.')

print(res_func)  # 5 5 2 6 5 4 2 3 8 3 11 8.
print(res_func == res_lambda)  # True

В примере сверху в функциях я:

  • Получаю Match объект в функции.
  • Из него беру нулевую группу - т.е. всё, что захватило регулярное выражение.
  • Получаю её длину, конвертирую в строку, и возвращаю значение.

Можно использовать как и лямбда-функции, так и обычные.

re.compile

re.compile(pattern, flags=0) - метод, который позволяет вручную компилировать регулярные выражения.

Параметры:

  • pattern - регулярное выражение
  • flags - флаги, пройдём позже

Возвращаемое значение:

  • Скомпилированное регулярное выражение

Зачем нужен re.compile?

Каждый раз, когда вы используете регулярное выражение в каком-либо методе, оно автоматически компилируется. С помощью метода re.compile можно вручную скомпилировать регулярное выражение, и уже использовать его по назначению.

С помощью re.compile можно:

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

Если вы не используете много регулярных выражений - не стоит бояться что производительность упадёт, так как все использованные регулярные выражения кешируются, и им не приходится компилироваться во второй раз, пока не очистится кеш. Кеш кстати очищается с помощью метода re.purge(), но его нет смысла использовать, так как кеш чистится автоматически.

Примеры использования:

import re

regex = re.compile(r'[a-zA-Z]{1,}')
# Регулярное выражение скомпилировано

print(regex)  # re.compile('[a-zA-Z]{1,}')

# Теперь можно использовать методы:

print(regex.findall('Some words.'))  # ['Some', 'words']
print(regex.sub('deleted', 'Some words again.'))  # deleted deleted deleted.

Для чего нужны флаги?

Флаги нужны для изменения работы регулярных выражений. Всего существует 7 флагов (на самом деле 8), которые открывают нам доступ к новым свойствам регулярных выражений.

Как использовать флаги?

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

import re

test1 = re.findall('123', '123', flags=re.MULTILINE)  # 1 флаг
test2 = re.findall('123', '123', flags=re.MULTILINE + re.IGNORECASE)  # 2 флага
test3 = re.findall('123', '123', flags=re.MULTILINE + re.IGNORECASE + re.DOTALL)  # 3 флага

Если нужно использовать несколько флагов сразу - нужно сложить их вместе. Да, именно сложить.

Ну или написать между ними символ или:

import re

test1 = re.findall('123', '123', flags=re.MULTILINE)  # 1 флаг
test2 = re.findall('123', '123', flags=re.MULTILINE | re.IGNORECASE)  # 2 флага
test3 = re.findall('123', '123', flags=re.MULTILINE | re.IGNORECASE | re.DOTALL)  # 3 флага

Сокращённые версии

У флагов сущестуют сокращённые версии. Они позволяют сократить код в размере.

import re

test1 = re.findall('123', '123', flags=re.MULTILINE)  # 1 флаг
test2 = re.findall('123', '123', flags=re.MULTILINE + re.IGNORECASE)  # 2 флага
test3 = re.findall('123', '123', flags=re.MULTILINE + re.IGNORECASE + re.DOTALL)  # 3 флага

Если заменить флаги из примера с прошлого шага их сокращёнными версиями, то получим:

import re

test1 = re.findall('123', '123', flags=re.M)  # 1 флаг
test2 = re.findall('123', '123', flags=re.M + re.I)  # 2 флага
test3 = re.findall('123', '123', flags=re.M + re.I + re.S)  # 3 флага

Встроенные флаги

Также флаги можно указать в самом регулярном выражении. Достаточно просто поставить встроенный флаг перед регулярным выражением r”(?i)I like flags”:

Встроенные флаги принято ставить в начале выражения - это лучшая практика.

Если нужно использовать сразу несколько флагов - достаточно их перечислить: r”(?ims)I like flags”.

re.IGNORECASE

Зачем нужен:

При использовании флага регулярные выражения будут игнорировать регистр.

Полная версия:

re.IGNORECASE

Сокращённая версия:

re.I

Встроенный флаг:

(?i)

Примеры использования:

import re

string = 'I like flags I LIKE FLAGS i like flags'

test1 = re.findall(r'I like flags', string, flags=re.IGNORECASE)
test2 = re.findall(r'I like flags', string, flags=re.I)
test3 = re.findall(r'(?i)I like flags', string)

print(test1)  # ['I like flags', 'I LIKE FLAGS', 'i like flags']
print(test1 == test2 and test2 == test3)  # True

re.MULTILINE

Зачем нужен:

При использовании флага спецсимволы ^ и $ будут совпадать не с началом и концом всего текста, а с началом и концом строк. Это было разобрано тут.

Полная версия:

re.MULTILINE

Сокращённая версия:

re.M

Встроенный флаг:

(?m)

Примеры использования:

import re

string = '''
I like flags
I like flags
I like flags
'''

test1 = re.findall(r'^I like flags$', string, flags=re.MULTILINE)
test2 = re.findall(r'^I like flags$', string, flags=re.M)
test3 = re.findall(r'(?m)^I like flags$', string)

print(test1)  # ['I like flags', 'I like flags', 'I like flags']
print(test1 == test2 and test2 == test3)  # True

re.ASCII

Зачем нужен:

Шаблоны \w, \W, \b, \B, \d, \D, \s и \S будут выполнять только ASCII соответствие, вместо соответствия по умолчанию - соответствия по UNICODE .

Полная версия:

re.ASCII

Сокращённая версия:

re.A

Встроенный флаг:

(?a)

Примеры использования:

Можно использовать, если нужно искать символы из ASCII. Мне кажется легче указать нужные символы в квадратных скобках, чем урезать все UNICODE символы и переходить на ASCII. Не знаю, где это может понадобиться ¯_(ツ)_/¯

re.UNICODE

Зачем нужен:

Шаблоны \w, \W, \b, \B, \d, \D, \s и \S будут выполнять соответствие по UNICODE. Существует для обратной совместимости с re.ASCII, но он является излишеством, так как по умолчанию Python выполняет сопоставления в UNICODE.

Полная версия:

re.UNICODE

Сокращённая версия:

re.U

Встроенный флаг:

(?u)

Примеры использования:

Python по умолчанию выполняет сопоставления в UNICODE ¯_(ツ)_/¯

re.LOCALE

Зачем нужен:

Сопоставляет \w, \W, \b, \B без учета регистра, зависимо от текущей локали. Использование этого флага не рекомендуется, так как механизм локализации очень ненадежен и он работает только с 8-битными локалями.

Полная версия:

re.LOCALE

Сокращённая версия:

re.L

Встроенный флаг:

(?L)

Примеры использования:

Не нашёл применения для этого флага ¯_(ツ)_/¯

re.DOTALL

Зачем нужен:

Точка . теперь будет соответствовать любому символу. Если флаг не используется - точка соответствует любому символу, кроме символа новой строки.

Полная версия:

re.DOTALL

Сокращённая версия:

re.S

Встроенный флаг:

(?s)

Примеры использования:

import re

string = '''
I like flags
I like flags
I like flags
'''

test1 = re.findall(r'I like flags.', string, flags=re.DOTALL)
test2 = re.findall(r'I like flags.', string, flags=re.S)
test3 = re.findall(r'(?s)I like flags.', string)

print(test1)  # ['I like flags\n', 'I like flags\n', 'I like flags\n']
print(test1 == test2 and test2 == test3)  # True

re.VERBOSE

Зачем нужен:

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

Полная версия:

re.VERBOSE

Сокращённая версия:

re.X

Встроенный флаг:

(?x)

Примеры использования:

Можно писать такие регулярные выражения, и всё будет работать:

import re

test1 = re.findall(r"""[1-9] +  # Любая цифра, кроме 0
                   .            # Любой символ, кроме новой строки
                   \d {2,}      # Любая цифра""", '4G22', flags=re.VERBOSE)

test2 = re.findall(r"""[1-9] +
                   .
                   \d {2,}""", '4G22', flags=re.VERBOSE)

Использование сокращённых и встроенных флагов:

import re

test1 = re.findall(r"""[1-9] +
                   .
                   \d {2,}""", '4G22', flags=re.VERBOSE)

test2 = re.findall(r"""[1-9] +
                   .
                   \d {2,}""", '4G22', flags=re.X)

test3 = re.findall(r"""(?x)
                   [1-9] +
                   .
                   \d {2,}""", '4G22')

print(test3)  # ['4G22']
print(test1 == test2 and test2 == test3)  # True

re.DEBUG

Зачем нужен:

Показывает отладочную информацию о скомпилированном выражении. Используется только с re.compile.

Полная версия:

re.DEBUG

Сокращённая версия:

Нет

Встроенный флаг:

Нет

Примеры использования:

import re


regex = re.compile(r'I like flags', flags=re.DEBUG)

'''
Выводит следующую информацию:

LITERAL 73
LITERAL 32
LITERAL 108
LITERAL 105
LITERAL 107
LITERAL 101
LITERAL 32
LITERAL 102
LITERAL 108
LITERAL 97
LITERAL 103
LITERAL 115

 0. INFO 30 0b11 12 12 (to 31)
      prefix_skip 12
      prefix [0x49, 0x20, 0x6c, 0x69, 0x6b, 0x65, 0x20, 0x66, 0x6c, 0x61, 0x67, 0x73] ('I like flags')
      overlap [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
31: LITERAL 0x49 ('I')
33. LITERAL 0x20 (' ')
35. LITERAL 0x6c ('l')
37. LITERAL 0x69 ('i')
39. LITERAL 0x6b ('k')
41. LITERAL 0x65 ('e')
43. LITERAL 0x20 (' ')
45. LITERAL 0x66 ('f')
47. LITERAL 0x6c ('l')
49. LITERAL 0x61 ('a')
51. LITERAL 0x67 ('g')
53. LITERAL 0x73 ('s')
55. SUCCESS
'''
⤧  Previous post Javascript Cheatsheet ⤧  Next post Мануал по работе с Docker