logo
Ещё

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

Регулярные выражения позволяют найти в тексте все подстроки, которые подходят под заданный вами шаблон. Вообще, «регулярными выражениями» их называют ошибочно, правильный перевод – «шаблонные выражения», что лучше передает суть. В Python регулярные выражения поддерживаются «из коробки», а специальный объект Match, возвращаемый при наличии совпадений, позволяет быстро получить к ним доступ. Ниже – о том, как регулярные выражения работают вообще, о том, как они работают в Python, и о том, как правильно составлять регулярки.

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

Для начала пройдемся по возможностях самих регулярных выражений. Проходиться будем вкратце, потому что мы предполагаем, что вы уже имеете хоть какое-то представление о регулярках. Итак:

  • Регулярное выражение – это шаблон. Обычно регулярка записывается в формате «/выражение/i», где «выражение» – это шаблон, «/» – слэши, обозначающие начало и конец регулярки, «i» – флаги, модифицирующие стандартное поведение компилятора (их указывать не обязательно, можно пользоваться дефолтными значениями).
  • Самое простое регулярное выражение – это набор конкретных символов. Если регулярное выражение выглядит как «/sravni/», то будут найдены все точные совпадения со «”sravni”» в тексте.
  • Некоторые символы в регулярных выражениях имеют особое значение. Их называют мета-символами. Самый известный – «.». Вы познакомитесь с ними в процессе изучения регулярных выражений.
  • Вместо конкретных символов регулярки позволяют указывать мета-последовательности. Если компилятор встречает такую последовательность – он ищет не конкретный символ, а любой символ, входящий в последовательность. «.», упомянутая выше – как раз из этой категории, «.» в шаблоне означает «любой символ, кроме символа конца строки». То есть регулярка «/sravni.ru/» найдет совпадение со «”sravni1ru”», например.
  • 3 частые мета-последовательности, не считая, «.», это: \d – любая цифра; \w – любая цифра, буква или «_»; \s – любой пробельный символ.
  • Если вам нужен обычный символ, а не мета-символ – экранируйте его. Чтобы «отключить» специальное значение символа, вам нужно поставить «\» перед ним, то есть «.» – любой символ, «\.» – обычная точка, «\\» – \.
  • Если вам нужен свой диапазон – используйте «[]». Квадратные скобки позволяют создавать собственные последовательности – совпадение засчитается, если на месте символа в тексте будет любой символ из диапазона. Можно либо перечислить символы, которые вам нужны, либо задать диапазон через тире: [a-d]. Например, «/sr[ao]vni/» найдет совпадения со «”sravni”» и «”srovni”».
  • Группы символов ищут с помощью круглых скобок. Самое простое, что позволяют делать группы – искать через логическое «или» (“|”) под-шаблоны одинаковой или разной длины: «/s(rav|o)ni/» найдет совпадения «”sravni”» и «”soni”».
  • Квантификаторы позволяют указать число раз, которое должна повториться последовательность. Число повторов можно указать как мета-символами, так и диапазоном. Мета-символы: * – любое число раз; + – 1 или более; ? – 0 или 1. Диапазон указывается в фигурных скобках: {3, 5}, {2,} или {5}. Например, «/\d{4}/» найдет все последовательности из 4 цифр.
  • Квантификаторы бывают жадными и нежадными. Жадные квантификаторы стараются захватить самую большую последовательность в рамках шаблона, нежадные – самую маленькую.
  • Найденные подстроки не пересекаются. Если хотите, чтобы они пересекались – нужно указать специальный флаг.
  • Совпадения можно искать с учетом окружающего контекста. \b позволяет указать на начало или конец слова, ^ и $ – на начало или конец строки, lookaround позволяет проверить, присутствует/отсутствует ли до/после совпадения дополнительный шаблон.

Инструменты Python для регулярных выражений

В Python все инструменты для работы с регулярками хранятся в пакете re – если хотите ими пользоваться, сначала нужно его импортировать:

import re

В re есть ряд встроенных методов, которые позволяют производить как основные операции (поиск последовательности), так и сопутствующие – работа с кэшем, компиляция регулярных выражений и так далее. Полную документацию как по методам, так и по всему пакету вы найдете здесь, мы же опишем основные методы, с которыми вам предстоит работать.

Заметка 1: если в сигнатуре функции параметр имеет «=0», значит, этот параметр – необязательный.

Заметка 2: все флаги можно посмотреть вот здесь.

re.findall(шаблон, строка, flags=0)

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

import re
text = "The rain in Spain stays mainly in the plain."
pattern = r'\b[Ss]\w+'
matches = re.findall(pattern, text)
print(matches)
# ['Spain', 'stays']

re.split(шаблон, строка, maxsplit=0, flags=0)

Работает так же, как split для строк – берет шаблон, разбивает исходную строку на подстроки, используя шаблон в качестве разделителя, и возвращает список этих подстрок. Параметром maxsplit можно задать максимальное количество разделений. Если шаблон не найден – возвращает исходную строку.

import re
text = "Words, words, words."
pattern = r'\W+'
result = re.split(pattern, text)
print(result)
# ['Words', 'words', 'words', '']

re.sub(шаблон, замена, строка, count=0, flags=0) и re.subn()

re.sub() получает шаблон, замену и строку, после чего ищет все вхождения шаблона в строке и заменяет их на «замену». Возвращает новую строку. count указывает на максимальное количество замен, по умолчанию – заменить все совпадения. re.sub() активно использует группы – вы можете указать группу в паттерне и ссылку на нее (\1, \2 и так далее), после чего под-строка из группы будет вставлена по ссылке. С именованными группами это работает так же – указываем имя группы, указываем имя ссылки. Наконец, в качестве замены можно передать функцию – в этом случае при каждом новом вхождении она будет вызываться, производить какие-то манипуляции и возвращать строку, на которую будет заменена исходная под-строка.

Простой пример sub:

import re
text = "The rain in Spain"
result = re.sub(r'\s', '-', text)
print(result)
# The-rain-in-Spain

sub с группой:

import re
text = "The rain in Spain"
result = re.sub(r'\b(S\w+)', r'X', text)
print(result)
# The rain in X

sub с именованной группой:

import re
text = "The rain in Spain"
result = re.sub(r'\b(?P<word>S\w+)', r'X', text)
print(result)
# The rain in X

sub с функцией:

import re
text = "The rain in Spain"
def replace(match):
return match.group(0).upper()
result = re.sub(r'\b(S\w+)', replace, text)
print(result)
# The rain in SPAIN

re.subn() работает так же и принимает те же аргументы, единственное отличие – он возвращает кортеж, состоящий из новой строки и количества замен.

re.search(шаблон, строка, flags=0), re.fullmatch(), re.finditer()

re.search() ищет совпадение с шаблоном в строке, возвращает None или объект Match, из которого можно получить информацию о совпадении.

import re
text = "The rain in Spain"
pattern = r'\b[Ss]\w+'
match = re.search(pattern, text)
if match:
print(f"Найдено совпадение: {match.group(0)}")
else:
print("Совпадений не найдено")
# Найдено совпадение: Spain

Остальные методы принимают те же аргументы и делают почти то же самое:

  • fullmatch() возвращает Match только в том случае, если шаблон совпал со всей строкой.
  • findall() ищет все совпадения и возвращает кортеж.
  • finditer() ищет все совпадения и возвращает итератор.

Объект Match

Многие функции пакета re возвращают объект Match, внутри которого хранятся данные о совпадении. У класса Match – много методов, с полным списком вы можете ознакомиться здесь. Основные методы и атрибуты, которые пригодятся вам в работе:

  • .group() – возвращает соответствия по группам.
  • .start() – возвращает индекс первого элемента в совпавшей под-строке.
  • .end() – возвращает индекс элемента, следующего за последним в совпавшей под-строке.
  • .span() – возвращает в кортеже .start() и .end().
  • .re – поле, в котором хранится шаблон регулярки.
  • .string – поле, в котором хранится исходная строка.

Как правильно составлять шаблоны?

Увы, нет какого-то единого мнения о том, как именно нужно составлять регулярные выражения в Python или другом языке – каждый делает так, как считает нужным. И это – большая проблема, потому что регулярки часто называют «write only» – если вам дадут задачу дополнить чье-то сложное регулярное выражение, вы через 5 минут попыток понять, что там происходит, плюнете, все сотрете и напишете заново. Вряд-ли у кого-либо когда-либо получится полностью справиться с этой проблемой, но вы можете облегчить жизнь и себе, и остальным, если будете соблюдать ряд рекомендаций:

  1. Чем проще – тем лучше. Вот вам известный пример «полной валидации адреса почты через регулярное выражение» – очень интересно, но ничего не понятно. На практике проще написать регулярку, проверяющую наличие @ и домена в почте, и проводить валидацию по уникальной ссылке в письме – если пользователь по ней кликнул, значит почта работает.
  2. Создавайте регулярку по шагам. Начните с небольших элементов, после чего объединяйте их в более крупные – так вы не утонете в сложности.
  3. Тестируйте. Есть отличный сервис, который позволяет протестировать регулярное выражение в Python или другом языке – пользуйтесь.
  4. Пишите комментарии. Оставьте где-нибудь рядом с регулярным выражением комментарии о том, как именно она работает и какие у нее особенности – это поможет и другим разработчикам, и вам, если придется возвращаться к регулярке через пару месяцев.
  5. Используйте raw string. Raw string в Python – это строка в формате «r’строка’», при ее использовании интерпретатор Python игнорирует управляющие символы строк. Если вы не используете raw string, Python может подкинуть вам «своих» багов в регулярку – например, правильным экранированием символа «\» в регулярном выражении будет «\\\\», потому что вам сначала нужно экранировать символ для regex, а затем экранировать экранирование для Python.

Вывод

Тезисно:

  • Регулярные выражения позволяют найти подстроку (или несколько/все) в строке по шаблону.
  • В Python все инструменты для работы с регулярными выражениями лежат в пакете «re».
  • Основные методы, которыми вы будете пользоваться – search, findall, split, sub.
  • При нахождении совпадения Python возвращает объект Match, в котором лежат детали о вхождении.
  • Регулярки не могут решить все проблемы в жизни – оставляйте их простыми и ищите дополнительные способы валидации.
Интересные предложения по программированию