Регулярные выражения позволяют найти в тексте все подстроки, которые подходят под заданный вами шаблон. Вообще, «регулярными выражениями» их называют ошибочно, правильный перевод – «шаблонные выражения», что лучше передает суть. В Python регулярные выражения поддерживаются «из коробки», а специальный объект Match, возвращаемый при наличии совпадений, позволяет быстро получить к ним доступ. Ниже – о том, как регулярные выражения работают вообще, о том, как они работают в Python, и о том, как правильно составлять регулярки.
Инструменты регулярных выражений
Инструменты 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)
re.split(шаблон, строка, maxsplit=0, flags=0)
Работает так же, как split для строк – берет шаблон, разбивает исходную строку на подстроки, используя шаблон в качестве разделителя, и возвращает список этих подстрок. Параметром maxsplit можно задать максимальное количество разделений. Если шаблон не найден – возвращает исходную строку.
import re
text = "Words, words, words."
pattern = r'\W+'
result = re.split(pattern, text)
print(result)
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)
sub с группой:
import re
text = "The rain in Spain"
result = re.sub(r'\b(S\w+)', r'X', text)
print(result)
sub с именованной группой:
import re
text = "The rain in Spain"
result = re.sub(r'\b(?P<word>S\w+)', r'X', text)
print(result)
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)
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("Совпадений не найдено")
Остальные методы принимают те же аргументы и делают почти то же самое:
- fullmatch() возвращает Match только в том случае, если шаблон совпал со всей строкой.
- findall() ищет все совпадения и возвращает кортеж.
- finditer() ищет все совпадения и возвращает итератор.
Объект Match
Многие функции пакета re возвращают объект Match, внутри которого хранятся данные о совпадении. У класса Match – много методов, с полным списком вы можете ознакомиться здесь. Основные методы и атрибуты, которые пригодятся вам в работе:
- .group() – возвращает соответствия по группам.
- .start() – возвращает индекс первого элемента в совпавшей под-строке.
- .end() – возвращает индекс элемента, следующего за последним в совпавшей под-строке.
- .span() – возвращает в кортеже .start() и .end().
- .re – поле, в котором хранится шаблон регулярки.
- .string – поле, в котором хранится исходная строка.
Как правильно составлять шаблоны?
Увы, нет какого-то единого мнения о том, как именно нужно составлять регулярные выражения в Python или другом языке – каждый делает так, как считает нужным. И это – большая проблема, потому что регулярки часто называют «write only» – если вам дадут задачу дополнить чье-то сложное регулярное выражение, вы через 5 минут попыток понять, что там происходит, плюнете, все сотрете и напишете заново. Вряд-ли у кого-либо когда-либо получится полностью справиться с этой проблемой, но вы можете облегчить жизнь и себе, и остальным, если будете соблюдать ряд рекомендаций:
- Чем проще – тем лучше. Вот вам известный пример «полной валидации адреса почты через регулярное выражение» – очень интересно, но ничего не понятно. На практике проще написать регулярку, проверяющую наличие @ и домена в почте, и проводить валидацию по уникальной ссылке в письме – если пользователь по ней кликнул, значит почта работает.
- Создавайте регулярку по шагам. Начните с небольших элементов, после чего объединяйте их в более крупные – так вы не утонете в сложности.
- Тестируйте. Есть отличный сервис, который позволяет протестировать регулярное выражение в Python или другом языке – пользуйтесь.
- Пишите комментарии. Оставьте где-нибудь рядом с регулярным выражением комментарии о том, как именно она работает и какие у нее особенности – это поможет и другим разработчикам, и вам, если придется возвращаться к регулярке через пару месяцев.
- Используйте raw string. Raw string в Python – это строка в формате «r’строка’», при ее использовании интерпретатор Python игнорирует управляющие символы строк. Если вы не используете raw string, Python может подкинуть вам «своих» багов в регулярку – например, правильным экранированием символа «\» в регулярном выражении будет «\\\\», потому что вам сначала нужно экранировать символ для regex, а затем экранировать экранирование для Python.
Вывод
Тезисно:
- Регулярные выражения позволяют найти подстроку (или несколько/все) в строке по шаблону.
- В Python все инструменты для работы с регулярными выражениями лежат в пакете «re».
- Основные методы, которыми вы будете пользоваться – search, findall, split, sub.
- При нахождении совпадения Python возвращает объект Match, в котором лежат детали о вхождении.
- Регулярки не могут решить все проблемы в жизни – оставляйте их простыми и ищите дополнительные способы валидации.