Регулярные выражения в Python от простого к сложному
Решил я давеча моим школьникам дать задачек на регулярные выражения для изучения. А к задачкам нужна какая-нибудь теория. И стал я искать хорошие тексты на русском. Пяток сносных нашёл, но всё не то. Что-то смято, что-то упущено. У этих текстов был не только фатальный недостаток. Мало картинок, мало примеров. И почти нет разумных задач. Ну неужели поиск IP-адреса — это самая частая задача для регулярных выражений? Вот и я думаю, что нет.
Про разницу (?:…) / (…) фиг найдёшь, а без этого знания в некоторых случаях можно только страдать.
Плюс в питоне есть немало регулярных плюшек. Например, re.split
может добавлять тот кусок текста, по которому был разрез, в список частей. А в re.sub
можно вместо шаблона для замены передать функцию. Это — реальные вещи, которые прямо очень нужны, но никто про это не пишет.
Так и родился этот достаточно многобуквенный материал с подробностями, тонкостями, картинками и задачами.
Надеюсь, вам удастся из него извлечь что-нибудь новое и полезное, даже если вы уже в ладах с регулярками.
PS. Решения задач школьники сдают в тестирующую систему, поэтому задачи оформлены в несколько формальном виде.
Содержание
Регулярные выражения в Python от простого к сложному;
Содержание;
Примеры регулярных выражений;
Сила и ответственность;
Документация и ссылки;
Основы синтаксиса;
Шаблоны, соответствующие одному символу;
Квантификаторы (указание количества повторений);
Жадность в регулярках и границы найденного шаблона;
Пересечение подстрок;
Эксперименты в песочнице;
Регулярки в питоне;
Пример использования всех основных функций;
Тонкости экранирования в питоне (‘\\\\foo’);
Использование дополнительных флагов в питоне;
Написание и тестирование регулярных выражений;
Задачи — 1;
Скобочные группы (?:…) и перечисления |;
Перечисления (операция «ИЛИ»);
Скобочные группы (группировка плюс квантификаторы);
Скобки плюс перечисления;
Ещё примеры;
Задачи — 2;
Группирующие скобки (…) и match-объекты в питоне;
Match-объекты;
Группирующие скобки (…);
Тонкости со скобками и нумерацией групп.;
Группы и re.findall;
Группы и re.split;
Использование групп при заменах;
Замена с обработкой шаблона функцией в питоне;
Ссылки на группы при поиске;
Задачи — 3;
Шаблоны, соответствующие не конкретному тексту, а позиции;
Простые шаблоны, соответствующие позиции;
Сложные шаблоны, соответствующие позиции (lookaround и Co);
lookaround на примере королей и императоров Франции;
Задачи — 4;
Post scriptum;
Регулярное выражение — это строка, задающая шаблон поиска подстрок в тексте. Одному шаблону может соответствовать много разных строчек. Термин «Регулярные выражения» является переводом английского словосочетания «Regular expressions». Перевод не очень точно отражает смысл, правильнее было бы «шаблонные выражения». Регулярное выражение, или коротко «регулярка», состоит из обычных символов и специальных командных последовательностей. Например, d
задаёт любую цифру, а d+
— задает любую последовательность из одной или более цифр. Работа с регулярками реализована во всех современных языках программирования. Однако существует несколько «диалектов», поэтому функционал регулярных выражений может различаться от языка к языку. В некоторых языках программирования регулярками пользоваться очень удобно (например, в питоне), в некоторых — не слишком (например, в C++).
Примеры регулярных выражений
Сила и ответственность
Регулярные выражения, или коротко, регулярки — это очень мощный инструмент. Но использовать их следует с умом и осторожностью, и только там, где они действительно приносят пользу, а не вред. Во-первых, плохо написанные регулярные выражения работают медленно. Во-вторых, их зачастую очень сложно читать, особенно если регулярка написана не лично тобой пять минут назад. В-третьих, очень часто даже небольшое изменение задачи (того, что требуется найти) приводит к значительному изменению выражения. Поэтому про регулярки часто говорят, что это write only code (код, который только пишут с нуля, но не читают и не правят). А также шутят: Некоторые люди, когда сталкиваются с проблемой, думают «Я знаю, я решу её с помощью регулярных выражений.» Теперь у них две проблемы. Вот пример write-only регулярки (для проверки валидности e-mail адреса (не надо так делать!!!)):
(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[x01-x08x0bx0cx0e-x1fx21x23-x5bx5d-x7f]|\[x01-x09x0bx0cx0e-x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|[(?:(?:25[0-5]|
2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[x01-x08x0bx0cx0e-x1fx21-x5ax53-x7f]|\[x01-x09x0bx0cx0e-x7f])+)])
А вот здесь более точная регулярка для проверки корректности email адреса стандарту RFC822. Если вдруг будете проверять email, то не делайте так!Если адрес вводит пользователь, то пусть вводит почти что угодно, лишь бы там была собака. Надёжнее всего отправить туда письмо и убедиться, что пользователь может его получить.
Документация и ссылки
- Оригинальная документация: https://docs.python.org/3/library/re.html;
- Очень подробный и обстоятельный материал: https://www.regular-expressions.info/;
- Разные сложные трюки и тонкости с примерами: http://www.rexegg.com/;
- Он-лайн отладка регулярок https://regex101.com (не забудьте поставить галочку Python в разделе FLAVOR слева);
- Он-лайн визуализация регулярок https://www.debuggex.com/ (не забудьте выбрать Python);
- Могущественный текстовый редактор Sublime text 3, в котором очень удобный поиск по регуляркам;
Основы синтаксиса
Любая строка (в которой нет символов .^$*+?{}[]|()
) сама по себе является регулярным выражением. Так, выражению Хаха
будет соответствовать строка “Хаха” и только она. Регулярные выражения являются регистрозависимыми, поэтому строка “хаха” (с маленькой буквы) уже не будет соответствовать выражению выше. Подобно строкам в языке Python, регулярные выражения имеют спецсимволы .^$*+?{}[]|()
, которые в регулярках являются управляющими конструкциями. Для написания их просто как символов требуется их экранировать, для чего нужно поставить перед ними знак . Так же, как и в питоне, в регулярных выражениях выражение
n
соответствует концу строки, а t
— табуляции.
Шаблоны, соответствующие одному символу
Во всех примерах ниже соответствия регулярному выражению выделяются бирюзовым цветом с подчёркиванием.
Квантификаторы (указание количества повторений)
Жадность в регулярках и границы найденного шаблона
Как указано выше, по умолчанию квантификаторы жадные. Этот подход решает очень важную проблему — проблему границы шаблона. Скажем, шаблон d+
захватывает максимально возможное количество цифр. Поэтому можно быть уверенным, что перед найденным шаблоном идёт не цифра, и после идёт не цифра. Однако если в шаблоне есть не жадные части (например, явный текст), то подстрока может быть найдена неудачно. Например, если мы хотим найти «слова», начинающиеся на СУ
, после которой идут цифры, при помощи регулярки СУd*
, то мы найдём и неправильные шаблоны:
ПАСУ13 СУ12, ЧТОБЫ СУ6ЕНИЕ УДАЛОСЬ.
В тех случаях, когда это важно, условие на границу шаблона нужно обязательно добавлять в регулярку. О том, как это можно делать, будет дальше.
Пересечение подстрок
В обычной ситуации регулярки позволяют найти только непересекающиеся шаблоны. Вместе с проблемой границы слова это делает их использование в некоторых случаях более сложным. Например, если мы решим искать e-mail адреса при помощи неправильной регулярки w+@w+
(или даже лучше, [w'._+-]+@[w'._+-]+
), то в неудачном случае найдём вот что:
foo@boo@goo@moo@roo@zoo
То есть это с одной стороны и не e-mail, а с другой стороны это не все подстроки вида текст-собака-текст
, так как boo@goo и moo@roo пропущены.
Эксперименты в песочнице
Если вы впервые сталкиваетесь с регулярными выражениями, то лучше всего сначала попробовать песочницу. Посмотрите, как работают простые шаблоны и квантификаторы. Решите следующие задачи для этого текста (возможно, к части придётся вернуться после следующей теории):
- Найдите все натуральные числа (возможно, окружённые буквами);
- Найдите все «слова», написанные капсом (то есть строго заглавными), возможно внутри настоящих слов (аааБББввв);
- Найдите слова, в которых есть русская буква, а когда-нибудь за ней цифра;
- Найдите все слова, начинающиеся с русской или латинской большой буквы (
b
— граница слова); - Найдите слова, которые начинаются на гласную (
b
— граница слова);; - Найдите все натуральные числа, не находящиеся внутри или на границе слова;
- Найдите строчки, в которых есть символ
*
(.
— это точно не конец строки!); - Найдите строчки, в которых есть открывающая и когда-нибудь потом закрывающая скобки;
- Выделите одним махом весь кусок оглавления (в конце примера, вместе с тегами);
- Выделите одним махом только текстовую часть оглавления, без тегов;
- Найдите пустые строчки;
Регулярки в питоне
Функции для работы с регулярками живут в модуле re
. Основные функции:
Пример использования всех основных функций
import re
match = re.search(r'ddDdd', r'Телефон 123-12-12')
print(match[0] if match else 'Not found')
# -> 23-12
match = re.search(r'ddDdd', r'Телефон 1231212')
print(match[0] if match else 'Not found')
# -> Not found
match = re.fullmatch(r'ddDdd', r'12-12')
print('YES' if match else 'NO')
# -> YES
match = re.fullmatch(r'ddDdd', r'Т. 12-12')
print('YES' if match else 'NO')
# -> NO
print(re.split(r'W+', 'Где, скажите мне, мои очки??!'))
# -> ['Где', 'скажите', 'мне', 'мои', 'очки', '']
print(re.findall(r'dd.dd.d{4}',
r'Эта строка написана 19.01.2018, а могла бы и 01.09.2017'))
# -> ['19.01.2018', '01.09.2017']
for m in re.finditer(r'dd.dd.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'dd.dd.d{4}',
r'DD.MM.YYYY',
r'Эта строка написана 19.01.2018, а могла бы и 01.09.2017'))
# -> Эта строка написана DD.MM.YYYY, а могла бы и DD.MM.YYYY
Тонкости экранирования в питоне ('\\\\foo'
)
Так как символ в питоновских строках также необходимо экранировать, то в результате в шаблонах могут возникать конструкции вида
'\\par'
. Первый слеш означает, что следующий за ним символ нужно оставить «как есть». Третий также. В результате с точки зрения питона '\\'
означает просто два слеша \
. Теперь с точки зрения движка регулярных выражений, первый слеш экранирует второй. Тем самым как шаблон для регулярки '\\par'
означает просто текст par
. Для того, чтобы не было таких нагромождений слешей, перед открывающей кавычкой нужно поставить символ r
, что скажет питону «не рассматривай как экранирующий символ (кроме случаев экранирования открывающей кавычки)». Соответственно можно будет писать r'\par'
.
Использование дополнительных флагов в питоне
Каждой из функций, перечисленных выше, можно дать дополнительный параметр flags
, что несколько изменит режим работы регулярок. В качестве значения нужно передать сумму выбранных констант, вот они:
import re
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']
Написание и тестирование регулярных выражений
Для написания и тестирования регулярных выражений удобно использовать сервис https://regex101.com (не забудьте поставить галочку Python в разделе FLAVOR слева) или текстовый редактор Sublime text 3.
Задачи — 1
Задача 01. Регистрационные знаки транспортных средств
В России применяются регистрационные знаки нескольких видов.
Общего в них то, что они состоят из цифр и букв. Причём используются только 12 букв кириллицы, имеющие графические аналоги в латинском алфавите — А, В, Е, К, М, Н, О, Р, С, Т, У и Х.
У частных легковых автомобилях номера — это буква, три цифры, две буквы, затем две или три цифры с кодом региона. У такси — две буквы, три цифры, затем две или три цифры с кодом региона. Есть также и другие виды, но в этой задаче они не понадобятся.
Вам потребуется определить, является ли последовательность букв корректным номером указанных двух типов, и если является, то каким.
На вход даются строки, которые претендуют на то, чтобы быть номером. Определите тип номера. Буквы в номерах — заглавные русские. Маленькие и английские для простоты можно игнорировать.
Задача 02. Количество слов
Слово — это последовательность из букв (русских или английских), внутри которой могут быть дефисы.
На вход даётся текст, посчитайте, сколько в нём слов.
PS. Задача решается в одну строчку. Никакие хитрые техники, не упомянутые выше, не требуются.
Задача 03. Поиск e-mailов
Допустимый формат e-mail адреса регулируется стандартом RFC 5322.
Если говорить вкратце, то e-mail состоит из одного символа @
(at-символ или собака), текста до собаки (Local-part) и текста после собаки (Domain part). Вообще в адресе может быть всякий беспредел (вкратце можно прочитать о нём в википедии). Довольно странные штуки могут быть валидным адресом, например:
"very.(),:;<>[]".VERY."very@\ "very".unusual"@[IPv6:2001:db8::1]
"()<>[]:,;@\"!#$%&'-/=?^_`{}| ~.a"@(comment)exa-mple
Но большинство почтовых сервисов такой ад и вакханалию не допускают. И мы тоже не будем
Будем рассматривать только адреса, имя которых состоит из не более, чем 64 латинских букв, цифр и символов '._+-
, а домен — из не более, чем 255 латинских букв, цифр и символов .-
. Ни Local-part, ни Domain part не может начинаться или заканчиваться на .+-
, а ещё в адресе не может быть более одной точки подряд.
Кстати, полезно знать, что часть имени после символа +
игнорируется, поэтому можно использовать синонимы своего адреса (например, shаshkоv+spam@179.ru
и shаshkоv+vk@179.ru
), для того, чтобы упростить себе сортировку почты. (Правда не все сайты позволяют использовать «+», увы)
На вход даётся текст. Необходимо вывести все e-mail адреса, которые в нём встречаются. В общем виде задача достаточно сложная, поэтому у нас будет 3 ограничения:
две точки внутри адреса не встречаются;
две собаки внутри адреса не встречаются;
считаем, что e-mail может быть частью «слова», то есть в boo@ya_ru
мы видим адрес boo@ya
, а в foo№boo@ya.ru
видим boo@ya.ru
.
PS. Совсем не обязательно делать все проверки только регулярками. Регулярные выражения — это просто инструмент, который делает часть задач простыми. Не нужно делать их назад сложными
Скобочные группы (?:...)
и перечисления |
Перечисления (операция «ИЛИ»)
Чтобы проверить, удовлетворяет ли строка хотя бы одному из шаблонов, можно воспользоваться аналогом оператора or
, который записывается с помощью символа |
. Так, некоторая строка подходит к регулярному выражению A|B
тогда и только тогда, когда она подходит хотя бы к одному из регулярных выражений A
или B
. Например, отдельные овощи в тексте можно искать при помощи шаблона морковк|св[её]кл|картошк|редиск
.
Скобочные группы (группировка плюс квантификаторы)
Зачастую шаблон состоит из нескольких повторяющихся групп. Так, MAC-адрес сетевого устройства обычно записывается как шесть групп из двух шестнадцатиричных цифр, разделённых символами -
или :
. Например, 01:23:45:67:89:ab
. Каждый отдельный символ можно задать как [0-9a-fA-F]
, и можно весь шаблон записать так:
[0-9a-fA-F]{2}[:-][0-9a-fA-F]{2}[:-][0-9a-fA-F]{2}[:-][0-9a-fA-F]{2}[:-][0-9a-fA-F]{2}[:-][0-9a-fA-F]{2}
Ситуация становится гораздо сложнее, когда количество групп заранее не зафиксировано.
Чтобы разрешить эту проблему в синтаксисе регулярных выражений есть группировка (?:...)
. Можно писать круглые скобки и без значков ?:
, однако от этого у группировки значительно меняется смысл, регулярка начинает работать гораздо медленнее. Об этом будет написано ниже. Итак, если REGEXP
— шаблон, то (?:REGEXP)
— эквивалентный ему шаблон. Разница только в том, что теперь к (?:REGEXP)
можно применять квантификаторы, указывая, сколько именно раз должна повториться группа. Например, шаблон для поиска MAC-адреса, можно записать так:
[0-9a-fA-F]{2}(?:[:-][0-9a-fA-F]{2}){5}
Скобки плюс перечисления
Также скобки (?:...)
позволяют локализовать часть шаблона, внутри которого происходит перечисление. Например, шаблон (?:он|тот) (?:шёл|плыл)
соответствует каждой из строк «он шёл», «он плыл», «тот шёл», «тот плыл», и является синонимом он шёл|он плыл|тот шёл|тот плыл
.
Ещё примеры
Задачи — 2
Задача 04. Замена времени
Вовочка подготовил одно очень важное письмо, но везде указал неправильное время.
Поэтому нужно заменить все вхождения времени на строку (TBD)
. Время — это строка вида HH:MM:SS
или HH:MM
, в которой HH
— число от 00 до 23, а MM
и SS
— число от 00 до 59.
Задача 05. Действительные числа в паскале
Pascal requires that real constants have either a decimal point, or an exponent (starting with the letter e or E, and officially called a scale factor), or both, in addition to the usual collection of decimal digits. If a decimal point is included it must have at least one decimal digit on each side of it. As expected, a sign (+ or -) may precede the entire number, or the exponent, or both. Exponents may not include fractional digits. Blanks may precede or follow the real constant, but they may not be embedded within it. Note that the Pascal syntax rules for real constants make no assumptions about the range of real values, and neither does this problem. Your task in this problem is to identify legal Pascal real constants.
Задача 06. Аббревиатуры
Владимир устроился на работу в одно очень важное место. И в первом же документе он ничего не понял,
там были сплошные ФГУП НИЦ ГИДГЕО, ФГОУ ЧШУ АПК и т.п. Тогда он решил собрать все аббревиатуры, чтобы потом найти их расшифровки на http://sokr.ru/. Помогите ему.
Будем считать аббревиатурой слова только лишь из заглавных букв (как минимум из двух). Если несколько таких слов разделены пробелами, то они
считаются одной аббревиатурой.
Группирующие скобки (...)
и match
-объекты в питоне
Match-объекты
Если функции re.search
, re.fullmatch
не находят соответствие шаблону в строке, то они возвращают None
, функция re.finditer
не выдаёт ничего. Однако если соответствие найдено, то возвращается match
-объект. Эта штука содержит в себе кучу полезной информации о соответствии шаблону. Полный набор атрибутов можно посмотреть в документации, а здесь приведём самое полезное.
Группирующие скобки (...)
Если в шаблоне регулярного выражения встречаются скобки (...)
без ?:
, то они становятся группирующими. В 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)} до {match.end(0)}')
print(f'Группа букв >{match[1]}< с позиции {match.start(1)} до {match.end(1)}')
print(f'Группа цифр >{match[2]}< с позиции {match.start(2)} до {match.end(2)}')
###
# -> Найдена подстрока > Опять45 < с позиции 3 до 16
# -> Группа букв >Опять< с позиции 6 до 11
# -> Группа цифр >45< с позиции 11 до 13
Тонкости со скобками и нумерацией групп.
Если к группирующим скобкам применён квантификатор (то есть указано число повторений), то подгруппа в match-объекте будет создана только для последнего соответствия. Например, если бы в примере выше квантификаторы были снаружи от скобок '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)} до {match.end(0)}')
for i in range(1, 7):
print(f'Группа №{i} >{match[i]}< с позиции {match.start(i)} до {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.findall
Если в шаблоне есть группирующие скобки, то вместо списка найденных подстрок будет возвращён список кортежей, в каждом из которых только соответствие каждой группе. Это не всегда происходит по плану, поэтому обычно нужно использовать негруппирующие скобки (?:...)
.
import re
print(re.findall(r'([a-z]+)(d*)', r'foo3, im12, go, 24buz42'))
# -> [('foo', '3'), ('im', '12'), ('go', ''), ('buz', '42')]
Группы и re.split
Если в шаблоне нет группирующих скобок, то re.split
работает очень похожим образом на str.split
. А вот если группирующие скобки в шаблоне есть, то между каждыми разрезанными строками будут все соответствия каждой из подгрупп.
import re
print(re.split(r'(s*)([+*/-])(s*)', r'12 + 13*15 - 6'))
# -> ['12', ' ', '+', ' ', '13', '', '*', '', '15', ' ', '-', ' ', '6']
В некоторых ситуация эта возможность бывает чрезвычайно удобна! Например, достаточно из предыдущего примера убрать лишние группы, и польза сразу станет очевидна!
import re
print(re.split(r's*([+*/-])s*', r'12 + 13*15 - 6'))
# -> ['12', '+', '13', '*', '15', '-', '6']
Использование групп при заменах
Использование групп добавляет замене (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'(dd)/(dd)/(d{4})', r'2.1.3', text))
# -> We arrive on 25.03.2018. So you are welcome after 01.04.2018.
Если групп больше 9, то можно ссылаться на них при помощи конструкции вида g<12>
.
Замена с обработкой шаблона функцией в питоне
Ещё одна питоновская фича для регулярных выражений: в функции re.sub
вместо текста для замены можно передать функцию, которая будет получать на вход match-объект и должна возвращать строку, на которую и будет произведена замена. Это позволяет не писать ад в шаблоне для замены, а использовать удобную функцию. Например, «зацензурим» все слова, начинающиеся на букву «Х»:
import re
def repl(m):
return '>censored(' + str(len(m[0])) + ')<'
text = "Некоторые хорошие слова подозрительны: хор, хоровод, хороводоводовед."
print(re.sub(r'b[хХxX]w*', repl, text))
# -> Некоторые >censored(7)< слова подозрительны: >censored(3)<, >censored(7)<, >censored(15)<.
Ссылки на группы при поиске
При помощи 1, 2, 3, ...
и g<12>
можно ссылаться на найденную группу и при поиске. Необходимость в этом встречается довольно редко, но это бывает полезно при обработке простых xml и html.
Только пообещайте, что не будете парсить сложный xml и тем более html при помощи регулярок! Регулярные выражения для этого не подходят. Используйте другие инструменты. Каждый раз, когда неопытный программист парсит html регулярками, в мире умирает котёнок. Если кажется «Да здесь очень простой html, напишу регулярку», то сразу вспоминайте шутку про две проблемы. Не нужно пытаться парсить html регулярками, даже Пётр Митричев не сможет это сделать в общем случае Использование регулярных выражений при парсинге html подобно залатыванию резиновой лодки шилом. Закон Мёрфи для парсинга html и xml при помощи регулярок гласит: парсинг html и xml регулярками иногда работает, но в точности до того момента, когда правильность результата будет очень важна.
Используйте lxml и beautiful soup.
import re
text = "SPAM <foo>Here we can <boo>find</boo> something interesting</foo> SPAM"
print(re.search(r'<(w+?)>.*?</1>', text)[0])
# -> <foo>Here we can <boo>find</boo> something interesting</foo>
text = "SPAM <foo>Here we can <foo>find</foo> OH, NO MATCH HERE!</foo> SPAM"
print(re.search(r'<(w+?)>.*?</1>', text)[0])
# -> <foo>Here we can <foo>find</foo>
Задачи — 3
Задача 07. Шифровка
Владимиру потребовалось срочно запутать финансовую документацию. Но так, чтобы это было обратимо.
Он не придумал ничего лучше, чем заменить каждое целое число (последовательность цифр) на его куб. Помогите ему.
Задача 08. То ли акростих, то ли акроним, то ли апроним
Акростих — осмысленный текст, сложенный из начальных букв каждой строки стихотворения.
Акроним — вид аббревиатуры, образованной начальными звуками (напр. НАТО, вуз, НАСА, ТАСС), которое можно произнести слитно (в отличие от аббревиатуры, которую произносят «по буквам», например: КГБ — «ка-гэ-бэ»).
На вход даётся текст. Выведите слитно первые буквы каждого слова. Буквы необходимо выводить заглавными.
Эту задачу можно решить в одну строчку.
Задача 09. Хайку
Хайку — жанр традиционной японской лирической поэзии века, известный с XIV века.
Оригинальное японское хайку состоит из 17 слогов, составляющих один столбец иероглифов. Особыми разделительными словами — кирэдзи — текст хайку делится на части из 5, 7 и снова 5 слогов. При переводе хайку на западные языки традиционно вместо разделительного слова использую разрыв строки и, таким образом, хайку записываются как трёхстишия.
Перед вами трёхстишия, которые претендуют на то, чтобы быть хайку. В качестве разделителя строк используются символы /
. Если разделители делят текст на строки, в которых 5/7/5 слогов, то выведите «Хайку!». Если число строк не равно 3, то выведите строку «Не хайку. Должно быть 3 строки.» Иначе выведите строку вида «Не хайку. В i строке слогов не s, а j.», где строка i
— самая ранняя, в которой количество слогов неправильное.
Для простоты будем считать, что слогов ровно столько же, сколько гласных, не задумываясь о тонкостях.
Шаблоны, соответствующие не конкретному тексту, а позиции
Отдельные части регулярного выражения могут соответствовать не части текста, а позиции в этом тексте. То есть такому шаблону соответствует не подстрока, а некоторая позиция в тексте, как бы «между» буквами.
Простые шаблоны, соответствующие позиции
Для определённости строку, в которой мы ищем шаблон будем называть всем текстом.Каждую строчку всего текста (то есть каждый максимальный кусок без символов конца строки) будем называть строчкой текста.
Сложные шаблоны, соответствующие позиции (lookaround и Co)
Следующие шаблоны применяются в основном в тех случаях, когда нужно уточнить, что должно идти непосредственно перед или после шаблона, но при этом
не включать найденное в match-объект.
На всякий случай ещё раз. Каждый их этих шаблонов проверяет лишь то, что идёт непосредственно перед позицией или непосредственно после позиции. Если пару таких шаблонов написать рядом, то проверки будут независимы (то есть будут соответствовать AND в каком-то смысле).
lookaround на примере королей и императоров Франции
Людовик(?=VI)
— Людовик, за которым идёт VI
КарлIV, КарлIX, КарлV, КарлVI, КарлVII, КарлVIII,
ЛюдовикIX, ЛюдовикVI, ЛюдовикVII, ЛюдовикVIII, ЛюдовикX, …, ЛюдовикXVIII,
ФилиппI, ФилиппII, ФилиппIII, ФилиппIV, ФилиппV, ФилиппVI
Людовик(?!VI)
— Людовик, за которым идёт не VI
КарлIV, КарлIX, КарлV, КарлVI, КарлVII, КарлVIII,
ЛюдовикIX, ЛюдовикVI, ЛюдовикVII, ЛюдовикVIII, ЛюдовикX, …, ЛюдовикXVIII,
ФилиппI, ФилиппII, ФилиппIII, ФилиппIV, ФилиппV, ФилиппVI
(?<=Людовик)VI
— «шестой», но только если Людовик
КарлIV, КарлIX, КарлV, КарлVI, КарлVII, КарлVIII,
ЛюдовикIX, ЛюдовикVI, ЛюдовикVII, ЛюдовикVIII, ЛюдовикX, …, ЛюдовикXVIII,
ФилиппI, ФилиппII, ФилиппIII, ФилиппIV, ФилиппV, ФилиппVI
(?<!Людовик)VI
— «шестой», но только если не Людовик
КарлIV, КарлIX, КарлV, КарлVI, КарлVII, КарлVIII,
ЛюдовикIX, ЛюдовикVI, ЛюдовикVII, ЛюдовикVIII, ЛюдовикX, …, ЛюдовикXVIII,
ФилиппI, ФилиппII, ФилиппIII, ФилиппIV, ФилиппV, ФилиппVI
Прочие фичи
Конечно, здесь описано не всё, что умеют регулярные выражения, и даже не всё, что умеют регулярные выражения в питоне. За дальнейшим можно обращаться к этому разделу. Из полезного за кадром осталась компиляция регулярок для ускорения многократного использования одного шаблона, использование именных групп и разные хитрые трюки.
А уж какие извращения можно делать с регулярными выражениями в языке Perl — поручик Ржевский просто отдыхает
Задачи — 4
Задача 10. CamelCase -> under_score
Владимир написал свой открытый проект, именуя переменные в стиле «ВерблюжийРегистр».
И только после того, как написал о нём статью, он узнал, что в питоне для имён переменных принято использовать подчёркивания для разделения слов (under_score). Нужно срочно всё исправить, пока его не «закидали тапками».
Задача могла бы оказаться достаточно сложной, но, к счастью, Владимир совсем не использовал строковых констант и классов.
Поэтому любая последовательность букв и цифр, внутри которой есть заглавные, — это имя переменной, которое нужно поправить.
Задача 11. Удаление повторов
Довольно распространённая ошибка ошибка — это повтор слова.
Вот в предыдущем предложении такая допущена. Необходимо исправить каждый такой повтор (слово, один или несколько пробельных символов, и снова то же слово).
Задача 12. Близкие слова
Для простоты будем считать словом любую последовательность букв, цифр и знаков _
(то есть символов w
).
Дан текст. Необходимо найти в нём любой фрагмент, где сначала идёт слово «олень», затем не более 5 слов, и после этого идёт слово «заяц».
Задача 13. Форматирование больших чисел
Большие целые числа удобно читать, когда цифры в них разделены на тройки запятыми.
Переформатируйте целые числа в тексте.
Задача 14. Разделить текст на предложения
Для простоты будем считать, что:
- каждое предложение начинается с заглавной русской или латинской буквы;
- каждое предложение заканчивается одним из знаков препинания
.;!?
; - между предложениями может быть любой непустой набор пробельных символов;
- внутри предложений нет заглавных и точек (нет пакостей в духе «Мы любим творчество А. С. Пушкина)».
Разделите текст на предложения так, чтобы каждое предложение занимало одну строку.
Пустых строк в выводе быть не должно. Любые наборы из полее одного пробельного символа замените на один пробел.
Задача 15. Форматирование номера телефона
Если вы когда-нибудь пытались собирать номера мобильных телефонов, то наверняка знаете, что почти любые 10 человек используют как минимум пяток различных способов записать номер телефона. Кто-то начинает с +7
, кто-то просто с 7
или 8
, а некоторые вообще не пишут префикс. Трёхзначный код кто-то отделяет пробелами, кто-то при помощи дефиса, кто-то скобками (и после скобки ещё пробел некоторые добавляют). После следующих трёх цифр кто-то ставит пробел, кто-то дефис, кто-то ничего не ставит. И после следующих двух цифр — тоже. А некоторые начинают за здравие, а заканчивают… В общем очень неудобно!
На вход даётся номер телефона, как его мог бы ввести человек. Необходимо его переформатировать в формат +7 123 456-78-90
. Если с номером что-то не так, то нужно вывести строчку Fail!
.
Задача 16. Поиск e-mail’ов — 2
В предыдущей задаче мы немного схалтурили.
Однако к этому моменту задача должна стать посильной!
На вход даётся текст. Необходимо вывести все e-mail адреса, которые в нём встречаются. При этом e-mail не может быть частью слова, то есть слева и справа от e-mail’а должен быть либо конец строки, либо не-буква и при этом не один из символов '._+-
, допустимых в адресе.
Post scriptum
PS. Текст длинный, в нём наверняка есть опечатки и ошибки. Пишите о них скорее в личку, я тут же исправлю.
PSS. Ух и намаялся я нормальный html в хабра-html перегонять. Кажется, парсер хабра писан на регулярках, иначе как объяснить все те странности, которые приходилось вылавливать бинпоиском?
Source code: Lib/re.py
This module provides regular expression matching operations similar to
those found in Perl.
Both patterns and strings to be searched can be Unicode strings (str
)
as well as 8-bit strings (bytes
).
However, Unicode strings and 8-bit strings cannot be mixed:
that is, you cannot match a Unicode string with a byte pattern or
vice-versa; similarly, when asking for a substitution, the replacement
string must be of the same type as both the pattern and the search string.
Regular expressions use the backslash character (''
) to indicate
special forms or to allow special characters to be used without invoking
their special meaning. This collides with Python’s usage of the same
character for the same purpose in string literals; for example, to match
a literal backslash, one might have to write '\\'
as the pattern
string, because the regular expression must be \
, and each
backslash must be expressed as \
inside a regular Python string
literal.
The solution is to use Python’s raw string notation for regular expression
patterns; backslashes are not handled in any special way in a string literal
prefixed with 'r'
. So r"n"
is a two-character string containing
''
and 'n'
, while "n"
is a one-character string containing a
newline. Usually patterns will be expressed in Python code using this raw
string notation.
It is important to note that most regular expression operations are available as
module-level functions and methods on
compiled regular expressions. The functions are shortcuts
that don’t require you to compile a regex object first, but miss some
fine-tuning parameters.
See also
The third-party regex module,
which has an API compatible with the standard library re
module,
but offers additional functionality and a more thorough Unicode support.
6.2.1. Regular Expression Syntax¶
A regular expression (or RE) specifies a set of strings that matches it; the
functions in this module let you check if a particular string matches a given
regular expression (or if a given regular expression matches a particular
string, which comes down to the same thing).
Regular expressions can be concatenated to form new regular expressions; if A
and B are both regular expressions, then AB is also a regular expression.
In general, if a string p matches A and another string q matches B, the
string pq will match AB. This holds unless A or B contain low precedence
operations; boundary conditions between A and B; or have numbered group
references. Thus, complex expressions can easily be constructed from simpler
primitive expressions like the ones described here. For details of the theory
and implementation of regular expressions, consult the Friedl book referenced
above, or almost any textbook about compiler construction.
A brief explanation of the format of regular expressions follows. For further
information and a gentler presentation, consult the Regular Expression HOWTO.
Regular expressions can contain both special and ordinary characters. Most
ordinary characters, like 'A'
, 'a'
, or '0'
, are the simplest regular
expressions; they simply match themselves. You can concatenate ordinary
characters, so last
matches the string 'last'
. (In the rest of this
section, we’ll write RE’s in this special style
, usually without quotes, and
strings to be matched 'in single quotes'
.)
Some characters, like '|'
or '('
, are special. Special
characters either stand for classes of ordinary characters, or affect
how the regular expressions around them are interpreted.
Repetition qualifiers (*
, +
, ?
, {m,n}
, etc) cannot be
directly nested. This avoids ambiguity with the non-greedy modifier suffix
?
, and with other modifiers in other implementations. To apply a second
repetition to an inner repetition, parentheses may be used. For example,
the expression (?:a{6})*
matches any multiple of six 'a'
characters.
The special characters are:
.
- (Dot.) In the default mode, this matches any character except a newline. If
theDOTALL
flag has been specified, this matches any character
including a newline. ^
- (Caret.) Matches the start of the string, and in
MULTILINE
mode also
matches immediately after each newline. $
- Matches the end of the string or just before the newline at the end of the
string, and inMULTILINE
mode also matches before a newline.foo
matches both ‘foo’ and ‘foobar’, while the regular expressionfoo$
matches
only ‘foo’. More interestingly, searching forfoo.$
in'foo1nfoo2n'
matches ‘foo2’ normally, but ‘foo1’ inMULTILINE
mode; searching for
a single$
in'foon'
will find two (empty) matches: one just before
the newline, and one at the end of the string. *
- Causes the resulting RE to match 0 or more repetitions of the preceding RE, as
many repetitions as are possible.ab*
will match ‘a’, ‘ab’, or ‘a’ followed
by any number of ‘b’s. +
- Causes the resulting RE to match 1 or more repetitions of the preceding RE.
ab+
will match ‘a’ followed by any non-zero number of ‘b’s; it will not
match just ‘a’. ?
- Causes the resulting RE to match 0 or 1 repetitions of the preceding RE.
ab?
will match either ‘a’ or ‘ab’. *?
,+?
,??
- The
'*'
,'+'
, and'?'
qualifiers are all greedy; they match
as much text as possible. Sometimes this behaviour isn’t desired; if the RE
<.*>
is matched against'<a> b <c>'
, it will match the entire
string, and not just'<a>'
. Adding?
after the qualifier makes it
perform the match in non-greedy or minimal fashion; as few
characters as possible will be matched. Using the RE<.*?>
will match
only'<a>'
. {m}
- Specifies that exactly m copies of the previous RE should be matched; fewer
matches cause the entire RE not to match. For example,a{6}
will match
exactly six'a'
characters, but not five. {m,n}
- Causes the resulting RE to match from m to n repetitions of the preceding
RE, attempting to match as many repetitions as possible. For example,
a{3,5}
will match from 3 to 5'a'
characters. Omitting m specifies a
lower bound of zero, and omitting n specifies an infinite upper bound. As an
example,a{4,}b
will match'aaaab'
or a thousand'a'
characters
followed by a'b'
, but not'aaab'
. The comma may not be omitted or the
modifier would be confused with the previously described form. {m,n}?
- Causes the resulting RE to match from m to n repetitions of the preceding
RE, attempting to match as few repetitions as possible. This is the
non-greedy version of the previous qualifier. For example, on the
6-character string'aaaaaa'
,a{3,5}
will match 5'a'
characters,
whilea{3,5}?
will only match 3 characters. -
Either escapes special characters (permitting you to match characters like
'*'
,'?'
, and so forth), or signals a special sequence; special
sequences are discussed below.If you’re not using a raw string to express the pattern, remember that Python
also uses the backslash as an escape sequence in string literals; if the escape
sequence isn’t recognized by Python’s parser, the backslash and subsequent
character are included in the resulting string. However, if Python would
recognize the resulting sequence, the backslash should be repeated twice. This
is complicated and hard to understand, so it’s highly recommended that you use
raw strings for all but the simplest expressions. []
-
Used to indicate a set of characters. In a set:
- Characters can be listed individually, e.g.
[amk]
will match'a'
,
'm'
, or'k'
. - Ranges of characters can be indicated by giving two characters and separating
them by a'-'
, for example[a-z]
will match any lowercase ASCII letter,
[0-5][0-9]
will match all the two-digits numbers from00
to59
, and
[0-9A-Fa-f]
will match any hexadecimal digit. If-
is escaped (e.g.
[a-z]
) or if it’s placed as the first or last character
(e.g.[-a]
or[a-]
), it will match a literal'-'
. - Special characters lose their special meaning inside sets. For example,
[(+*)]
will match any of the literal characters'('
,'+'
,
'*'
, or')'
. - Character classes such as
w
orS
(defined below) are also accepted
inside a set, although the characters they match depends on whether
ASCII
orLOCALE
mode is in force. - Characters that are not within a range can be matched by complementing
the set. If the first character of the set is'^'
, all the characters
that are not in the set will be matched. For example,[^5]
will match
any character except'5'
, and[^^]
will match any character except
'^'
.^
has no special meaning if it’s not the first character in
the set. - To match a literal
']'
inside a set, precede it with a backslash, or
place it at the beginning of the set. For example, both[()[]{}]
and
[]()[{}]
will both match a parenthesis.
- Characters can be listed individually, e.g.
|
A|B
, where A and B can be arbitrary REs, creates a regular expression that
will match either A or B. An arbitrary number of REs can be separated by the
'|'
in this way. This can be used inside groups (see below) as well. As
the target string is scanned, REs separated by'|'
are tried from left to
right. When one pattern completely matches, that branch is accepted. This means
that once A matches, B will not be tested further, even if it would
produce a longer overall match. In other words, the'|'
operator is never
greedy. To match a literal'|'
, use|
, or enclose it inside a
character class, as in[|]
.(...)
- Matches whatever regular expression is inside the parentheses, and indicates the
start and end of a group; the contents of a group can be retrieved after a match
has been performed, and can be matched later in the string with thenumber
special sequence, described below. To match the literals'('
or')'
,
use(
or)
, or enclose them inside a character class:[(]
,[)]
. (?...)
- This is an extension notation (a
'?'
following a'('
is not meaningful
otherwise). The first character after the'?'
determines what the meaning
and further syntax of the construct is. Extensions usually do not create a new
group;(?P<name>...)
is the only exception to this rule. Following are the
currently supported extensions. (?aiLmsux)
- (One or more letters from the set
'a'
,'i'
,'L'
,'m'
,
's'
,'u'
,'x'
.) The group matches the empty string; the
letters set the corresponding flags:re.A
(ASCII-only matching),
re.I
(ignore case),re.L
(locale dependent),
re.M
(multi-line),re.S
(dot matches all),
re.U
(Unicode matching), andre.X
(verbose),
for the entire regular expression.
(The flags are described in Module Contents.)
This is useful if you wish to include the flags as part of the
regular expression, instead of passing a flag argument to the
re.compile()
function. Flags should be used first in the
expression string. (?:...)
- A non-capturing version of regular parentheses. Matches whatever regular
expression is inside the parentheses, but the substring matched by the group
cannot be retrieved after performing a match or referenced later in the
pattern. (?aiLmsux-imsx:...)
-
(Zero or more letters from the set
'a'
,'i'
,'L'
,'m'
,
's'
,'u'
,'x'
, optionally followed by'-'
followed by
one or more letters from the'i'
,'m'
,'s'
,'x'
.)
The letters set or remove the corresponding flags:
re.A
(ASCII-only matching),re.I
(ignore case),
re.L
(locale dependent),re.M
(multi-line),
re.S
(dot matches all),re.U
(Unicode matching),
andre.X
(verbose), for the part of the expression.
(The flags are described in Module Contents.)The letters
'a'
,'L'
and'u'
are mutually exclusive when used
as inline flags, so they can’t be combined or follow'-'
. Instead,
when one of them appears in an inline group, it overrides the matching mode
in the enclosing group. In Unicode patterns(?a:...)
switches to
ASCII-only matching, and(?u:...)
switches to Unicode matching
(default). In byte pattern(?L:...)
switches to locale depending
matching, and(?a:...)
switches to ASCII-only matching (default).
This override is only in effect for the narrow inline group, and the
original matching mode is restored outside of the group.New in version 3.6.
Changed in version 3.7: The letters
'a'
,'L'
and'u'
also can be used in a group. (?P<name>...)
-
Similar to regular parentheses, but the substring matched by the group is
accessible via the symbolic group name name. Group names must be valid
Python identifiers, and each group name must be defined only once within a
regular expression. A symbolic group is also a numbered group, just as if
the group were not named.Named groups can be referenced in three contexts. If the pattern is
(?P<quote>['"]).*?(?P=quote)
(i.e. matching a string quoted with either
single or double quotes):Context of reference to group “quote” Ways to reference it in the same pattern itself (?P=quote)
(as shown)1
when processing match object m m.group('quote')
m.end('quote')
(etc.)
in a string passed to the repl
argument ofre.sub()
g<quote>
g<1>
1
(?P=name)
- A backreference to a named group; it matches whatever text was matched by the
earlier group named name. (?#...)
- A comment; the contents of the parentheses are simply ignored.
(?=...)
- Matches if
...
matches next, but doesn’t consume any of the string. This is
called a lookahead assertion. For example,Isaac (?=Asimov)
will match
'Isaac '
only if it’s followed by'Asimov'
. (?!...)
- Matches if
...
doesn’t match next. This is a negative lookahead assertion.
For example,Isaac (?!Asimov)
will match'Isaac '
only if it’s not
followed by'Asimov'
. (?<=...)
-
Matches if the current position in the string is preceded by a match for
...
that ends at the current position. This is called a positive lookbehind
assertion.(?<=abc)def
will find a match in'abcdef'
, since the
lookbehind will back up 3 characters and check if the contained pattern matches.
The contained pattern must only match strings of some fixed length, meaning that
abc
ora|b
are allowed, buta*
anda{3,4}
are not. Note that
patterns which start with positive lookbehind assertions will not match at the
beginning of the string being searched; you will most likely want to use the
search()
function rather than thematch()
function:>>> import re >>> m = re.search('(?<=abc)def', 'abcdef') >>> m.group(0) 'def'
This example looks for a word following a hyphen:
>>> m = re.search('(?<=-)w+', 'spam-egg') >>> m.group(0) 'egg'
Changed in version 3.5: Added support for group references of fixed length.
(?<!...)
- Matches if the current position in the string is not preceded by a match for
...
. This is called a negative lookbehind assertion. Similar to
positive lookbehind assertions, the contained pattern must only match strings of
some fixed length. Patterns which start with negative lookbehind assertions may
match at the beginning of the string being searched. (?(id/name)yes-pattern|no-pattern)
- Will try to match with
yes-pattern
if the group with given id or
name exists, and withno-pattern
if it doesn’t.no-pattern
is
optional and can be omitted. For example,
(<)?(w+@w+(?:.w+)+)(?(1)>|$)
is a poor email matching pattern, which
will match with'<user@host.com>'
as well as'user@host.com'
, but
not with'<user@host.com'
nor'user@host.com>'
.
The special sequences consist of ''
and a character from the list below.
If the ordinary character is not an ASCII digit or an ASCII letter, then the
resulting RE will match the second character. For example, $
matches the
character '$'
.
number
- Matches the contents of the group of the same number. Groups are numbered
starting from 1. For example,(.+) 1
matches'the the'
or'55 55'
,
but not'thethe'
(note the space after the group). This special sequence
can only be used to match one of the first 99 groups. If the first digit of
number is 0, or number is 3 octal digits long, it will not be interpreted as
a group match, but as the character with octal value number. Inside the
'['
and']'
of a character class, all numeric escapes are treated as
characters. A
- Matches only at the start of the string.
b
-
Matches the empty string, but only at the beginning or end of a word.
A word is defined as a sequence of word characters. Note that formally,
b
is defined as the boundary between aw
and aW
character
(or vice versa), or betweenw
and the beginning/end of the string.
This means thatr'bfoob'
matches'foo'
,'foo.'
,'(foo)'
,
'bar foo baz'
but not'foobar'
or'foo3'
.By default Unicode alphanumerics are the ones used in Unicode patterns, but
this can be changed by using theASCII
flag. Word boundaries are
determined by the current locale if theLOCALE
flag is used.
Inside a character range,b
represents the backspace character, for
compatibility with Python’s string literals. B
- Matches the empty string, but only when it is not at the beginning or end
of a word. This means thatr'pyB'
matches'python'
,'py3'
,
'py2'
, but not'py'
,'py.'
, or'py!'
.
B
is just the opposite ofb
, so word characters in Unicode
patterns are Unicode alphanumerics or the underscore, although this can
be changed by using theASCII
flag. Word boundaries are
determined by the current locale if theLOCALE
flag is used. d
-
- For Unicode (str) patterns:
- Matches any Unicode decimal digit (that is, any character in
Unicode character category [Nd]). This includes[0-9]
, and
also many other digit characters. If theASCII
flag is
used only[0-9]
is matched. - For 8-bit (bytes) patterns:
- Matches any decimal digit; this is equivalent to
[0-9]
.
D
- Matches any character which is not a decimal digit. This is
the opposite ofd
. If theASCII
flag is used this
becomes the equivalent of[^0-9]
. s
-
- For Unicode (str) patterns:
- Matches Unicode whitespace characters (which includes
[ tnrfv]
, and also many other characters, for example the
non-breaking spaces mandated by typography rules in many
languages). If theASCII
flag is used, only
[ tnrfv]
is matched. - For 8-bit (bytes) patterns:
- Matches characters considered whitespace in the ASCII character set;
this is equivalent to[ tnrfv]
.
S
- Matches any character which is not a whitespace character. This is
the opposite ofs
. If theASCII
flag is used this
becomes the equivalent of[^ tnrfv]
. w
-
- For Unicode (str) patterns:
- Matches Unicode word characters; this includes most characters
that can be part of a word in any language, as well as numbers and
the underscore. If theASCII
flag is used, only
[a-zA-Z0-9_]
is matched. - For 8-bit (bytes) patterns:
- Matches characters considered alphanumeric in the ASCII character set;
this is equivalent to[a-zA-Z0-9_]
. If theLOCALE
flag is
used, matches characters considered alphanumeric in the current locale
and the underscore.
W
- Matches any character which is not a word character. This is
the opposite ofw
. If theASCII
flag is used this
becomes the equivalent of[^a-zA-Z0-9_]
. If theLOCALE
flag is
used, matches characters considered alphanumeric in the current locale
and the underscore. Z
- Matches only at the end of the string.
Most of the standard escapes supported by Python string literals are also
accepted by the regular expression parser:
a b f n r t u U v x \
(Note that b
is used to represent word boundaries, and means “backspace”
only inside character classes.)
'u'
and 'U'
escape sequences are only recognized in Unicode
patterns. In bytes patterns they are errors.
Octal escapes are included in a limited form. If the first digit is a 0, or if
there are three octal digits, it is considered an octal escape. Otherwise, it is
a group reference. As for string literals, octal escapes are always at most
three digits in length.
Changed in version 3.3: The 'u'
and 'U'
escape sequences have been added.
Changed in version 3.6: Unknown escapes consisting of ''
and an ASCII letter now are errors.
See also
- Mastering Regular Expressions
- Book on regular expressions by Jeffrey Friedl, published by O’Reilly. The
second edition of the book no longer covers Python at all, but the first
edition covered writing good regular expression patterns in great detail.
6.2.2. Module Contents¶
The module defines several functions, constants, and an exception. Some of the
functions are simplified versions of the full featured methods for compiled
regular expressions. Most non-trivial applications always use the compiled
form.
Changed in version 3.6: Flag constants are now instances of RegexFlag
, which is a subclass of
enum.IntFlag
.
-
re.
compile
(pattern, flags=0)¶ -
Compile a regular expression pattern into a regular expression object, which can be used for matching using its
match()
,search()
and other methods, described
below.The expression’s behaviour can be modified by specifying a flags value.
Values can be any of the following variables, combined using bitwise OR (the
|
operator).The sequence
prog = re.compile(pattern) result = prog.match(string)
is equivalent to
result = re.match(pattern, string)
but using
re.compile()
and saving the resulting regular expression
object for reuse is more efficient when the expression will be used several
times in a single program.Note
The compiled versions of the most recent patterns passed to
re.compile()
and the module-level matching functions are cached, so
programs that use only a few regular expressions at a time needn’t worry
about compiling regular expressions.
-
re.
A
¶ -
re.
ASCII
¶ -
Make
w
,W
,b
,B
,d
,D
,s
andS
perform ASCII-only matching instead of full Unicode matching. This is only
meaningful for Unicode patterns, and is ignored for byte patterns.
Corresponds to the inline flag(?a)
.Note that for backward compatibility, the
re.U
flag still
exists (as well as its synonymre.UNICODE
and its embedded
counterpart(?u)
), but these are redundant in Python 3 since
matches are Unicode by default for strings (and Unicode matching
isn’t allowed for bytes).
-
re.
DEBUG
¶ -
Display debug information about compiled expression.
No corresponding inline flag.
-
re.
I
¶ -
re.
IGNORECASE
¶ -
Perform case-insensitive matching; expressions like
[A-Z]
will also
match lowercase letters. Full Unicode matching (such asÜ
matching
ü
) also works unless there.ASCII
flag is used to disable
non-ASCII matches. The current locale does not change the effect of this
flag unless there.LOCALE
flag is also used.
Corresponds to the inline flag(?i)
.Note that when the Unicode patterns
[a-z]
or[A-Z]
are used in
combination with theIGNORECASE
flag, they will match the 52 ASCII
letters and 4 additional non-ASCII letters: ‘İ’ (U+0130, Latin capital
letter I with dot above), ‘ı’ (U+0131, Latin small letter dotless i),
‘ſ’ (U+017F, Latin small letter long s) and ‘K’ (U+212A, Kelvin sign).
If theASCII
flag is used, only letters ‘a’ to ‘z’
and ‘A’ to ‘Z’ are matched.
-
re.
L
¶ -
re.
LOCALE
¶ -
Make
w
,W
,b
,B
and case-insensitive matching
dependent on the current locale. This flag can be used only with bytes
patterns. The use of this flag is discouraged as the locale mechanism
is very unreliable, it only handles one “culture” at a time, and it only
works with 8-bit locales. Unicode matching is already enabled by default
in Python 3 for Unicode (str) patterns, and it is able to handle different
locales/languages.
Corresponds to the inline flag(?L)
.Changed in version 3.6:
re.LOCALE
can be used only with bytes patterns and is
not compatible withre.ASCII
.Changed in version 3.7: Compiled regular expression objects with the
re.LOCALE
flag no
longer depend on the locale at compile time. Only the locale at
matching time affects the result of matching.
-
re.
M
¶ -
re.
MULTILINE
¶ -
When specified, the pattern character
'^'
matches at the beginning of the
string and at the beginning of each line (immediately following each newline);
and the pattern character'$'
matches at the end of the string and at the
end of each line (immediately preceding each newline). By default,'^'
matches only at the beginning of the string, and'$'
only at the end of the
string and immediately before the newline (if any) at the end of the string.
Corresponds to the inline flag(?m)
.
-
re.
S
¶ -
re.
DOTALL
¶ -
Make the
'.'
special character match any character at all, including a
newline; without this flag,'.'
will match anything except a newline.
Corresponds to the inline flag(?s)
.
-
re.
X
¶ -
re.
VERBOSE
¶ -
This flag allows you to write regular expressions that look nicer and are
more readable by allowing you to visually separate logical sections of the
pattern and add comments. Whitespace within the pattern is ignored, except
when in a character class or when preceded by an unescaped backslash.
When a line contains a#
that is not in a character class and is not
preceded by an unescaped backslash, all characters from the leftmost such
#
through the end of the line are ignored.This means that the two following regular expression objects that match a
decimal number are functionally equal:a = re.compile(r"""d + # the integral part . # the decimal point d * # some fractional digits""", re.X) b = re.compile(r"d+.d*")
Corresponds to the inline flag
(?x)
.
-
re.
search
(pattern, string, flags=0)¶ -
Scan through string looking for the first location where the regular expression
pattern produces a match, and return a corresponding match object. ReturnNone
if no position in the string matches the
pattern; note that this is different from finding a zero-length match at some
point in the string.
-
re.
match
(pattern, string, flags=0)¶ -
If zero or more characters at the beginning of string match the regular
expression pattern, return a corresponding match object. ReturnNone
if the string does not match the pattern;
note that this is different from a zero-length match.Note that even in
MULTILINE
mode,re.match()
will only match
at the beginning of the string and not at the beginning of each line.If you want to locate a match anywhere in string, use
search()
instead (see also search() vs. match()).
-
re.
fullmatch
(pattern, string, flags=0)¶ -
If the whole string matches the regular expression pattern, return a
corresponding match object. ReturnNone
if the
string does not match the pattern; note that this is different from a
zero-length match.New in version 3.4.
-
re.
split
(pattern, string, maxsplit=0, flags=0)¶ -
Split string by the occurrences of pattern. If capturing parentheses are
used in pattern, then the text of all groups in the pattern are also returned
as part of the resulting list. If maxsplit is nonzero, at most maxsplit
splits occur, and the remainder of the string is returned as the final element
of the list.>>> re.split('W+', 'Words, words, words.') ['Words', 'words', 'words', ''] >>> re.split('(W+)', 'Words, words, words.') ['Words', ', ', 'words', ', ', 'words', '.', ''] >>> re.split('W+', 'Words, words, words.', 1) ['Words', 'words, words.'] >>> re.split('[a-f]+', '0a3B9', flags=re.IGNORECASE) ['0', '3', '9']
If there are capturing groups in the separator and it matches at the start of
the string, the result will start with an empty string. The same holds for
the end of the string:>>> re.split('(W+)', '...words, words...') ['', '...', 'words', ', ', 'words', '...', '']
That way, separator components are always found at the same relative
indices within the result list.Note
split()
doesn’t currently split a string on an empty pattern match.
For example:>>> re.split('x*', 'axbc') ['a', 'bc']
Even though
'x*'
also matches 0 ‘x’ before ‘a’, between ‘b’ and ‘c’,
and after ‘c’, currently these matches are ignored. The correct behavior
(i.e. splitting on empty matches too and returning['', 'a', 'b', 'c',
) will be implemented in future versions of Python, but since this
'']
is a backward incompatible change, aFutureWarning
will be raised
in the meanwhile.Patterns that can only match empty strings currently never split the
string. Since this doesn’t match the expected behavior, a
ValueError
will be raised starting from Python 3.5:>>> re.split("^$", "foonnbarn", flags=re.M) Traceback (most recent call last): File "<stdin>", line 1, in <module> ... ValueError: split() requires a non-empty pattern match.
Changed in version 3.1: Added the optional flags argument.
Changed in version 3.5: Splitting on a pattern that could match an empty string now raises
a warning. Patterns that can only match empty strings are now rejected.
-
re.
findall
(pattern, string, flags=0)¶ -
Return all non-overlapping matches of pattern in string, as a list of
strings. The string is scanned left-to-right, and matches are returned in
the order found. If one or more groups are present in the pattern, return a
list of groups; this will be a list of tuples if the pattern has more than
one group. Empty matches are included in the result unless they touch the
beginning of another match.
-
re.
finditer
(pattern, string, flags=0)¶ -
Return an iterator yielding match objects over
all non-overlapping matches for the RE pattern in string. The string
is scanned left-to-right, and matches are returned in the order found. Empty
matches are included in the result unless they touch the beginning of another
match.
-
re.
sub
(pattern, repl, string, count=0, flags=0)¶ -
Return the string obtained by replacing the leftmost non-overlapping occurrences
of pattern in string by the replacement repl. If the pattern isn’t found,
string is returned unchanged. repl can be a string or a function; if it is
a string, any backslash escapes in it are processed. That is,n
is
converted to a single newline character,r
is converted to a carriage return, and
so forth. Unknown escapes such as&
are left alone. Backreferences, such
as6
, are replaced with the substring matched by group 6 in the pattern.
For example:>>> re.sub(r'defs+([a-zA-Z_][a-zA-Z_0-9]*)s*(s*):', ... r'static PyObject*npy_1(void)n{', ... 'def myfunc():') 'static PyObject*npy_myfunc(void)n{'
If repl is a function, it is called for every non-overlapping occurrence of
pattern. The function takes a single match object
argument, and returns the replacement string. For example:>>> def dashrepl(matchobj): ... if matchobj.group(0) == '-': return ' ' ... else: return '-' >>> re.sub('-{1,2}', dashrepl, 'pro----gram-files') 'pro--gram files' >>> re.sub(r'sANDs', ' & ', 'Baked Beans And Spam', flags=re.IGNORECASE) 'Baked Beans & Spam'
The pattern may be a string or a pattern object.
The optional argument count is the maximum number of pattern occurrences to be
replaced; count must be a non-negative integer. If omitted or zero, all
occurrences will be replaced. Empty matches for the pattern are replaced only
when not adjacent to a previous match, sosub('x*', '-', 'abc')
returns
'-a-b-c-'
.In string-type repl arguments, in addition to the character escapes and
backreferences described above,
g<name>
will use the substring matched by the group namedname
, as
defined by the(?P<name>...)
syntax.g<number>
uses the corresponding
group number;g<2>
is therefore equivalent to2
, but isn’t ambiguous
in a replacement such asg<2>0
.20
would be interpreted as a
reference to group 20, not a reference to group 2 followed by the literal
character'0'
. The backreferenceg<0>
substitutes in the entire
substring matched by the RE.Changed in version 3.1: Added the optional flags argument.
Changed in version 3.5: Unmatched groups are replaced with an empty string.
Changed in version 3.6: Unknown escapes in pattern consisting of
''
and an ASCII letter
now are errors.Changed in version 3.7: Unknown escapes in repl consisting of
''
and an ASCII letter
now are errors.
-
re.
subn
(pattern, repl, string, count=0, flags=0)¶ -
Perform the same operation as
sub()
, but return a tuple(new_string,
.
number_of_subs_made)Changed in version 3.1: Added the optional flags argument.
Changed in version 3.5: Unmatched groups are replaced with an empty string.
-
re.
escape
(pattern)¶ -
Escape special characters in pattern.
This is useful if you want to match an arbitrary literal string that may
have regular expression metacharacters in it. For example:>>> print(re.escape('python.exe')) python.exe >>> legal_chars = string.ascii_lowercase + string.digits + "!#$%&'*+-.^_`|~:" >>> print('[%s]+' % re.escape(legal_chars)) [abcdefghijklmnopqrstuvwxyz0123456789!#$%&'*+-.^_`|~:]+ >>> operators = ['+', '-', '*', '/', '**'] >>> print('|'.join(map(re.escape, sorted(operators, reverse=True)))) /|-|+|**|*
This functions must not be used for the replacement string in
sub()
andsubn()
, only backslashes should be escaped. For example:>>> digits_re = r'd+' >>> sample = '/usr/sbin/sendmail - 0 errors, 12 warnings' >>> print(re.sub(digits_re, digits_re.replace('\', r'\'), sample)) /usr/sbin/sendmail - d+ errors, d+ warnings
Changed in version 3.3: The
'_'
character is no longer escaped.Changed in version 3.7: Only characters that can have special meaning in a regular expression
are escaped.
-
re.
purge
()¶ -
Clear the regular expression cache.
-
exception
re.
error
(msg, pattern=None, pos=None)¶ -
Exception raised when a string passed to one of the functions here is not a
valid regular expression (for example, it might contain unmatched parentheses)
or when some other error occurs during compilation or matching. It is never an
error if a string contains no match for a pattern. The error instance has
the following additional attributes:-
msg
¶ -
The unformatted error message.
-
pattern
¶ -
The regular expression pattern.
-
pos
¶ -
The index in pattern where compilation failed (may be
None
).
-
lineno
¶ -
The line corresponding to pos (may be
None
).
-
colno
¶ -
The column corresponding to pos (may be
None
).
Changed in version 3.5: Added additional attributes.
-
6.2.3. Regular Expression Objects¶
Compiled regular expression objects support the following methods and
attributes:
-
Pattern.
search
(string[, pos[, endpos]])¶ -
Scan through string looking for the first location where this regular
expression produces a match, and return a corresponding match object. ReturnNone
if no position in the string matches the
pattern; note that this is different from finding a zero-length match at some
point in the string.The optional second parameter pos gives an index in the string where the
search is to start; it defaults to0
. This is not completely equivalent to
slicing the string; the'^'
pattern character matches at the real beginning
of the string and at positions just after a newline, but not necessarily at the
index where the search is to start.The optional parameter endpos limits how far the string will be searched; it
will be as if the string is endpos characters long, so only the characters
from pos toendpos - 1
will be searched for a match. If endpos is less
than pos, no match will be found; otherwise, if rx is a compiled regular
expression object,rx.search(string, 0, 50)
is equivalent to
rx.search(string[:50], 0)
.>>> pattern = re.compile("d") >>> pattern.search("dog") # Match at index 0 <re.Match object; span=(0, 1), match='d'> >>> pattern.search("dog", 1) # No match; search doesn't include the "d"
-
Pattern.
match
(string[, pos[, endpos]])¶ -
If zero or more characters at the beginning of string match this regular
expression, return a corresponding match object.
ReturnNone
if the string does not match the pattern; note that this is
different from a zero-length match.The optional pos and endpos parameters have the same meaning as for the
search()
method.>>> pattern = re.compile("o") >>> pattern.match("dog") # No match as "o" is not at the start of "dog". >>> pattern.match("dog", 1) # Match as "o" is the 2nd character of "dog". <re.Match object; span=(1, 2), match='o'>
If you want to locate a match anywhere in string, use
search()
instead (see also search() vs. match()).
-
Pattern.
fullmatch
(string[, pos[, endpos]])¶ -
If the whole string matches this regular expression, return a corresponding
match object. ReturnNone
if the string does not
match the pattern; note that this is different from a zero-length match.The optional pos and endpos parameters have the same meaning as for the
search()
method.>>> pattern = re.compile("o[gh]") >>> pattern.fullmatch("dog") # No match as "o" is not at the start of "dog". >>> pattern.fullmatch("ogre") # No match as not the full string matches. >>> pattern.fullmatch("doggie", 1, 3) # Matches within given limits. <re.Match object; span=(1, 3), match='og'>
New in version 3.4.
-
Pattern.
split
(string, maxsplit=0)¶ -
Identical to the
split()
function, using the compiled pattern.
-
Pattern.
findall
(string[, pos[, endpos]])¶ -
Similar to the
findall()
function, using the compiled pattern, but
also accepts optional pos and endpos parameters that limit the search
region like forsearch()
.
-
Pattern.
finditer
(string[, pos[, endpos]])¶ -
Similar to the
finditer()
function, using the compiled pattern, but
also accepts optional pos and endpos parameters that limit the search
region like forsearch()
.
-
Pattern.
sub
(repl, string, count=0)¶ -
Identical to the
sub()
function, using the compiled pattern.
-
Pattern.
subn
(repl, string, count=0)¶ -
Identical to the
subn()
function, using the compiled pattern.
-
Pattern.
flags
¶ -
The regex matching flags. This is a combination of the flags given to
compile()
, any(?...)
inline flags in the pattern, and implicit
flags such asUNICODE
if the pattern is a Unicode string.
-
Pattern.
groups
¶ -
The number of capturing groups in the pattern.
-
Pattern.
groupindex
¶ -
A dictionary mapping any symbolic group names defined by
(?P<id>)
to group
numbers. The dictionary is empty if no symbolic groups were used in the
pattern.
-
Pattern.
pattern
¶ -
The pattern string from which the pattern object was compiled.
Changed in version 3.7: Added support of copy.copy()
and copy.deepcopy()
. Compiled
regular expression objects are considered atomic.
6.2.4. Match Objects¶
Match objects always have a boolean value of True
.
Since match()
and search()
return None
when there is no match, you can test whether there was a match with a simple
if
statement:
match = re.search(pattern, string) if match: process(match)
Match objects support the following methods and attributes:
-
Match.
expand
(template)¶ -
Return the string obtained by doing backslash substitution on the template
string template, as done by thesub()
method.
Escapes such asn
are converted to the appropriate characters,
and numeric backreferences (1
,2
) and named backreferences
(g<1>
,g<name>
) are replaced by the contents of the
corresponding group.Changed in version 3.5: Unmatched groups are replaced with an empty string.
-
Match.
group
([group1, …])¶ -
Returns one or more subgroups of the match. If there is a single argument, the
result is a single string; if there are multiple arguments, the result is a
tuple with one item per argument. Without arguments, group1 defaults to zero
(the whole match is returned). If a groupN argument is zero, the corresponding
return value is the entire matching string; if it is in the inclusive range
[1..99], it is the string matching the corresponding parenthesized group. If a
group number is negative or larger than the number of groups defined in the
pattern, anIndexError
exception is raised. If a group is contained in a
part of the pattern that did not match, the corresponding result isNone
.
If a group is contained in a part of the pattern that matched multiple times,
the last match is returned.>>> m = re.match(r"(w+) (w+)", "Isaac Newton, physicist") >>> m.group(0) # The entire match 'Isaac Newton' >>> m.group(1) # The first parenthesized subgroup. 'Isaac' >>> m.group(2) # The second parenthesized subgroup. 'Newton' >>> m.group(1, 2) # Multiple arguments give us a tuple. ('Isaac', 'Newton')
If the regular expression uses the
(?P<name>...)
syntax, the groupN
arguments may also be strings identifying groups by their group name. If a
string argument is not used as a group name in the pattern, anIndexError
exception is raised.A moderately complicated example:
>>> m = re.match(r"(?P<first_name>w+) (?P<last_name>w+)", "Malcolm Reynolds") >>> m.group('first_name') 'Malcolm' >>> m.group('last_name') 'Reynolds'
Named groups can also be referred to by their index:
>>> m.group(1) 'Malcolm' >>> m.group(2) 'Reynolds'
If a group matches multiple times, only the last match is accessible:
>>> m = re.match(r"(..)+", "a1b2c3") # Matches 3 times. >>> m.group(1) # Returns only the last match. 'c3'
-
Match.
__getitem__
(g)¶ -
This is identical to
m.group(g)
. This allows easier access to
an individual group from a match:>>> m = re.match(r"(w+) (w+)", "Isaac Newton, physicist") >>> m[0] # The entire match 'Isaac Newton' >>> m[1] # The first parenthesized subgroup. 'Isaac' >>> m[2] # The second parenthesized subgroup. 'Newton'
New in version 3.6.
-
Match.
groups
(default=None)¶ -
Return a tuple containing all the subgroups of the match, from 1 up to however
many groups are in the pattern. The default argument is used for groups that
did not participate in the match; it defaults toNone
.For example:
>>> m = re.match(r"(d+).(d+)", "24.1632") >>> m.groups() ('24', '1632')
If we make the decimal place and everything after it optional, not all groups
might participate in the match. These groups will default toNone
unless
the default argument is given:>>> m = re.match(r"(d+).?(d+)?", "24") >>> m.groups() # Second group defaults to None. ('24', None) >>> m.groups('0') # Now, the second group defaults to '0'. ('24', '0')
-
Match.
groupdict
(default=None)¶ -
Return a dictionary containing all the named subgroups of the match, keyed by
the subgroup name. The default argument is used for groups that did not
participate in the match; it defaults toNone
. For example:>>> m = re.match(r"(?P<first_name>w+) (?P<last_name>w+)", "Malcolm Reynolds") >>> m.groupdict() {'first_name': 'Malcolm', 'last_name': 'Reynolds'}
-
Match.
start
([group])¶ -
Match.
end
([group])¶ -
Return the indices of the start and end of the substring matched by group;
group defaults to zero (meaning the whole matched substring). Return-1
if
group exists but did not contribute to the match. For a match object m, and
a group g that did contribute to the match, the substring matched by group g
(equivalent tom.group(g)
) ism.string[m.start(g):m.end(g)]
Note that
m.start(group)
will equalm.end(group)
if group matched a
null string. For example, afterm = re.search('b(c?)', 'cba')
,
m.start(0)
is 1,m.end(0)
is 2,m.start(1)
andm.end(1)
are both
2, andm.start(2)
raises anIndexError
exception.An example that will remove remove_this from email addresses:
>>> email = "tony@tiremove_thisger.net" >>> m = re.search("remove_this", email) >>> email[:m.start()] + email[m.end():] 'tony@tiger.net'
-
Match.
span
([group])¶ -
For a match m, return the 2-tuple
(m.start(group), m.end(group))
. Note
that if group did not contribute to the match, this is(-1, -1)
.
group defaults to zero, the entire match.
-
Match.
pos
¶ -
The value of pos which was passed to the
search()
or
match()
method of a regex object. This is
the index into the string at which the RE engine started looking for a match.
-
Match.
endpos
¶ -
The value of endpos which was passed to the
search()
or
match()
method of a regex object. This is
the index into the string beyond which the RE engine will not go.
-
Match.
lastindex
¶ -
The integer index of the last matched capturing group, or
None
if no group
was matched at all. For example, the expressions(a)b
,((a)(b))
, and
((ab))
will havelastindex == 1
if applied to the string'ab'
, while
the expression(a)(b)
will havelastindex == 2
, if applied to the same
string.
-
Match.
lastgroup
¶ -
The name of the last matched capturing group, or
None
if the group didn’t
have a name, or if no group was matched at all.
-
Match.
re
¶ -
The regular expression object whose
match()
or
search()
method produced this match instance.
-
Match.
string
¶ -
The string passed to
match()
orsearch()
.
Changed in version 3.7: Added support of copy.copy()
and copy.deepcopy()
. Match objects
are considered atomic.
6.2.5. Regular Expression Examples¶
6.2.5.1. Checking for a Pair¶
In this example, we’ll use the following helper function to display match
objects a little more gracefully:
def displaymatch(match): if match is None: return None return '<Match: %r, groups=%r>' % (match.group(), match.groups())
Suppose you are writing a poker program where a player’s hand is represented as
a 5-character string with each character representing a card, “a” for ace, “k”
for king, “q” for queen, “j” for jack, “t” for 10, and “2” through “9”
representing the card with that value.
To see if a given string is a valid hand, one could do the following:
>>> valid = re.compile(r"^[a2-9tjqk]{5}$") >>> displaymatch(valid.match("akt5q")) # Valid. "<Match: 'akt5q', groups=()>" >>> displaymatch(valid.match("akt5e")) # Invalid. >>> displaymatch(valid.match("akt")) # Invalid. >>> displaymatch(valid.match("727ak")) # Valid. "<Match: '727ak', groups=()>"
That last hand, "727ak"
, contained a pair, or two of the same valued cards.
To match this with a regular expression, one could use backreferences as such:
>>> pair = re.compile(r".*(.).*1") >>> displaymatch(pair.match("717ak")) # Pair of 7s. "<Match: '717', groups=('7',)>" >>> displaymatch(pair.match("718ak")) # No pairs. >>> displaymatch(pair.match("354aa")) # Pair of aces. "<Match: '354aa', groups=('a',)>"
To find out what card the pair consists of, one could use the
group()
method of the match object in the following manner:
>>> pair.match("717ak").group(1) '7' # Error because re.match() returns None, which doesn't have a group() method: >>> pair.match("718ak").group(1) Traceback (most recent call last): File "<pyshell#23>", line 1, in <module> re.match(r".*(.).*1", "718ak").group(1) AttributeError: 'NoneType' object has no attribute 'group' >>> pair.match("354aa").group(1) 'a'
6.2.5.2. Simulating scanf()¶
Python does not currently have an equivalent to scanf()
. Regular
expressions are generally more powerful, though also more verbose, than
scanf()
format strings. The table below offers some more-or-less
equivalent mappings between scanf()
format tokens and regular
expressions.
scanf() Token |
Regular Expression |
---|---|
%c |
. |
%5c |
.{5} |
%d |
[-+]?d+ |
%e , %E , %f , %g |
[-+]?(d+(.d*)?|.d+)([eE][-+]?d+)? |
%i |
[-+]?(0[xX][dA-Fa-f]+|0[0-7]*|d+) |
%o |
[-+]?[0-7]+ |
%s |
S+ |
%u |
d+ |
%x , %X |
[-+]?(0[xX])?[dA-Fa-f]+ |
To extract the filename and numbers from a string like
/usr/sbin/sendmail - 0 errors, 4 warnings
you would use a scanf()
format like
%s - %d errors, %d warnings
The equivalent regular expression would be
(S+) - (d+) errors, (d+) warnings
6.2.5.3. search() vs. match()¶
Python offers two different primitive operations based on regular expressions:
re.match()
checks for a match only at the beginning of the string, while
re.search()
checks for a match anywhere in the string (this is what Perl
does by default).
For example:
>>> re.match("c", "abcdef") # No match >>> re.search("c", "abcdef") # Match <re.Match object; span=(2, 3), match='c'>
Regular expressions beginning with '^'
can be used with search()
to
restrict the match at the beginning of the string:
>>> re.match("c", "abcdef") # No match >>> re.search("^c", "abcdef") # No match >>> re.search("^a", "abcdef") # Match <re.Match object; span=(0, 1), match='a'>
Note however that in MULTILINE
mode match()
only matches at the
beginning of the string, whereas using search()
with a regular expression
beginning with '^'
will match at the beginning of each line.
>>> re.match('X', 'AnBnX', re.MULTILINE) # No match >>> re.search('^X', 'AnBnX', re.MULTILINE) # Match <re.Match object; span=(4, 5), match='X'>
6.2.5.4. Making a Phonebook¶
split()
splits a string into a list delimited by the passed pattern. The
method is invaluable for converting textual data into data structures that can be
easily read and modified by Python as demonstrated in the following example that
creates a phonebook.
First, here is the input. Normally it may come from a file, here we are using
triple-quoted string syntax:
>>> text = """Ross McFluff: 834.345.1254 155 Elm Street ... ... Ronald Heathmore: 892.345.3428 436 Finley Avenue ... Frank Burger: 925.541.7625 662 South Dogwood Way ... ... ... Heather Albrecht: 548.326.4584 919 Park Place"""
The entries are separated by one or more newlines. Now we convert the string
into a list with each nonempty line having its own entry:
>>> entries = re.split("n+", text) >>> entries ['Ross McFluff: 834.345.1254 155 Elm Street', 'Ronald Heathmore: 892.345.3428 436 Finley Avenue', 'Frank Burger: 925.541.7625 662 South Dogwood Way', 'Heather Albrecht: 548.326.4584 919 Park Place']
Finally, split each entry into a list with first name, last name, telephone
number, and address. We use the maxsplit
parameter of split()
because the address has spaces, our splitting pattern, in it:
>>> [re.split(":? ", entry, 3) for entry in entries] [['Ross', 'McFluff', '834.345.1254', '155 Elm Street'], ['Ronald', 'Heathmore', '892.345.3428', '436 Finley Avenue'], ['Frank', 'Burger', '925.541.7625', '662 South Dogwood Way'], ['Heather', 'Albrecht', '548.326.4584', '919 Park Place']]
The :?
pattern matches the colon after the last name, so that it does not
occur in the result list. With a maxsplit
of 4
, we could separate the
house number from the street name:
>>> [re.split(":? ", entry, 4) for entry in entries] [['Ross', 'McFluff', '834.345.1254', '155', 'Elm Street'], ['Ronald', 'Heathmore', '892.345.3428', '436', 'Finley Avenue'], ['Frank', 'Burger', '925.541.7625', '662', 'South Dogwood Way'], ['Heather', 'Albrecht', '548.326.4584', '919', 'Park Place']]
6.2.5.5. Text Munging¶
sub()
replaces every occurrence of a pattern with a string or the
result of a function. This example demonstrates using sub()
with
a function to “munge” text, or randomize the order of all the characters
in each word of a sentence except for the first and last characters:
>>> def repl(m): ... inner_word = list(m.group(2)) ... random.shuffle(inner_word) ... return m.group(1) + "".join(inner_word) + m.group(3) >>> text = "Professor Abdolmalek, please report your absences promptly." >>> re.sub(r"(w)(w+)(w)", repl, text) 'Poefsrosr Aealmlobdk, pslaee reorpt your abnseces plmrptoy.' >>> re.sub(r"(w)(w+)(w)", repl, text) 'Pofsroser Aodlambelk, plasee reoprt yuor asnebces potlmrpy.'
6.2.5.6. Finding all Adverbs¶
findall()
matches all occurrences of a pattern, not just the first
one as search()
does. For example, if one was a writer and wanted to
find all of the adverbs in some text, he or she might use findall()
in
the following manner:
>>> text = "He was carefully disguised but captured quickly by police." >>> re.findall(r"w+ly", text) ['carefully', 'quickly']
6.2.5.7. Finding all Adverbs and their Positions¶
If one wants more information about all matches of a pattern than the matched
text, finditer()
is useful as it provides match objects instead of strings. Continuing with the previous example, if
one was a writer who wanted to find all of the adverbs and their positions in
some text, he or she would use finditer()
in the following manner:
>>> text = "He was carefully disguised but captured quickly by police." >>> for m in re.finditer(r"w+ly", text): ... print('%02d-%02d: %s' % (m.start(), m.end(), m.group(0))) 07-16: carefully 40-47: quickly
6.2.5.8. Raw String Notation¶
Raw string notation (r"text"
) keeps regular expressions sane. Without it,
every backslash (''
) in a regular expression would have to be prefixed with
another one to escape it. For example, the two following lines of code are
functionally identical:
>>> re.match(r"W(.)1W", " ff ") <re.Match object; span=(0, 4), match=' ff '> >>> re.match("\W(.)\1\W", " ff ") <re.Match object; span=(0, 4), match=' ff '>
When one wants to match a literal backslash, it must be escaped in the regular
expression. With raw string notation, this means r"\"
. Without raw string
notation, one must use "\\"
, making the following lines of code
functionally identical:
>>> re.match(r"\", r"\") <re.Match object; span=(0, 1), match='\'> >>> re.match("\\", r"\") <re.Match object; span=(0, 1), match='\'>
6.2.5.9. Writing a Tokenizer¶
A tokenizer or scanner
analyzes a string to categorize groups of characters. This is a useful first
step in writing a compiler or interpreter.
The text categories are specified with regular expressions. The technique is
to combine those into a single master regular expression and to loop over
successive matches:
import collections import re Token = collections.namedtuple('Token', ['typ', 'value', 'line', 'column']) def tokenize(code): keywords = {'IF', 'THEN', 'ENDIF', 'FOR', 'NEXT', 'GOSUB', 'RETURN'} token_specification = [ ('NUMBER', r'd+(.d*)?'), # Integer or decimal number ('ASSIGN', r':='), # Assignment operator ('END', r';'), # Statement terminator ('ID', r'[A-Za-z]+'), # Identifiers ('OP', r'[+-*/]'), # Arithmetic operators ('NEWLINE', r'n'), # Line endings ('SKIP', r'[ t]+'), # Skip over spaces and tabs ('MISMATCH',r'.'), # Any other character ] tok_regex = '|'.join('(?P<%s>%s)' % pair for pair in token_specification) line_num = 1 line_start = 0 for mo in re.finditer(tok_regex, code): kind = mo.lastgroup value = mo.group(kind) if kind == 'NEWLINE': line_start = mo.end() line_num += 1 elif kind == 'SKIP': pass elif kind == 'MISMATCH': raise RuntimeError(f'{value!r} unexpected on line {line_num}') else: if kind == 'ID' and value in keywords: kind = value column = mo.start() - line_start yield Token(kind, value, line_num, column) statements = ''' IF quantity THEN total := total + price * quantity; tax := price * 0.05; ENDIF; ''' for token in tokenize(statements): print(token)
The tokenizer produces the following output:
Token(typ='IF', value='IF', line=2, column=4) Token(typ='ID', value='quantity', line=2, column=7) Token(typ='THEN', value='THEN', line=2, column=16) Token(typ='ID', value='total', line=3, column=8) Token(typ='ASSIGN', value=':=', line=3, column=14) Token(typ='ID', value='total', line=3, column=17) Token(typ='OP', value='+', line=3, column=23) Token(typ='ID', value='price', line=3, column=25) Token(typ='OP', value='*', line=3, column=31) Token(typ='ID', value='quantity', line=3, column=33) Token(typ='END', value=';', line=3, column=41) Token(typ='ID', value='tax', line=4, column=8) Token(typ='ASSIGN', value=':=', line=4, column=12) Token(typ='ID', value='price', line=4, column=15) Token(typ='OP', value='*', line=4, column=21) Token(typ='NUMBER', value='0.05', line=4, column=23) Token(typ='END', value=';', line=4, column=27) Token(typ='ENDIF', value='ENDIF', line=5, column=4) Token(typ='END', value=';', line=5, column=9)
A Regular Expressions (RegEx) is a special sequence of characters that uses a search pattern to find a string or set of strings. It can detect the presence or absence of a text by matching it with a particular pattern, and also can split a pattern into one or more sub-patterns. Python provides a re module that supports the use of regex in Python. Its primary function is to offer a search, where it takes a regular expression and a string. Here, it either returns the first match or else none.
Example:
Python3
import
re
s
=
'GeeksforGeeks: A computer science portal for geeks'
match
=
re.search(r
'portal'
, s)
print
(
'Start Index:'
, match.start())
print
(
'End Index:'
, match.end())
Output
Start Index: 34 End Index: 40
The above code gives the starting index and the ending index of the string portal.
Note: Here r character (r’portal’) stands for raw, not regex. The raw string is slightly different from a regular string, it won’t interpret the character as an escape character. This is because the regular expression engine uses character for its own escaping purpose.
Before starting with the Python regex module let’s see how to actually write regex using metacharacters or special sequences.
MetaCharacters
To understand the RE analogy, MetaCharacters are useful, important, and will be used in functions of module re. Below is the list of metacharacters.
MetaCharacters | Description |
---|---|
Used to drop the special meaning of character following it | |
[] | Represent a character class |
^ | Matches the beginning |
$ | Matches the end |
. | Matches any character except newline |
| | Means OR (Matches with any of the characters separated by it. |
? | Matches zero or one occurrence |
* | Any number of occurrences (including 0 occurrences) |
+ | One or more occurrences |
{} | Indicate the number of occurrences of a preceding regex to match. |
() | Enclose a group of Regex |
Let’s discuss each of these metacharacters in detail
– Backslash
The backslash () makes sure that the character is not treated in a special way. This can be considered a way of escaping metacharacters. For example, if you want to search for the dot(.) in the string then you will find that dot(.) will be treated as a special character as is one of the metacharacters (as shown in the above table). So for this case, we will use the backslash() just before the dot(.) so that it will lose its specialty. See the below example for a better understanding.
Example:
Python3
import
re
s
=
'geeks.forgeeks'
match
=
re.search(r
'.'
, s)
print
(match)
match
=
re.search(r
'.'
, s)
print
(match)
Output
<re.Match object; span=(0, 1), match='g'> <re.Match object; span=(5, 6), match='.'>
[] – Square Brackets
Square Brackets ([]) represent a character class consisting of a set of characters that we wish to match. For example, the character class [abc] will match any single a, b, or c.
We can also specify a range of characters using – inside the square brackets. For example,
- [0, 3] is sample as [0123]
- [a-c] is same as [abc]
We can also invert the character class using the caret(^) symbol. For example,
- [^0-3] means any number except 0, 1, 2, or 3
- [^a-c] means any character except a, b, or c
Example:
Python3
import
re
string
=
"The quick brown fox jumps over the lazy dog"
pattern
=
"[a-m]"
result
=
re.findall(pattern, string)
print
(result)
Output
['h', 'e', 'i', 'c', 'k', 'b', 'f', 'j', 'm', 'e', 'h', 'e', 'l', 'a', 'd', 'g']
^ – Caret
Caret (^) symbol matches the beginning of the string i.e. checks whether the string starts with the given character(s) or not. For example –
- ^g will check if the string starts with g such as geeks, globe, girl, g, etc.
- ^ge will check if the string starts with ge such as geeks, geeksforgeeks, etc.
Example:
Python3
import
re
regex
=
r
'^The'
strings
=
[
'The quick brown fox'
,
'The lazy dog'
,
'A quick brown fox'
]
for
string
in
strings:
if
re.match(regex, string):
print
(f
'Matched: {string}'
)
else
:
print
(f
'Not matched: {string}'
)
Output
Matched: The quick brown fox Matched: The lazy dog Not matched: A quick brown fox
$ – Dollar
Dollar($) symbol matches the end of the string i.e checks whether the string ends with the given character(s) or not. For example –
- s$ will check for the string that ends with a such as geeks, ends, s, etc.
- ks$ will check for the string that ends with ks such as geeks, geeksforgeeks, ks, etc.
Example:
Python3
import
re
string
=
"Hello World!"
pattern
=
r
"World!$"
match
=
re.search(pattern, string)
if
match:
print
(
"Match found!"
)
else
:
print
(
"Match not found."
)
. – Dot
Dot(.) symbol matches only a single character except for the newline character (n). For example –
- a.b will check for the string that contains any character at the place of the dot such as acb, acbd, abbb, etc
- .. will check if the string contains at least 2 characters
Example:
Python3
import
re
string
=
"The quick brown fox jumps over the lazy dog."
pattern
=
r
"brown.fox"
match
=
re.search(pattern, string)
if
match:
print
(
"Match found!"
)
else
:
print
(
"Match not found."
)
| – Or
Or symbol works as the or operator meaning it checks whether the pattern before or after the or symbol is present in the string or not. For example –
- a|b will match any string that contains a or b such as acd, bcd, abcd, etc.
? – Question Mark
Question mark(?) checks if the string before the question mark in the regex occurs at least once or not at all. For example –
- ab?c will be matched for the string ac, acb, dabc but will not be matched for abbc because there are two b. Similarly, it will not be matched for abdc because b is not followed by c.
* – Star
Star (*) symbol matches zero or more occurrences of the regex preceding the * symbol. For example –
- ab*c will be matched for the string ac, abc, abbbc, dabc, etc. but will not be matched for abdc because b is not followed by c.
+ – Plus
Plus (+) symbol matches one or more occurrences of the regex preceding the + symbol. For example –
- ab+c will be matched for the string abc, abbc, dabc, but will not be matched for ac, abdc because there is no b in ac and b is not followed by c in abdc.
{m, n} – Braces
Braces match any repetitions preceding regex from m to n both inclusive. For example –
- a{2, 4} will be matched for the string aaab, baaaac, gaad, but will not be matched for strings like abc, bc because there is only one a or no a in both the cases.
(<regex>) – Group
Group symbol is used to group sub-patterns. For example –
- (a|b)cd will match for strings like acd, abcd, gacd, etc.
Special Sequences
Special sequences do not match for the actual character in the string instead it tells the specific location in the search string where the match must occur. It makes it easier to write commonly used patterns.
List of special sequences
Special Sequence | Description | Examples | |
---|---|---|---|
A | Matches if the string begins with the given character | Afor | for geeks |
for the world | |||
b | Matches if the word begins or ends with the given character. b(string) will check for the beginning of the word and (string)b will check for the ending of the word. | bge | geeks |
get | |||
B | It is the opposite of the b i.e. the string should not start or end with the given regex. | Bge | together |
forge | |||
d | Matches any decimal digit, this is equivalent to the set class [0-9] | d | 123 |
gee1 | |||
D | Matches any non-digit character, this is equivalent to the set class [^0-9] | D | geeks |
geek1 | |||
s | Matches any whitespace character. | s | gee ks |
a bc a | |||
S | Matches any non-whitespace character | S | a bd |
abcd | |||
w | Matches any alphanumeric character, this is equivalent to the class [a-zA-Z0-9_]. | w | 123 |
geeKs4 | |||
W | Matches any non-alphanumeric character. | W | >$ |
gee<> | |||
Z | Matches if the string ends with the given regex | abZ | abcdab |
abababab |
Regex Module in Python
Python has a module named re that is used for regular expressions in Python. We can import this module by using the import statement.
Example: Importing re module in Python
Python3
Let’s see various functions provided by this module to work with regex in Python.
re.findall()
Return all non-overlapping matches of pattern in string, as a list of strings. The string is scanned left-to-right, and matches are returned in the order found.
Example: Finding all occurrences of a pattern
Python3
import
re
string
=
regex
=
'd+'
match
=
re.findall(regex, string)
print
(match)
Output
['123456789', '987654321']
re.compile()
Regular expressions are compiled into pattern objects, which have methods for various operations such as searching for pattern matches or performing string substitutions.
Example 1:
Python
import
re
p
=
re.
compile
(
'[a-e]'
)
print
(p.findall(
"Aye, said Mr. Gibenson Stark"
))
Output
['e', 'a', 'd', 'b', 'e', 'a']
Output:
['e', 'a', 'd', 'b', 'e', 'a']
Understanding the Output:
- First occurrence is ‘e’ in “Aye” and not ‘A’, as it is Case Sensitive.
- Next Occurrence is ‘a’ in “said”, then ‘d’ in “said”, followed by ‘b’ and ‘e’ in “Gibenson”, the Last ‘a’ matches with “Stark”.
- Metacharacter backslash ‘’ has a very important role as it signals various sequences. If the backslash is to be used without its special meaning as metacharacter, use’\’
Example 2: Set class [s,.] will match any whitespace character, ‘,’, or, ‘.’ .
Python
import
re
p
=
re.
compile
(
'd'
)
print
(p.findall(
"I went to him at 11 A.M. on 4th July 1886"
))
p
=
re.
compile
(
'd+'
)
print
(p.findall(
"I went to him at 11 A.M. on 4th July 1886"
))
Output
['1', '1', '4', '1', '8', '8', '6'] ['11', '4', '1886']
Output:
['1', '1', '4', '1', '8', '8', '6'] ['11', '4', '1886']
Example 3:
Python
import
re
p
=
re.
compile
(
'w'
)
print
(p.findall(
"He said * in some_lang."
))
p
=
re.
compile
(
'w+'
)
print
(p.findall("I went to him at
11
A.M., he
said
*
*
*
in
some_language."))
p
=
re.
compile
(
'W'
)
print
(p.findall(
"he said *** in some_language."
))
Output
['H', 'e', 's', 'a', 'i', 'd', 'i', 'n', 's', 'o', 'm', 'e', '_', 'l', 'a', 'n', 'g'] ['I', 'went', 'to', 'him', 'at', '11', 'A', 'M', 'he', 'said', 'in', 'some_language'] [' ', ' ', '*', '*', '*', ' ', ' ', '.']
Output:
['H', 'e', 's', 'a', 'i', 'd', 'i', 'n', 's', 'o', 'm', 'e', '_', 'l', 'a', 'n', 'g'] ['I', 'went', 'to', 'him', 'at', '11', 'A', 'M', 'he', 'said', 'in', 'some_language'] [' ', ' ', '*', '*', '*', ' ', ' ', '.']
Example 4:
Python
import
re
p
=
re.
compile
(
'ab*'
)
print
(p.findall(
"ababbaabbb"
))
Output
['ab', 'abb', 'a', 'abbb']
Output:
['ab', 'abb', 'a', 'abbb']
Understanding the Output:
- Our RE is ab*, which ‘a’ accompanied by any no. of ‘b’s, starting from 0.
- Output ‘ab’, is valid because of single ‘a’ accompanied by single ‘b’.
- Output ‘abb’, is valid because of single ‘a’ accompanied by 2 ‘b’.
- Output ‘a’, is valid because of single ‘a’ accompanied by 0 ‘b’.
- Output ‘abbb’, is valid because of single ‘a’ accompanied by 3 ‘b’.
re.split()
Split string by the occurrences of a character or a pattern, upon finding that pattern, the remaining characters from the string are returned as part of the resulting list.
Syntax :
re.split(pattern, string, maxsplit=0, flags=0)
The First parameter, pattern denotes the regular expression, string is the given string in which pattern will be searched for and in which splitting occurs, maxsplit if not provided is considered to be zero ‘0’, and if any nonzero value is provided, then at most that many splits occur. If maxsplit = 1, then the string will split once only, resulting in a list of length 2. The flags are very useful and can help to shorten code, they are not necessary parameters, eg: flags = re.IGNORECASE, in this split, the case, i.e. the lowercase or the uppercase will be ignored.
Example 1:
Python
from
re
import
split
print
(split(
'W+'
,
'Words, words , Words'
))
print
(split(
'W+'
,
"Word's words Words"
))
print
(split(
'W+'
,
'On 12th Jan 2016, at 11:02 AM'
))
print
(split(
'd+'
,
'On 12th Jan 2016, at 11:02 AM'
))
Output
['Words', 'words', 'Words'] ['Word', 's', 'words', 'Words'] ['On', '12th', 'Jan', '2016', 'at', '11', '02', 'AM'] ['On ', 'th Jan ', ', at ', ':', ' AM']
Output:
['Words', 'words', 'Words'] ['Word', 's', 'words', 'Words'] ['On', '12th', 'Jan', '2016', 'at', '11', '02', 'AM'] ['On ', 'th Jan ', ', at ', ':', ' AM']
Example 2:
Python
import
re
print
(re.split(
'd+'
,
'On 12th Jan 2016, at 11:02 AM'
,
1
))
print
(re.split(
'[a-f]+'
,
'Aey, Boy oh boy, come here'
, flags
=
re.IGNORECASE))
print
(re.split(
'[a-f]+'
,
'Aey, Boy oh boy, come here'
))
Output
['On ', 'th Jan 2016, at 11:02 AM'] ['', 'y, ', 'oy oh ', 'oy, ', 'om', ' h', 'r', ''] ['A', 'y, Boy oh ', 'oy, ', 'om', ' h', 'r', '']
Output:
['On ', 'th Jan 2016, at 11:02 AM'] ['', 'y, ', 'oy oh ', 'oy, ', 'om', ' h', 'r', ''] ['A', 'y, Boy oh ', 'oy, ', 'om', ' h', 'r', '']
re.sub()
The ‘sub’ in the function stands for SubString, a certain regular expression pattern is searched in the given string(3rd parameter), and upon finding the substring pattern is replaced by repl(2nd parameter), count checks and maintains the number of times this occurs.
Syntax:
re.sub(pattern, repl, string, count=0, flags=0)
Example 1:
Python
import
re
print
(re.sub(
'ub'
,
'~*'
,
'Subject has Uber booked already'
,
flags
=
re.IGNORECASE))
print
(re.sub(
'ub'
,
'~*'
,
'Subject has Uber booked already'
))
print
(re.sub(
'ub'
,
'~*'
,
'Subject has Uber booked already'
,
count
=
1
, flags
=
re.IGNORECASE))
print
(re.sub(r
'sANDs'
,
' & '
,
'Baked Beans And Spam'
,
flags
=
re.IGNORECASE))
Output
S~*ject has ~*er booked already S~*ject has Uber booked already S~*ject has Uber booked already Baked Beans & Spam
Output
S~*ject has ~*er booked already S~*ject has Uber booked already S~*ject has Uber booked already Baked Beans & Spam
re.subn()
subn() is similar to sub() in all ways, except in its way of providing output. It returns a tuple with a count of the total of replacement and the new string rather than just the string.
Syntax:
re.subn(pattern, repl, string, count=0, flags=0)
Example:
Python
import
re
print
(re.subn(
'ub'
,
'~*'
,
'Subject has Uber booked already'
))
t
=
re.subn(
'ub'
,
'~*'
,
'Subject has Uber booked already'
,
flags
=
re.IGNORECASE)
print
(t)
print
(
len
(t))
print
(t[
0
])
Output
('S~*ject has Uber booked already', 1) ('S~*ject has ~*er booked already', 2) 2 S~*ject has ~*er booked already
Output
('S~*ject has Uber booked already', 1) ('S~*ject has ~*er booked already', 2) Length of Tuple is: 2 S~*ject has ~*er booked already
re.escape()
Returns string with all non-alphanumerics backslashed, this is useful if you want to match an arbitrary literal string that may have regular expression metacharacters in it.
Syntax:
re.escape(string)
Example:
Python
import
re
print
(re.escape(
"This is Awesome even 1 AM"
))
print
(re.escape(
"I Asked what is this [a-9], he said t ^WoW"
))
Output
This is Awesome even 1 AM I Asked what is this [a-9], he said ^WoW
re.search()
This method either returns None (if the pattern doesn’t match), or a re.MatchObject contains information about the matching part of the string. This method stops after the first match, so this is best suited for testing a regular expression more than extracting data.
Example: Searching for an occurrence of the pattern
Python3
import
re
regex
=
r
"([a-zA-Z]+) (d+)"
match
=
re.search(regex,
"I was born on June 24"
)
if
match !
=
None
:
print
(
"Match at index %s, %s"
%
(match.start(), match.end()))
print
(
"Full match: %s"
%
(match.group(
0
)))
print
(
"Month: %s"
%
(match.group(
1
)))
print
(
"Day: %s"
%
(match.group(
2
)))
else
:
print
(
"The regex pattern does not match."
)
Output
Match at index 14, 21 Full match: June 24 Month: June Day: 24
Match Object
A Match object contains all the information about the search and the result and if there is no match found then None will be returned. Let’s see some of the commonly used methods and attributes of the match object.
Getting the string and the regex
match.re attribute returns the regular expression passed and match.string attribute returns the string passed.
Example: Getting the string and the regex of the matched object
Python3
import
re
s
=
"Welcome to GeeksForGeeks"
res
=
re.search(r
"bG"
, s)
print
(res.re)
print
(res.string)
Output
re.compile('\bG') Welcome to GeeksForGeeks
Getting index of matched object
- start() method returns the starting index of the matched substring
- end() method returns the ending index of the matched substring
- span() method returns a tuple containing the starting and the ending index of the matched substring
Example: Getting index of matched object
Python3
import
re
s
=
"Welcome to GeeksForGeeks"
res
=
re.search(r
"bGee"
, s)
print
(res.start())
print
(res.end())
print
(res.span())
Getting matched substring
group() method returns the part of the string for which the patterns match. See the below example for a better understanding.
Example: Getting matched substring
Python3
import
re
s
=
"Welcome to GeeksForGeeks"
res
=
re.search(r
"D{2} t"
, s)
print
(res.group())
In the above example, our pattern specifies for the string that contains at least 2 characters which are followed by a space, and that space is followed by a t.
Related Article :
https://www.geeksforgeeks.org/regular-expressions-python-set-1-search-match-find/
Reference:
https://docs.python.org/2/library/re.html
This article is contributed by Piyush Doorwar. If you like GeeksforGeeks and would like to contribute, you can also write an article using write.geeksforgeeks.org or mail your article to review-team@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks.
Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above.
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Regular Expressions and Building Regexes in Python
In this tutorial, you’ll explore regular expressions, also known as regexes, in Python. A regex is a special sequence of characters that defines a pattern for complex string-matching functionality.
Earlier in this series, in the tutorial Strings and Character Data in Python, you learned how to define and manipulate string objects. Since then, you’ve seen some ways to determine whether two strings match each other:
-
You can test whether two strings are equal using the equality (
==
) operator. -
You can test whether one string is a substring of another with the
in
operator or the built-in string methods.find()
and.index()
.
String matching like this is a common task in programming, and you can get a lot done with string operators and built-in methods. At times, though, you may need more sophisticated pattern-matching capabilities.
In this tutorial, you’ll learn:
- How to access the
re
module, which implements regex matching in Python - How to use
re.search()
to match a pattern against a string - How to create complex matching pattern with regex metacharacters
Fasten your seat belt! Regex syntax takes a little getting used to. But once you get comfortable with it, you’ll find regexes almost indispensable in your Python programming.
Regexes in Python and Their Uses
Imagine you have a string object s
. Now suppose you need to write Python code to find out whether s
contains the substring '123'
. There are at least a couple ways to do this. You could use the in
operator:
>>>
>>> s = 'foo123bar'
>>> '123' in s
True
If you want to know not only whether '123'
exists in s
but also where it exists, then you can use .find()
or .index()
. Each of these returns the character position within s
where the substring resides:
>>>
>>> s = 'foo123bar'
>>> s.find('123')
3
>>> s.index('123')
3
In these examples, the matching is done by a straightforward character-by-character comparison. That will get the job done in many cases. But sometimes, the problem is more complicated than that.
For example, rather than searching for a fixed substring like '123'
, suppose you wanted to determine whether a string contains any three consecutive decimal digit characters, as in the strings 'foo123bar'
, 'foo456bar'
, '234baz'
, and 'qux678'
.
Strict character comparisons won’t cut it here. This is where regexes in Python come to the rescue.
A (Very Brief) History of Regular Expressions
In 1951, mathematician Stephen Cole Kleene described the concept of a regular language, a language that is recognizable by a finite automaton and formally expressible using regular expressions. In the mid-1960s, computer science pioneer Ken Thompson, one of the original designers of Unix, implemented pattern matching in the QED text editor using Kleene’s notation.
Since then, regexes have appeared in many programming languages, editors, and other tools as a means of determining whether a string matches a specified pattern. Python, Java, and Perl all support regex functionality, as do most Unix tools and many text editors.
The re
Module
Regex functionality in Python resides in a module named re
. The re
module contains many useful functions and methods, most of which you’ll learn about in the next tutorial in this series.
For now, you’ll focus predominantly on one function, re.search()
.
re.search(<regex>, <string>)
Scans a string for a regex match.
re.search(<regex>, <string>)
scans <string>
looking for the first location where the pattern <regex>
matches. If a match is found, then re.search()
returns a match object. Otherwise, it returns None
.
re.search()
takes an optional third <flags>
argument that you’ll learn about at the end of this tutorial.
How to Import re.search()
Because search()
resides in the re
module, you need to import it before you can use it. One way to do this is to import the entire module and then use the module name as a prefix when calling the function:
Alternatively, you can import the function from the module by name and then refer to it without the module name prefix:
from re import search
search(...)
You’ll always need to import re.search()
by one means or another before you’ll be able to use it.
The examples in the remainder of this tutorial will assume the first approach shown—importing the re
module and then referring to the function with the module name prefix: re.search()
. For the sake of brevity, the import re
statement will usually be omitted, but remember that it’s always necessary.
For more information on importing from modules and packages, check out Python Modules and Packages—An Introduction.
First Pattern-Matching Example
Now that you know how to gain access to re.search()
, you can give it a try:
>>>
1>>> s = 'foo123bar'
2
3>>> # One last reminder to import!
4>>> import re
5
6>>> re.search('123', s)
7<_sre.SRE_Match object; span=(3, 6), match='123'>
Here, the search pattern <regex>
is 123
and <string>
is s
. The returned match object appears on line 7. Match objects contain a wealth of useful information that you’ll explore soon.
For the moment, the important point is that re.search()
did in fact return a match object rather than None
. That tells you that it found a match. In other words, the specified <regex>
pattern 123
is present in s
.
A match object is truthy, so you can use it in a Boolean context like a conditional statement:
>>>
>>> if re.search('123', s):
... print('Found a match.')
... else:
... print('No match.')
...
Found a match.
The interpreter displays the match object as <_sre.SRE_Match object; span=(3, 6), match='123'>
. This contains some useful information.
span=(3, 6)
indicates the portion of <string>
in which the match was found. This means the same thing as it would in slice notation:
In this example, the match starts at character position 3
and extends up to but not including position 6
.
match='123'
indicates which characters from <string>
matched.
This is a good start. But in this case, the <regex>
pattern is just the plain string '123'
. The pattern matching here is still just character-by-character comparison, pretty much the same as the in
operator and .find()
examples shown earlier. The match object helpfully tells you that the matching characters were '123'
, but that’s not much of a revelation since those were exactly the characters you searched for.
You’re just getting warmed up.
Python Regex Metacharacters
The real power of regex matching in Python emerges when <regex>
contains special characters called metacharacters. These have a unique meaning to the regex matching engine and vastly enhance the capability of the search.
Consider again the problem of how to determine whether a string contains any three consecutive decimal digit characters.
In a regex, a set of characters specified in square brackets ([]
) makes up a character class. This metacharacter sequence matches any single character that is in the class, as demonstrated in the following example:
>>>
>>> s = 'foo123bar'
>>> re.search('[0-9][0-9][0-9]', s)
<_sre.SRE_Match object; span=(3, 6), match='123'>
[0-9]
matches any single decimal digit character—any character between '0'
and '9'
, inclusive. The full expression [0-9][0-9][0-9]
matches any sequence of three decimal digit characters. In this case, s
matches because it contains three consecutive decimal digit characters, '123'
.
These strings also match:
>>>
>>> re.search('[0-9][0-9][0-9]', 'foo456bar')
<_sre.SRE_Match object; span=(3, 6), match='456'>
>>> re.search('[0-9][0-9][0-9]', '234baz')
<_sre.SRE_Match object; span=(0, 3), match='234'>
>>> re.search('[0-9][0-9][0-9]', 'qux678')
<_sre.SRE_Match object; span=(3, 6), match='678'>
On the other hand, a string that doesn’t contain three consecutive digits won’t match:
>>>
>>> print(re.search('[0-9][0-9][0-9]', '12foo34'))
None
With regexes in Python, you can identify patterns in a string that you wouldn’t be able to find with the in
operator or with string methods.
Take a look at another regex metacharacter. The dot (.
) metacharacter matches any character except a newline, so it functions like a wildcard:
>>>
>>> s = 'foo123bar'
>>> re.search('1.3', s)
<_sre.SRE_Match object; span=(3, 6), match='123'>
>>> s = 'foo13bar'
>>> print(re.search('1.3', s))
None
In the first example, the regex 1.3
matches '123'
because the '1'
and '3'
match literally, and the .
matches the '2'
. Here, you’re essentially asking, “Does s
contain a '1'
, then any character (except a newline), then a '3'
?” The answer is yes for 'foo123bar'
but no for 'foo13bar'
.
These examples provide a quick illustration of the power of regex metacharacters. Character class and dot are but two of the metacharacters supported by the re
module. There are many more. Next, you’ll explore them fully.
Metacharacters Supported by the re
Module
The following table briefly summarizes all the metacharacters supported by the re
module. Some characters serve more than one purpose:
Character(s) | Meaning |
---|---|
. |
Matches any single character except newline |
^ |
∙ Anchors a match at the start of a string ∙ Complements a character class |
$ |
Anchors a match at the end of a string |
* |
Matches zero or more repetitions |
+ |
Matches one or more repetitions |
? |
∙ Matches zero or one repetition ∙ Specifies the non-greedy versions of * , + , and ? ∙ Introduces a lookahead or lookbehind assertion ∙ Creates a named group |
{} |
Matches an explicitly specified number of repetitions |
|
∙ Escapes a metacharacter of its special meaning ∙ Introduces a special character class ∙ Introduces a grouping backreference |
[] |
Specifies a character class |
| |
Designates alternation |
() |
Creates a group |
: # = ! |
Designate a specialized group |
<> |
Creates a named group |
This may seem like an overwhelming amount of information, but don’t panic! The following sections go over each one of these in detail.
The regex parser regards any character not listed above as an ordinary character that matches only itself. For example, in the first pattern-matching example shown above, you saw this:
>>>
>>> s = 'foo123bar'
>>> re.search('123', s)
<_sre.SRE_Match object; span=(3, 6), match='123'>
In this case, 123
is technically a regex, but it’s not a very interesting one because it doesn’t contain any metacharacters. It just matches the string '123'
.
Things get much more exciting when you throw metacharacters into the mix. The following sections explain in detail how you can use each metacharacter or metacharacter sequence to enhance pattern-matching functionality.
Metacharacters That Match a Single Character
The metacharacter sequences in this section try to match a single character from the search string. When the regex parser encounters one of these metacharacter sequences, a match happens if the character at the current parsing position fits the description that the sequence describes.
[]
Specifies a specific set of characters to match.
Characters contained in square brackets ([]
) represent a character class—an enumerated set of characters to match from. A character class metacharacter sequence will match any single character contained in the class.
You can enumerate the characters individually like this:
>>>
>>> re.search('ba[artz]', 'foobarqux')
<_sre.SRE_Match object; span=(3, 6), match='bar'>
>>> re.search('ba[artz]', 'foobazqux')
<_sre.SRE_Match object; span=(3, 6), match='baz'>
The metacharacter sequence [artz]
matches any single 'a'
, 'r'
, 't'
, or 'z'
character. In the example, the regex ba[artz]
matches both 'bar'
and 'baz'
(and would also match 'baa'
and 'bat'
).
A character class can also contain a range of characters separated by a hyphen (-
), in which case it matches any single character within the range. For example, [a-z]
matches any lowercase alphabetic character between 'a'
and 'z'
, inclusive:
>>>
>>> re.search('[a-z]', 'FOObar')
<_sre.SRE_Match object; span=(3, 4), match='b'>
[0-9]
matches any digit character:
>>>
>>> re.search('[0-9][0-9]', 'foo123bar')
<_sre.SRE_Match object; span=(3, 5), match='12'>
In this case, [0-9][0-9]
matches a sequence of two digits. The first portion of the string 'foo123bar'
that matches is '12'
.
[0-9a-fA-F]
matches any hexadecimal digit character:
>>>
>>> re.search('[0-9a-fA-f]', '--- a0 ---')
<_sre.SRE_Match object; span=(4, 5), match='a'>
Here, [0-9a-fA-F]
matches the first hexadecimal digit character in the search string, 'a'
.
You can complement a character class by specifying ^
as the first character, in which case it matches any character that isn’t in the set. In the following example, [^0-9]
matches any character that isn’t a digit:
>>>
>>> re.search('[^0-9]', '12345foo')
<_sre.SRE_Match object; span=(5, 6), match='f'>
Here, the match object indicates that the first character in the string that isn’t a digit is 'f'
.
If a ^
character appears in a character class but isn’t the first character, then it has no special meaning and matches a literal '^'
character:
>>>
>>> re.search('[#:^]', 'foo^bar:baz#qux')
<_sre.SRE_Match object; span=(3, 4), match='^'>
As you’ve seen, you can specify a range of characters in a character class by separating characters with a hyphen. What if you want the character class to include a literal hyphen character? You can place it as the first or last character or escape it with a backslash ():
>>>
>>> re.search('[-abc]', '123-456')
<_sre.SRE_Match object; span=(3, 4), match='-'>
>>> re.search('[abc-]', '123-456')
<_sre.SRE_Match object; span=(3, 4), match='-'>
>>> re.search('[ab-c]', '123-456')
<_sre.SRE_Match object; span=(3, 4), match='-'>
If you want to include a literal ']'
in a character class, then you can place it as the first character or escape it with backslash:
>>>
>>> re.search('[]]', 'foo[1]')
<_sre.SRE_Match object; span=(5, 6), match=']'>
>>> re.search('[ab]cd]', 'foo[1]')
<_sre.SRE_Match object; span=(5, 6), match=']'>
Other regex metacharacters lose their special meaning inside a character class:
>>>
>>> re.search('[)*+|]', '123*456')
<_sre.SRE_Match object; span=(3, 4), match='*'>
>>> re.search('[)*+|]', '123+456')
<_sre.SRE_Match object; span=(3, 4), match='+'>
As you saw in the table above, *
and +
have special meanings in a regex in Python. They designate repetition, which you’ll learn more about shortly. But in this example, they’re inside a character class, so they match themselves literally.
dot (.
)
Specifies a wildcard.
The .
metacharacter matches any single character except a newline:
>>>
>>> re.search('foo.bar', 'fooxbar')
<_sre.SRE_Match object; span=(0, 7), match='fooxbar'>
>>> print(re.search('foo.bar', 'foobar'))
None
>>> print(re.search('foo.bar', 'foonbar'))
None
As a regex, foo.bar
essentially means the characters 'foo'
, then any character except newline, then the characters 'bar'
. The first string shown above, 'fooxbar'
, fits the bill because the .
metacharacter matches the 'x'
.
The second and third strings fail to match. In the last case, although there’s a character between 'foo'
and 'bar'
, it’s a newline, and by default, the .
metacharacter doesn’t match a newline. There is, however, a way to force .
to match a newline, which you’ll learn about at the end of this tutorial.
w
W
Match based on whether a character is a word character.
w
matches any alphanumeric word character. Word characters are uppercase and lowercase letters, digits, and the underscore (_
) character, so w
is essentially shorthand for [a-zA-Z0-9_]
:
>>>
>>> re.search('w', '#(.a$@&')
<_sre.SRE_Match object; span=(3, 4), match='a'>
>>> re.search('[a-zA-Z0-9_]', '#(.a$@&')
<_sre.SRE_Match object; span=(3, 4), match='a'>
In this case, the first word character in the string '#(.a$@&'
is 'a'
.
W
is the opposite. It matches any non-word character and is equivalent to [^a-zA-Z0-9_]
:
>>>
>>> re.search('W', 'a_1*3Qb')
<_sre.SRE_Match object; span=(3, 4), match='*'>
>>> re.search('[^a-zA-Z0-9_]', 'a_1*3Qb')
<_sre.SRE_Match object; span=(3, 4), match='*'>
Here, the first non-word character in 'a_1*3!b'
is '*'
.
d
D
Match based on whether a character is a decimal digit.
d
matches any decimal digit character. D
is the opposite. It matches any character that isn’t a decimal digit:
>>>
>>> re.search('d', 'abc4def')
<_sre.SRE_Match object; span=(3, 4), match='4'>
>>> re.search('D', '234Q678')
<_sre.SRE_Match object; span=(3, 4), match='Q'>
d
is essentially equivalent to [0-9]
, and D
is equivalent to [^0-9]
.
s
S
Match based on whether a character represents whitespace.
s
matches any whitespace character:
>>>
>>> re.search('s', 'foonbar baz')
<_sre.SRE_Match object; span=(3, 4), match='n'>
Note that, unlike the dot wildcard metacharacter, s
does match a newline character.
S
is the opposite of s
. It matches any character that isn’t whitespace:
>>>
>>> re.search('S', ' n foo n ')
<_sre.SRE_Match object; span=(4, 5), match='f'>
Again, s
and S
consider a newline to be whitespace. In the example above, the first non-whitespace character is 'f'
.
The character class sequences w
, W
, d
, D
, s
, and S
can appear inside a square bracket character class as well:
>>>
>>> re.search('[dws]', '---3---')
<_sre.SRE_Match object; span=(3, 4), match='3'>
>>> re.search('[dws]', '---a---')
<_sre.SRE_Match object; span=(3, 4), match='a'>
>>> re.search('[dws]', '--- ---')
<_sre.SRE_Match object; span=(3, 4), match=' '>
In this case, [dws]
matches any digit, word, or whitespace character. And since w
includes d
, the same character class could also be expressed slightly shorter as [ws]
.
Escaping Metacharacters
Occasionally, you’ll want to include a metacharacter in your regex, except you won’t want it to carry its special meaning. Instead, you’ll want it to represent itself as a literal character.
backslash ()
Removes the special meaning of a metacharacter.
As you’ve just seen, the backslash character can introduce special character classes like word, digit, and whitespace. There are also special metacharacter sequences called anchors that begin with a backslash, which you’ll learn about below.
When it’s not serving either of these purposes, the backslash escapes metacharacters. A metacharacter preceded by a backslash loses its special meaning and matches the literal character instead. Consider the following examples:
>>>
1>>> re.search('.', 'foo.bar')
2<_sre.SRE_Match object; span=(0, 1), match='f'>
3
4>>> re.search('.', 'foo.bar')
5<_sre.SRE_Match object; span=(3, 4), match='.'>
In the <regex>
on line 1, the dot (.
) functions as a wildcard metacharacter, which matches the first character in the string ('f'
). The .
character in the <regex>
on line 4 is escaped by a backslash, so it isn’t a wildcard. It’s interpreted literally and matches the '.'
at index 3
of the search string.
Using backslashes for escaping can get messy. Suppose you have a string that contains a single backslash:
>>>
>>> s = r'foobar'
>>> print(s)
foobar
Now suppose you want to create a <regex>
that will match the backslash between 'foo'
and 'bar'
. The backslash is itself a special character in a regex, so to specify a literal backslash, you need to escape it with another backslash. If that’s that case, then the following should work:
>>>
>>> re.search('\', s)
Not quite. This is what you get if you try it:
>>>
>>> re.search('\', s)
Traceback (most recent call last):
File "<pyshell#3>", line 1, in <module>
re.search('\', s)
File "C:Python36libre.py", line 182, in search
return _compile(pattern, flags).search(string)
File "C:Python36libre.py", line 301, in _compile
p = sre_compile.compile(pattern, flags)
File "C:Python36libsre_compile.py", line 562, in compile
p = sre_parse.parse(p, flags)
File "C:Python36libsre_parse.py", line 848, in parse
source = Tokenizer(str)
File "C:Python36libsre_parse.py", line 231, in __init__
self.__next()
File "C:Python36libsre_parse.py", line 245, in __next
self.string, len(self.string) - 1) from None
sre_constants.error: bad escape (end of pattern) at position 0
Oops. What happened?
The problem here is that the backslash escaping happens twice, first by the Python interpreter on the string literal and then again by the regex parser on the regex it receives.
Here’s the sequence of events:
- The Python interpreter is the first to process the string literal
'\'
. It interprets that as an escaped backslash and passes only a single backslash tore.search()
. - The regex parser receives just a single backslash, which isn’t a meaningful regex, so the messy error ensues.
There are two ways around this. First, you can escape both backslashes in the original string literal:
>>>
>>> re.search('\\', s)
<_sre.SRE_Match object; span=(3, 4), match='\'>
Doing so causes the following to happen:
- The interpreter sees
'\\'
as a pair of escaped backslashes. It reduces each pair to a single backslash and passes'\'
to the regex parser. - The regex parser then sees
\
as one escaped backslash. As a<regex>
, that matches a single backslash character. You can see from the match object that it matched the backslash at index3
ins
as intended. It’s cumbersome, but it works.
The second, and probably cleaner, way to handle this is to specify the <regex>
using a raw string:
>>>
>>> re.search(r'\', s)
<_sre.SRE_Match object; span=(3, 4), match='\'>
This suppresses the escaping at the interpreter level. The string '\'
gets passed unchanged to the regex parser, which again sees one escaped backslash as desired.
It’s good practice to use a raw string to specify a regex in Python whenever it contains backslashes.
Anchors
Anchors are zero-width matches. They don’t match any actual characters in the search string, and they don’t consume any of the search string during parsing. Instead, an anchor dictates a particular location in the search string where a match must occur.
^
A
Anchor a match to the start of
<string>
.
When the regex parser encounters ^
or A
, the parser’s current position must be at the beginning of the search string for it to find a match.
In other words, regex ^foo
stipulates that 'foo'
must be present not just any old place in the search string, but at the beginning:
>>>
>>> re.search('^foo', 'foobar')
<_sre.SRE_Match object; span=(0, 3), match='foo'>
>>> print(re.search('^foo', 'barfoo'))
None
A
functions similarly:
>>>
>>> re.search('Afoo', 'foobar')
<_sre.SRE_Match object; span=(0, 3), match='foo'>
>>> print(re.search('Afoo', 'barfoo'))
None
^
and A
behave slightly differently from each other in MULTILINE
mode. You’ll learn more about MULTILINE
mode below in the section on flags.
$
Z
Anchor a match to the end of
<string>
.
When the regex parser encounters $
or Z
, the parser’s current position must be at the end of the search string for it to find a match. Whatever precedes $
or Z
must constitute the end of the search string:
>>>
>>> re.search('bar$', 'foobar')
<_sre.SRE_Match object; span=(3, 6), match='bar'>
>>> print(re.search('bar$', 'barfoo'))
None
>>> re.search('barZ', 'foobar')
<_sre.SRE_Match object; span=(3, 6), match='bar'>
>>> print(re.search('barZ', 'barfoo'))
None
As a special case, $
(but not Z
) also matches just before a single newline at the end of the search string:
>>>
>>> re.search('bar$', 'foobarn')
<_sre.SRE_Match object; span=(3, 6), match='bar'>
In this example, 'bar'
isn’t technically at the end of the search string because it’s followed by one additional newline character. But the regex parser lets it slide and calls it a match anyway. This exception doesn’t apply to Z
.
$
and Z
behave slightly differently from each other in MULTILINE
mode. See the section below on flags for more information on MULTILINE
mode.
b
Anchors a match to a word boundary.
b
asserts that the regex parser’s current position must be at the beginning or end of a word. A word consists of a sequence of alphanumeric characters or underscores ([a-zA-Z0-9_]
), the same as for the w
character class:
>>>
1>>> re.search(r'bbar', 'foo bar')
2<_sre.SRE_Match object; span=(4, 7), match='bar'>
3>>> re.search(r'bbar', 'foo.bar')
4<_sre.SRE_Match object; span=(4, 7), match='bar'>
5
6>>> print(re.search(r'bbar', 'foobar'))
7None
8
9>>> re.search(r'foob', 'foo bar')
10<_sre.SRE_Match object; span=(0, 3), match='foo'>
11>>> re.search(r'foob', 'foo.bar')
12<_sre.SRE_Match object; span=(0, 3), match='foo'>
13
14>>> print(re.search(r'foob', 'foobar'))
15None
In the above examples, a match happens on lines 1 and 3 because there’s a word boundary at the start of 'bar'
. This isn’t the case on line 6, so the match fails there.
Similarly, there are matches on lines 9 and 11 because a word boundary exists at the end of 'foo'
, but not on line 14.
Using the b
anchor on both ends of the <regex>
will cause it to match when it’s present in the search string as a whole word:
>>>
>>> re.search(r'bbarb', 'foo bar baz')
<_sre.SRE_Match object; span=(4, 7), match='bar'>
>>> re.search(r'bbarb', 'foo(bar)baz')
<_sre.SRE_Match object; span=(4, 7), match='bar'>
>>> print(re.search(r'bbarb', 'foobarbaz'))
None
This is another instance in which it pays to specify the <regex>
as a raw string, as the above examples have done.
Because 'b'
is an escape sequence for both string literals and regexes in Python, each use above would need to be double escaped as '\b'
if you didn’t use raw strings. That wouldn’t be the end of the world, but raw strings are tidier.
B
Anchors a match to a location that isn’t a word boundary.
B
does the opposite of b
. It asserts that the regex parser’s current position must not be at the start or end of a word:
>>>
1>>> print(re.search(r'BfooB', 'foo'))
2None
3>>> print(re.search(r'BfooB', '.foo.'))
4None
5
6>>> re.search(r'BfooB', 'barfoobaz')
7<_sre.SRE_Match object; span=(3, 6), match='foo'>
In this case, a match happens on line 7 because no word boundary exists at the start or end of 'foo'
in the search string 'barfoobaz'
.
Quantifiers
A quantifier metacharacter immediately follows a portion of a <regex>
and indicates how many times that portion must occur for the match to succeed.
*
Matches zero or more repetitions of the preceding regex.
For example, a*
matches zero or more 'a'
characters. That means it would match an empty string, 'a'
, 'aa'
, 'aaa'
, and so on.
Consider these examples:
>>>
1>>> re.search('foo-*bar', 'foobar') # Zero dashes
2<_sre.SRE_Match object; span=(0, 6), match='foobar'>
3>>> re.search('foo-*bar', 'foo-bar') # One dash
4<_sre.SRE_Match object; span=(0, 7), match='foo-bar'>
5>>> re.search('foo-*bar', 'foo--bar') # Two dashes
6<_sre.SRE_Match object; span=(0, 8), match='foo--bar'>
On line 1, there are zero '-'
characters between 'foo'
and 'bar'
. On line 3 there’s one, and on line 5 there are two. The metacharacter sequence -*
matches in all three cases.
You’ll probably encounter the regex .*
in a Python program at some point. This matches zero or more occurrences of any character. In other words, it essentially matches any character sequence up to a line break. (Remember that the .
wildcard metacharacter doesn’t match a newline.)
In this example, .*
matches everything between 'foo'
and 'bar'
:
>>>
>>> re.search('foo.*bar', '# foo $qux@grault % bar #')
<_sre.SRE_Match object; span=(2, 23), match='foo $qux@grault % bar'>
Did you notice the span=
and match=
information contained in the match object?
Until now, the regexes in the examples you’ve seen have specified matches of predictable length. Once you start using quantifiers like *
, the number of characters matched can be quite variable, and the information in the match object becomes more useful.
You’ll learn more about how to access the information stored in a match object in the next tutorial in the series.
+
Matches one or more repetitions of the preceding regex.
This is similar to *
, but the quantified regex must occur at least once:
>>>
1>>> print(re.search('foo-+bar', 'foobar')) # Zero dashes
2None
3>>> re.search('foo-+bar', 'foo-bar') # One dash
4<_sre.SRE_Match object; span=(0, 7), match='foo-bar'>
5>>> re.search('foo-+bar', 'foo--bar') # Two dashes
6<_sre.SRE_Match object; span=(0, 8), match='foo--bar'>
Remember from above that foo-*bar
matched the string 'foobar'
because the *
metacharacter allows for zero occurrences of '-'
. The +
metacharacter, on the other hand, requires at least one occurrence of '-'
. That means there isn’t a match on line 1 in this case.
?
Matches zero or one repetitions of the preceding regex.
Again, this is similar to *
and +
, but in this case there’s only a match if the preceding regex occurs once or not at all:
>>>
1>>> re.search('foo-?bar', 'foobar') # Zero dashes
2<_sre.SRE_Match object; span=(0, 6), match='foobar'>
3>>> re.search('foo-?bar', 'foo-bar') # One dash
4<_sre.SRE_Match object; span=(0, 7), match='foo-bar'>
5>>> print(re.search('foo-?bar', 'foo--bar')) # Two dashes
6None
In this example, there are matches on lines 1 and 3. But on line 5, where there are two '-'
characters, the match fails.
Here are some more examples showing the use of all three quantifier metacharacters:
>>>
>>> re.match('foo[1-9]*bar', 'foobar')
<_sre.SRE_Match object; span=(0, 6), match='foobar'>
>>> re.match('foo[1-9]*bar', 'foo42bar')
<_sre.SRE_Match object; span=(0, 8), match='foo42bar'>
>>> print(re.match('foo[1-9]+bar', 'foobar'))
None
>>> re.match('foo[1-9]+bar', 'foo42bar')
<_sre.SRE_Match object; span=(0, 8), match='foo42bar'>
>>> re.match('foo[1-9]?bar', 'foobar')
<_sre.SRE_Match object; span=(0, 6), match='foobar'>
>>> print(re.match('foo[1-9]?bar', 'foo42bar'))
None
This time, the quantified regex is the character class [1-9]
instead of the simple character '-'
.
*?
+?
??
The non-greedy (or lazy) versions of the
*
,+
, and?
quantifiers.
When used alone, the quantifier metacharacters *
, +
, and ?
are all greedy, meaning they produce the longest possible match. Consider this example:
>>>
>>> re.search('<.*>', '%<foo> <bar> <baz>%')
<_sre.SRE_Match object; span=(1, 18), match='<foo> <bar> <baz>'>
The regex <.*>
effectively means:
- A
'<'
character - Then any sequence of characters
- Then a
'>'
character
But which '>'
character? There are three possibilities:
- The one just after
'foo'
- The one just after
'bar'
- The one just after
'baz'
Since the *
metacharacter is greedy, it dictates the longest possible match, which includes everything up to and including the '>'
character that follows 'baz'
. You can see from the match object that this is the match produced.
If you want the shortest possible match instead, then use the non-greedy metacharacter sequence *?
:
>>>
>>> re.search('<.*?>', '%<foo> <bar> <baz>%')
<_sre.SRE_Match object; span=(1, 6), match='<foo>'>
In this case, the match ends with the '>'
character following 'foo'
.
There are lazy versions of the +
and ?
quantifiers as well:
>>>
1>>> re.search('<.+>', '%<foo> <bar> <baz>%')
2<_sre.SRE_Match object; span=(1, 18), match='<foo> <bar> <baz>'>
3>>> re.search('<.+?>', '%<foo> <bar> <baz>%')
4<_sre.SRE_Match object; span=(1, 6), match='<foo>'>
5
6>>> re.search('ba?', 'baaaa')
7<_sre.SRE_Match object; span=(0, 2), match='ba'>
8>>> re.search('ba??', 'baaaa')
9<_sre.SRE_Match object; span=(0, 1), match='b'>
The first two examples on lines 1 and 3 are similar to the examples shown above, only using +
and +?
instead of *
and *?
.
The last examples on lines 6 and 8 are a little different. In general, the ?
metacharacter matches zero or one occurrences of the preceding regex. The greedy version, ?
, matches one occurrence, so ba?
matches 'b'
followed by a single 'a'
. The non-greedy version, ??
, matches zero occurrences, so ba??
matches just 'b'
.
{m}
Matches exactly
m
repetitions of the preceding regex.
This is similar to *
or +
, but it specifies exactly how many times the preceding regex must occur for a match to succeed:
>>>
>>> print(re.search('x-{3}x', 'x--x')) # Two dashes
None
>>> re.search('x-{3}x', 'x---x') # Three dashes
<_sre.SRE_Match object; span=(0, 5), match='x---x'>
>>> print(re.search('x-{3}x', 'x----x')) # Four dashes
None
Here, x-{3}x
matches 'x'
, followed by exactly three instances of the '-'
character, followed by another 'x'
. The match fails when there are fewer or more than three dashes between the 'x'
characters.
{m,n}
Matches any number of repetitions of the preceding regex from
m
ton
, inclusive.
In the following example, the quantified <regex>
is -{2,4}
. The match succeeds when there are two, three, or four dashes between the 'x'
characters but fails otherwise:
>>>
>>> for i in range(1, 6):
... s = f"x{'-' * i}x"
... print(f'{i} {s:10}', re.search('x-{2,4}x', s))
...
1 x-x None
2 x--x <_sre.SRE_Match object; span=(0, 4), match='x--x'>
3 x---x <_sre.SRE_Match object; span=(0, 5), match='x---x'>
4 x----x <_sre.SRE_Match object; span=(0, 6), match='x----x'>
5 x-----x None
Omitting m
implies a lower bound of 0
, and omitting n
implies an unlimited upper bound:
Regular Expression | Matches | Identical to |
---|---|---|
<regex>{,n} |
Any number of repetitions of <regex> less than or equal to n |
<regex>{0,n} |
<regex>{m,} |
Any number of repetitions of <regex> greater than or equal to m |
---- |
<regex>{,} |
Any number of repetitions of <regex> |
<regex>{0,} <regex>* |
If you omit all of m
, n
, and the comma, then the curly braces no longer function as metacharacters. {}
matches just the literal string '{}'
:
>>>
>>> re.search('x{}y', 'x{}y')
<_sre.SRE_Match object; span=(0, 4), match='x{}y'>
In fact, to have any special meaning, a sequence with curly braces must fit one of the following patterns in which m
and n
are nonnegative integers:
{m,n}
{m,}
{,n}
{,}
Otherwise, it matches literally:
>>>
>>> re.search('x{foo}y', 'x{foo}y')
<_sre.SRE_Match object; span=(0, 7), match='x{foo}y'>
>>> re.search('x{a:b}y', 'x{a:b}y')
<_sre.SRE_Match object; span=(0, 7), match='x{a:b}y'>
>>> re.search('x{1,3,5}y', 'x{1,3,5}y')
<_sre.SRE_Match object; span=(0, 9), match='x{1,3,5}y'>
>>> re.search('x{foo,bar}y', 'x{foo,bar}y')
<_sre.SRE_Match object; span=(0, 11), match='x{foo,bar}y'>
Later in this tutorial, when you learn about the DEBUG
flag, you’ll see how you can confirm this.
{m,n}?
The non-greedy (lazy) version of
{m,n}
.
{m,n}
will match as many characters as possible, and {m,n}?
will match as few as possible:
>>>
>>> re.search('a{3,5}', 'aaaaaaaa')
<_sre.SRE_Match object; span=(0, 5), match='aaaaa'>
>>> re.search('a{3,5}?', 'aaaaaaaa')
<_sre.SRE_Match object; span=(0, 3), match='aaa'>
In this case, a{3,5}
produces the longest possible match, so it matches five 'a'
characters. a{3,5}?
produces the shortest match, so it matches three.
Grouping Constructs and Backreferences
Grouping constructs break up a regex in Python into subexpressions or groups. This serves two purposes:
- Grouping: A group represents a single syntactic entity. Additional metacharacters apply to the entire group as a unit.
- Capturing: Some grouping constructs also capture the portion of the search string that matches the subexpression in the group. You can retrieve captured matches later through several different mechanisms.
Here’s a look at how grouping and capturing work.
(<regex>)
Defines a subexpression or group.
This is the most basic grouping construct. A regex in parentheses just matches the contents of the parentheses:
>>>
>>> re.search('(bar)', 'foo bar baz')
<_sre.SRE_Match object; span=(4, 7), match='bar'>
>>> re.search('bar', 'foo bar baz')
<_sre.SRE_Match object; span=(4, 7), match='bar'>
As a regex, (bar)
matches the string 'bar'
, the same as the regex bar
would without the parentheses.
Treating a Group as a Unit
A quantifier metacharacter that follows a group operates on the entire subexpression specified in the group as a single unit.
For instance, the following example matches one or more occurrences of the string 'bar'
:
>>>
>>> re.search('(bar)+', 'foo bar baz')
<_sre.SRE_Match object; span=(4, 7), match='bar'>
>>> re.search('(bar)+', 'foo barbar baz')
<_sre.SRE_Match object; span=(4, 10), match='barbar'>
>>> re.search('(bar)+', 'foo barbarbarbar baz')
<_sre.SRE_Match object; span=(4, 16), match='barbarbarbar'>
Here’s a breakdown of the difference between the two regexes with and without grouping parentheses:
Regex | Interpretation | Matches | Examples |
---|---|---|---|
bar+ |
The + metacharacter applies only to the character 'r' . |
'ba' followed by one or more occurrences of 'r' |
'bar' 'barr' 'barrr' |
(bar)+ |
The + metacharacter applies to the entire string 'bar' . |
One or more occurrences of 'bar' |
'bar' 'barbar' 'barbarbar' |
Now take a look at a more complicated example. The regex (ba[rz]){2,4}(qux)?
matches 2
to 4
occurrences of either 'bar'
or 'baz'
, optionally followed by 'qux'
:
>>>
>>> re.search('(ba[rz]){2,4}(qux)?', 'bazbarbazqux')
<_sre.SRE_Match object; span=(0, 12), match='bazbarbazqux'>
>>> re.search('(ba[rz]){2,4}(qux)?', 'barbar')
<_sre.SRE_Match object; span=(0, 6), match='barbar'>
The following example shows that you can nest grouping parentheses:
>>>
>>> re.search('(foo(bar)?)+(ddd)?', 'foofoobar')
<_sre.SRE_Match object; span=(0, 9), match='foofoobar'>
>>> re.search('(foo(bar)?)+(ddd)?', 'foofoobar123')
<_sre.SRE_Match object; span=(0, 12), match='foofoobar123'>
>>> re.search('(foo(bar)?)+(ddd)?', 'foofoo123')
<_sre.SRE_Match object; span=(0, 9), match='foofoo123'>
The regex (foo(bar)?)+(ddd)?
is pretty elaborate, so let’s break it down into smaller pieces:
Regex | Matches |
---|---|
foo(bar)? |
'foo' optionally followed by 'bar' |
(foo(bar)?)+ |
One or more occurrences of the above |
ddd |
Three decimal digit characters |
(ddd)? |
Zero or one occurrences of the above |
String it all together and you get: at least one occurrence of 'foo'
optionally followed by 'bar'
, all optionally followed by three decimal digit characters.
As you can see, you can construct very complicated regexes in Python using grouping parentheses.
Capturing Groups
Grouping isn’t the only useful purpose that grouping constructs serve. Most (but not quite all) grouping constructs also capture the part of the search string that matches the group. You can retrieve the captured portion or refer to it later in several different ways.
Remember the match object that re.search()
returns? There are two methods defined for a match object that provide access to captured groups: .groups()
and .group()
.
m.groups()
Returns a tuple containing all the captured groups from a regex match.
Consider this example:
>>>
>>> m = re.search('(w+),(w+),(w+)', 'foo,quux,baz')
>>> m
<_sre.SRE_Match object; span=(0, 12), match='foo:quux:baz'>
Each of the three (w+)
expressions matches a sequence of word characters. The full regex (w+),(w+),(w+)
breaks the search string into three comma-separated tokens.
Because the (w+)
expressions use grouping parentheses, the corresponding matching tokens are captured. To access the captured matches, you can use .groups()
, which returns a tuple containing all the captured matches in order:
>>>
>>> m.groups()
('foo', 'quux', 'baz')
Notice that the tuple contains the tokens but not the commas that appeared in the search string. That’s because the word characters that make up the tokens are inside the grouping parentheses but the commas aren’t. The commas that you see between the returned tokens are the standard delimiters used to separate values in a tuple.
m.group(<n>)
Returns a string containing the
<n>
th
captured match.
With one argument, .group()
returns a single captured match. Note that the arguments are one-based, not zero-based. So, m.group(1)
refers to the first captured match, m.group(2)
to the second, and so on:
>>>
>>> m = re.search('(w+),(w+),(w+)', 'foo,quux,baz')
>>> m.groups()
('foo', 'quux', 'baz')
>>> m.group(1)
'foo'
>>> m.group(2)
'quux'
>>> m.group(3)
'baz'
Since the numbering of captured matches is one-based, and there isn’t any group numbered zero, m.group(0)
has a special meaning:
>>>
>>> m.group(0)
'foo,quux,baz'
>>> m.group()
'foo,quux,baz'
m.group(0)
returns the entire match, and m.group()
does the same.
m.group(<n1>, <n2>, ...)
Returns a tuple containing the specified captured matches.
With multiple arguments, .group()
returns a tuple containing the specified captured matches in the given order:
>>>
>>> m.groups()
('foo', 'quux', 'baz')
>>> m.group(2, 3)
('quux', 'baz')
>>> m.group(3, 2, 1)
('baz', 'quux', 'foo')
This is just convenient shorthand. You could create the tuple of matches yourself instead:
>>>
>>> m.group(3, 2, 1)
('baz', 'qux', 'foo')
>>> (m.group(3), m.group(2), m.group(1))
('baz', 'qux', 'foo')
The two statements shown are functionally equivalent.
Backreferences
You can match a previously captured group later within the same regex using a special metacharacter sequence called a backreference.
<n>
Matches the contents of a previously captured group.
Within a regex in Python, the sequence <n>
, where <n>
is an integer from 1
to 99
, matches the contents of the <n>
th
captured group.
Here’s a regex that matches a word, followed by a comma, followed by the same word again:
>>>
1>>> regex = r'(w+),1'
2
3>>> m = re.search(regex, 'foo,foo')
4>>> m
5<_sre.SRE_Match object; span=(0, 7), match='foo,foo'>
6>>> m.group(1)
7'foo'
8
9>>> m = re.search(regex, 'qux,qux')
10>>> m
11<_sre.SRE_Match object; span=(0, 7), match='qux,qux'>
12>>> m.group(1)
13'qux'
14
15>>> m = re.search(regex, 'foo,qux')
16>>> print(m)
17None
In the first example, on line 3, (w+)
matches the first instance of the string 'foo'
and saves it as the first captured group. The comma matches literally. Then 1
is a backreference to the first captured group and matches 'foo'
again. The second example, on line 9, is identical except that the (w+)
matches 'qux'
instead.
The last example, on line 15, doesn’t have a match because what comes before the comma isn’t the same as what comes after it, so the 1
backreference doesn’t match.
Numbered backreferences are one-based like the arguments to .group()
. Only the first ninety-nine captured groups are accessible by backreference. The interpreter will regard 100
as the '@'
character, whose octal value is 100.
Other Grouping Constructs
The (<regex>)
metacharacter sequence shown above is the most straightforward way to perform grouping within a regex in Python. The next section introduces you to some enhanced grouping constructs that allow you to tweak when and how grouping occurs.
(?P<name><regex>)
Creates a named captured group.
This metacharacter sequence is similar to grouping parentheses in that it creates a group matching <regex>
that is accessible through the match object or a subsequent backreference. The difference in this case is that you reference the matched group by its given symbolic <name>
instead of by its number.
Earlier, you saw this example with three captured groups numbered 1
, 2
, and 3
:
>>>
>>> m = re.search('(w+),(w+),(w+)', 'foo,quux,baz')
>>> m.groups()
('foo', 'quux', 'baz')
>>> m.group(1, 2, 3)
('foo', 'quux', 'baz')
The following effectively does the same thing except that the groups have the symbolic names w1
, w2
, and w3
:
>>>
>>> m = re.search('(?P<w1>w+),(?P<w2>w+),(?P<w3>w+)', 'foo,quux,baz')
>>> m.groups()
('foo', 'quux', 'baz')
You can refer to these captured groups by their symbolic names:
>>>
>>> m.group('w1')
'foo'
>>> m.group('w3')
'baz'
>>> m.group('w1', 'w2', 'w3')
('foo', 'quux', 'baz')
You can still access groups with symbolic names by number if you wish:
>>>
>>> m = re.search('(?P<w1>w+),(?P<w2>w+),(?P<w3>w+)', 'foo,quux,baz')
>>> m.group('w1')
'foo'
>>> m.group(1)
'foo'
>>> m.group('w1', 'w2', 'w3')
('foo', 'quux', 'baz')
>>> m.group(1, 2, 3)
('foo', 'quux', 'baz')
Any <name>
specified with this construct must conform to the rules for a Python identifier, and each <name>
can only appear once per regex.
(?P=<name>)
Matches the contents of a previously captured named group.
The (?P=<name>)
metacharacter sequence is a backreference, similar to <n>
, except that it refers to a named group rather than a numbered group.
Here again is the example from above, which uses a numbered backreference to match a word, followed by a comma, followed by the same word again:
>>>
>>> m = re.search(r'(w+),1', 'foo,foo')
>>> m
<_sre.SRE_Match object; span=(0, 7), match='foo,foo'>
>>> m.group(1)
'foo'
The following code does the same thing using a named group and a backreference instead:
>>>
>>> m = re.search(r'(?P<word>w+),(?P=word)', 'foo,foo')
>>> m
<_sre.SRE_Match object; span=(0, 7), match='foo,foo'>
>>> m.group('word')
'foo'
(?P=<word>w+)
matches 'foo'
and saves it as a captured group named word
. Again, the comma matches literally. Then (?P=word)
is a backreference to the named capture and matches 'foo'
again.
(?:<regex>)
Creates a non-capturing group.
(?:<regex>)
is just like (<regex>)
in that it matches the specified <regex>
. But (?:<regex>)
doesn’t capture the match for later retrieval:
>>>
>>> m = re.search('(w+),(?:w+),(w+)', 'foo,quux,baz')
>>> m.groups()
('foo', 'baz')
>>> m.group(1)
'foo'
>>> m.group(2)
'baz'
In this example, the middle word 'quux'
sits inside non-capturing parentheses, so it’s missing from the tuple of captured groups. It isn’t retrievable from the match object, nor would it be referable by backreference.
Why would you want to define a group but not capture it?
Remember that the regex parser will treat the <regex>
inside grouping parentheses as a single unit. You may have a situation where you need this grouping feature, but you don’t need to do anything with the value later, so you don’t really need to capture it. If you use non-capturing grouping, then the tuple of captured groups won’t be cluttered with values you don’t actually need to keep.
Additionally, it takes some time and memory to capture a group. If the code that performs the match executes many times and you don’t capture groups that you aren’t going to use later, then you may see a slight performance advantage.
(?(<n>)<yes-regex>|<no-regex>)
(?(<name>)<yes-regex>|<no-regex>)
Specifies a conditional match.
A conditional match matches against one of two specified regexes depending on whether the given group exists:
-
(?(<n>)<yes-regex>|<no-regex>)
matches against<yes-regex>
if a group numbered<n>
exists. Otherwise, it matches against<no-regex>
. -
(?(<name>)<yes-regex>|<no-regex>)
matches against<yes-regex>
if a group named<name>
exists. Otherwise, it matches against<no-regex>
.
Conditional matches are better illustrated with an example. Consider this regex:
regex = r'^(###)?foo(?(1)bar|baz)'
Here are the parts of this regex broken out with some explanation:
^(###)?
indicates that the search string optionally begins with'###'
. If it does, then the grouping parentheses around###
will create a group numbered1
. Otherwise, no such group will exist.- The next portion,
foo
, literally matches the string'foo'
. - Lastly,
(?(1)bar|baz)
matches against'bar'
if group1
exists and'baz'
if it doesn’t.
The following code blocks demonstrate the use of the above regex in several different Python code snippets:
Example 1:
>>>
>>> re.search(regex, '###foobar')
<_sre.SRE_Match object; span=(0, 9), match='###foobar'>
The search string '###foobar'
does start with '###'
, so the parser creates a group numbered 1
. The conditional match is then against 'bar'
, which matches.
Example 2:
>>>
>>> print(re.search(regex, '###foobaz'))
None
The search string '###foobaz'
does start with '###'
, so the parser creates a group numbered 1
. The conditional match is then against 'bar'
, which doesn’t match.
Example 3:
>>>
>>> print(re.search(regex, 'foobar'))
None
The search string 'foobar'
doesn’t start with '###'
, so there isn’t a group numbered 1
. The conditional match is then against 'baz'
, which doesn’t match.
Example 4:
>>>
>>> re.search(regex, 'foobaz')
<_sre.SRE_Match object; span=(0, 6), match='foobaz'>
The search string 'foobaz'
doesn’t start with '###'
, so there isn’t a group numbered 1
. The conditional match is then against 'baz'
, which matches.
Here’s another conditional match using a named group instead of a numbered group:
>>>
>>> regex = r'^(?P<ch>W)?foo(?(ch)(?P=ch)|)$'
This regex matches the string 'foo'
, preceded by a single non-word character and followed by the same non-word character, or the string 'foo'
by itself.
Again, let’s break this down into pieces:
Regex | Matches |
---|---|
^ |
The start of the string |
(?P<ch>W) |
A single non-word character, captured in a group named ch |
(?P<ch>W)? |
Zero or one occurrences of the above |
foo |
The literal string 'foo' |
(?(ch)(?P=ch)|) |
The contents of the group named ch if it exists, or the empty string if it doesn’t |
$ |
The end of the string |
If a non-word character precedes 'foo'
, then the parser creates a group named ch
which contains that character. The conditional match then matches against <yes-regex>
, which is (?P=ch)
, the same character again. That means the same character must also follow 'foo'
for the entire match to succeed.
If 'foo'
isn’t preceded by a non-word character, then the parser doesn’t create group ch
. <no-regex>
is the empty string, which means there must not be anything following 'foo'
for the entire match to succeed. Since ^
and $
anchor the whole regex, the string must equal 'foo'
exactly.
Here are some examples of searches using this regex in Python code:
>>>
1>>> re.search(regex, 'foo')
2<_sre.SRE_Match object; span=(0, 3), match='foo'>
3>>> re.search(regex, '#foo#')
4<_sre.SRE_Match object; span=(0, 5), match='#foo#'>
5>>> re.search(regex, '@foo@')
6<_sre.SRE_Match object; span=(0, 5), match='@foo@'>
7
8>>> print(re.search(regex, '#foo'))
9None
10>>> print(re.search(regex, 'foo@'))
11None
12>>> print(re.search(regex, '#foo@'))
13None
14>>> print(re.search(regex, '@foo#'))
15None
On line 1, 'foo'
is by itself. On lines 3 and 5, the same non-word character precedes and follows 'foo'
. As advertised, these matches succeed.
In the remaining cases, the matches fail.
Conditional regexes in Python are pretty esoteric and challenging to work through. If you ever do find a reason to use one, then you could probably accomplish the same goal with multiple separate re.search()
calls, and your code would be less complicated to read and understand.
Lookahead and Lookbehind Assertions
Lookahead and lookbehind assertions determine the success or failure of a regex match in Python based on what is just behind (to the left) or ahead (to the right) of the parser’s current position in the search string.
Like anchors, lookahead and lookbehind assertions are zero-width assertions, so they don’t consume any of the search string. Also, even though they contain parentheses and perform grouping, they don’t capture what they match.
(?=<lookahead_regex>)
Creates a positive lookahead assertion.
(?=<lookahead_regex>)
asserts that what follows the regex parser’s current position must match <lookahead_regex>
:
>>>
>>> re.search('foo(?=[a-z])', 'foobar')
<_sre.SRE_Match object; span=(0, 3), match='foo'>
The lookahead assertion (?=[a-z])
specifies that what follows 'foo'
must be a lowercase alphabetic character. In this case, it’s the character 'b'
, so a match is found.
In the next example, on the other hand, the lookahead fails. The next character after 'foo'
is '1'
, so there isn’t a match:
>>>
>>> print(re.search('foo(?=[a-z])', 'foo123'))
None
What’s unique about a lookahead is that the portion of the search string that matches <lookahead_regex>
isn’t consumed, and it isn’t part of the returned match object.
Take another look at the first example:
>>>
>>> re.search('foo(?=[a-z])', 'foobar')
<_sre.SRE_Match object; span=(0, 3), match='foo'>
The regex parser looks ahead only to the 'b'
that follows 'foo'
but doesn’t pass over it yet. You can tell that 'b'
isn’t considered part of the match because the match object displays match='foo'
.
Compare that to a similar example that uses grouping parentheses without a lookahead:
>>>
>>> re.search('foo([a-z])', 'foobar')
<_sre.SRE_Match object; span=(0, 4), match='foob'>
This time, the regex consumes the 'b'
, and it becomes a part of the eventual match.
Here’s another example illustrating how a lookahead differs from a conventional regex in Python:
>>>
1>>> m = re.search('foo(?=[a-z])(?P<ch>.)', 'foobar')
2>>> m.group('ch')
3'b'
4
5>>> m = re.search('foo([a-z])(?P<ch>.)', 'foobar')
6>>> m.group('ch')
7'a'
In the first search, on line 1, the parser proceeds as follows:
- The first portion of the regex,
foo
, matches and consumes'foo'
from the search string'foobar'
. - The next portion,
(?=[a-z])
, is a lookahead that matches'b'
, but the parser doesn’t advance past the'b'
. - Lastly,
(?P<ch>.)
matches the next single character available, which is'b'
, and captures it in a group namedch
.
The m.group('ch')
call confirms that the group named ch
contains 'b'
.
Compare that to the search on line 5, which doesn’t contain a lookahead:
- As in the first example, the first portion of the regex,
foo
, matches and consumes'foo'
from the search string'foobar'
. - The next portion,
([a-z])
, matches and consumes'b'
, and the parser advances past'b'
. - Lastly,
(?P<ch>.)
matches the next single character available, which is now'a'
.
m.group('ch')
confirms that, in this case, the group named ch
contains 'a'
.
(?!<lookahead_regex>)
Creates a negative lookahead assertion.
(?!<lookahead_regex>)
asserts that what follows the regex parser’s current position must not match <lookahead_regex>
.
Here are the positive lookahead examples you saw earlier, along with their negative lookahead counterparts:
>>>
1>>> re.search('foo(?=[a-z])', 'foobar')
2<_sre.SRE_Match object; span=(0, 3), match='foo'>
3>>> print(re.search('foo(?![a-z])', 'foobar'))
4None
5
6>>> print(re.search('foo(?=[a-z])', 'foo123'))
7None
8>>> re.search('foo(?![a-z])', 'foo123')
9<_sre.SRE_Match object; span=(0, 3), match='foo'>
The negative lookahead assertions on lines 3 and 8 stipulate that what follows 'foo'
should not be a lowercase alphabetic character. This fails on line 3 but succeeds on line 8. This is the opposite of what happened with the corresponding positive lookahead assertions.
As with a positive lookahead, what matches a negative lookahead isn’t part of the returned match object and isn’t consumed.
(?<=<lookbehind_regex>)
Creates a positive lookbehind assertion.
(?<=<lookbehind_regex>)
asserts that what precedes the regex parser’s current position must match <lookbehind_regex>
.
In the following example, the lookbehind assertion specifies that 'foo'
must precede 'bar'
:
>>>
>>> re.search('(?<=foo)bar', 'foobar')
<_sre.SRE_Match object; span=(3, 6), match='bar'>
This is the case here, so the match succeeds. As with lookahead assertions, the part of the search string that matches the lookbehind doesn’t become part of the eventual match.
The next example fails to match because the lookbehind requires that 'qux'
precede 'bar'
:
>>>
>>> print(re.search('(?<=qux)bar', 'foobar'))
None
There’s a restriction on lookbehind assertions that doesn’t apply to lookahead assertions. The <lookbehind_regex>
in a lookbehind assertion must specify a match of fixed length.
For example, the following isn’t allowed because the length of the string matched by a+
is indeterminate:
>>>
>>> re.search('(?<=a+)def', 'aaadef')
Traceback (most recent call last):
File "<pyshell#72>", line 1, in <module>
re.search('(?<=a+)def', 'aaadef')
File "C:Python36libre.py", line 182, in search
return _compile(pattern, flags).search(string)
File "C:Python36libre.py", line 301, in _compile
p = sre_compile.compile(pattern, flags)
File "C:Python36libsre_compile.py", line 566, in compile
code = _code(p, flags)
File "C:Python36libsre_compile.py", line 551, in _code
_compile(code, p.data, flags)
File "C:Python36libsre_compile.py", line 160, in _compile
raise error("look-behind requires fixed-width pattern")
sre_constants.error: look-behind requires fixed-width pattern
This, however, is okay:
>>>
>>> re.search('(?<=a{3})def', 'aaadef')
<_sre.SRE_Match object; span=(3, 6), match='def'>
Anything that matches a{3}
will have a fixed length of three, so a{3}
is valid in a lookbehind assertion.
(?<!<lookbehind_regex>)
Creates a negative lookbehind assertion.
(?<!<lookbehind_regex>)
asserts that what precedes the regex parser’s current position must not match <lookbehind_regex>
:
>>>
>>> print(re.search('(?<!foo)bar', 'foobar'))
None
>>> re.search('(?<!qux)bar', 'foobar')
<_sre.SRE_Match object; span=(3, 6), match='bar'>
As with the positive lookbehind assertion, <lookbehind_regex>
must specify a match of fixed length.
Miscellaneous Metacharacters
There are a couple more metacharacter sequences to cover. These are stray metacharacters that don’t obviously fall into any of the categories already discussed.
(?#...)
Specifies a comment.
The regex parser ignores anything contained in the sequence (?#...)
:
>>>
>>> re.search('bar(?#This is a comment) *baz', 'foo bar baz qux')
<_sre.SRE_Match object; span=(4, 11), match='bar baz'>
This allows you to specify documentation inside a regex in Python, which can be especially useful if the regex is particularly long.
Vertical bar, or pipe (|
)
Specifies a set of alternatives on which to match.
An expression of the form <regex
1
>|<regex
2
>|...|<regex
n
>
matches at most one of the specified <regex
i
>
expressions:
>>>
>>> re.search('foo|bar|baz', 'bar')
<_sre.SRE_Match object; span=(0, 3), match='bar'>
>>> re.search('foo|bar|baz', 'baz')
<_sre.SRE_Match object; span=(0, 3), match='baz'>
>>> print(re.search('foo|bar|baz', 'quux'))
None
Here, foo|bar|baz
will match any of 'foo'
, 'bar'
, or 'baz'
. You can separate any number of regexes using |
.
Alternation is non-greedy. The regex parser looks at the expressions separated by |
in left-to-right order and returns the first match that it finds. The remaining expressions aren’t tested, even if one of them would produce a longer match:
>>>
1>>> re.search('foo', 'foograult')
2<_sre.SRE_Match object; span=(0, 3), match='foo'>
3>>> re.search('grault', 'foograult')
4<_sre.SRE_Match object; span=(3, 9), match='grault'>
5
6>>> re.search('foo|grault', 'foograult')
7<_sre.SRE_Match object; span=(0, 3), match='foo'>
In this case, the pattern specified on line 6, 'foo|grault'
, would match on either 'foo'
or 'grault'
. The match returned is 'foo'
because that appears first when scanning from left to right, even though 'grault'
would be a longer match.
You can combine alternation, grouping, and any other metacharacters to achieve whatever level of complexity you need. In the following example, (foo|bar|baz)+
means a sequence of one or more of the strings 'foo'
, 'bar'
, or 'baz'
:
>>>
>>> re.search('(foo|bar|baz)+', 'foofoofoo')
<_sre.SRE_Match object; span=(0, 9), match='foofoofoo'>
>>> re.search('(foo|bar|baz)+', 'bazbazbazbaz')
<_sre.SRE_Match object; span=(0, 12), match='bazbazbazbaz'>
>>> re.search('(foo|bar|baz)+', 'barbazfoo')
<_sre.SRE_Match object; span=(0, 9), match='barbazfoo'>
In the next example, ([0-9]+|[a-f]+)
means a sequence of one or more decimal digit characters or a sequence of one or more of the characters 'a-f'
:
>>>
>>> re.search('([0-9]+|[a-f]+)', '456')
<_sre.SRE_Match object; span=(0, 3), match='456'>
>>> re.search('([0-9]+|[a-f]+)', 'ffda')
<_sre.SRE_Match object; span=(0, 4), match='ffda'>
With all the metacharacters that the re
module supports, the sky is practically the limit.
That’s All, Folks!
That completes our tour of the regex metacharacters supported by Python’s re
module. (Actually, it doesn’t quite—there are a couple more stragglers you’ll learn about below in the discussion on flags.)
It’s a lot to digest, but once you become familiar with regex syntax in Python, the complexity of pattern matching that you can perform is almost limitless. These tools come in very handy when you’re writing code to process textual data.
If you’re new to regexes and want more practice working with them, or if you’re developing an application that uses a regex and you want to test it interactively, then check out the Regular Expressions 101 website. It’s seriously cool!
Modified Regular Expression Matching With Flags
Most of the functions in the re
module take an optional <flags>
argument. This includes the function you’re now very familiar with, re.search()
.
re.search(<regex>, <string>, <flags>)
Scans a string for a regex match, applying the specified modifier
<flags>
.
Flags modify regex parsing behavior, allowing you to refine your pattern matching even further.
Supported Regular Expression Flags
The table below briefly summarizes the available flags. All flags except re.DEBUG
have a short, single-letter name and also a longer, full-word name:
Short Name | Long Name | Effect |
---|---|---|
re.I |
re.IGNORECASE |
Makes matching of alphabetic characters case-insensitive |
re.M |
re.MULTILINE |
Causes start-of-string and end-of-string anchors to match embedded newlines |
re.S |
re.DOTALL |
Causes the dot metacharacter to match a newline |
re.X |
re.VERBOSE |
Allows inclusion of whitespace and comments within a regular expression |
---- |
re.DEBUG |
Causes the regex parser to display debugging information to the console |
re.A |
re.ASCII |
Specifies ASCII encoding for character classification |
re.U |
re.UNICODE |
Specifies Unicode encoding for character classification |
re.L |
re.LOCALE |
Specifies encoding for character classification based on the current locale |
The following sections describe in more detail how these flags affect matching behavior.
re.I
re.IGNORECASE
Makes matching case insensitive.
When IGNORECASE
is in effect, character matching is case insensitive:
>>>
1>>> re.search('a+', 'aaaAAA')
2<_sre.SRE_Match object; span=(0, 3), match='aaa'>
3>>> re.search('A+', 'aaaAAA')
4<_sre.SRE_Match object; span=(3, 6), match='AAA'>
5
6>>> re.search('a+', 'aaaAAA', re.I)
7<_sre.SRE_Match object; span=(0, 6), match='aaaAAA'>
8>>> re.search('A+', 'aaaAAA', re.IGNORECASE)
9<_sre.SRE_Match object; span=(0, 6), match='aaaAAA'>
In the search on line 1, a+
matches only the first three characters of 'aaaAAA'
. Similarly, on line 3, A+
matches only the last three characters. But in the subsequent searches, the parser ignores case, so both a+
and A+
match the entire string.
IGNORECASE
affects alphabetic matching involving character classes as well:
>>>
>>> re.search('[a-z]+', 'aBcDeF')
<_sre.SRE_Match object; span=(0, 1), match='a'>
>>> re.search('[a-z]+', 'aBcDeF', re.I)
<_sre.SRE_Match object; span=(0, 6), match='aBcDeF'>
When case is significant, the longest portion of 'aBcDeF'
that [a-z]+
matches is just the initial 'a'
. Specifying re.I
makes the search case insensitive, so [a-z]+
matches the entire string.
re.M
re.MULTILINE
Causes start-of-string and end-of-string anchors to match at embedded newlines.
By default, the ^
(start-of-string) and $
(end-of-string) anchors match only at the beginning and end of the search string:
>>>
>>> s = 'foonbarnbaz'
>>> re.search('^foo', s)
<_sre.SRE_Match object; span=(0, 3), match='foo'>
>>> print(re.search('^bar', s))
None
>>> print(re.search('^baz', s))
None
>>> print(re.search('foo$', s))
None
>>> print(re.search('bar$', s))
None
>>> re.search('baz$', s)
<_sre.SRE_Match object; span=(8, 11), match='baz'>
In this case, even though the search string 'foonbarnbaz'
contains embedded newline characters, only 'foo'
matches when anchored at the beginning of the string, and only 'baz'
matches when anchored at the end.
If a string has embedded newlines, however, you can think of it as consisting of multiple internal lines. In that case, if the MULTILINE
flag is set, the ^
and $
anchor metacharacters match internal lines as well:
^
matches at the beginning of the string or at the beginning of any line within the string (that is, immediately following a newline).$
matches at the end of the string or at the end of any line within the string (immediately preceding a newline).
The following are the same searches as shown above:
>>>
>>> s = 'foonbarnbaz'
>>> print(s)
foo
bar
baz
>>> re.search('^foo', s, re.MULTILINE)
<_sre.SRE_Match object; span=(0, 3), match='foo'>
>>> re.search('^bar', s, re.MULTILINE)
<_sre.SRE_Match object; span=(4, 7), match='bar'>
>>> re.search('^baz', s, re.MULTILINE)
<_sre.SRE_Match object; span=(8, 11), match='baz'>
>>> re.search('foo$', s, re.M)
<_sre.SRE_Match object; span=(0, 3), match='foo'>
>>> re.search('bar$', s, re.M)
<_sre.SRE_Match object; span=(4, 7), match='bar'>
>>> re.search('baz$', s, re.M)
<_sre.SRE_Match object; span=(8, 11), match='baz'>
In the string 'foonbarnbaz'
, all three of 'foo'
, 'bar'
, and 'baz'
occur at either the start or end of the string or at the start or end of a line within the string. With the MULTILINE
flag set, all three match when anchored with either ^
or $
.
re.S
re.DOTALL
Causes the dot (
.
) metacharacter to match a newline.
Remember that by default, the dot metacharacter matches any character except the newline character. The DOTALL
flag lifts this restriction:
>>>
1>>> print(re.search('foo.bar', 'foonbar'))
2None
3>>> re.search('foo.bar', 'foonbar', re.DOTALL)
4<_sre.SRE_Match object; span=(0, 7), match='foonbar'>
5>>> re.search('foo.bar', 'foonbar', re.S)
6<_sre.SRE_Match object; span=(0, 7), match='foonbar'>
In this example, on line 1 the dot metacharacter doesn’t match the newline in 'foonbar'
. On lines 3 and 5, DOTALL
is in effect, so the dot does match the newline. Note that the short name of the DOTALL
flag is re.S
, not re.D
as you might expect.
re.X
re.VERBOSE
Allows inclusion of whitespace and comments within a regex.
The VERBOSE
flag specifies a few special behaviors:
-
The regex parser ignores all whitespace unless it’s within a character class or escaped with a backslash.
-
If the regex contains a
#
character that isn’t contained within a character class or escaped with a backslash, then the parser ignores it and all characters to the right of it.
What’s the use of this? It allows you to format a regex in Python so that it’s more readable and self-documenting.
Here’s an example showing how you might put this to use. Suppose you want to parse phone numbers that have the following format:
- Optional three-digit area code, in parentheses
- Optional whitespace
- Three-digit prefix
- Separator (either
'-'
or'.'
) - Four-digit line number
The following regex does the trick:
>>>
>>> regex = r'^((d{3}))?s*d{3}[-.]d{4}$'
>>> re.search(regex, '414.9229')
<_sre.SRE_Match object; span=(0, 8), match='414.9229'>
>>> re.search(regex, '414-9229')
<_sre.SRE_Match object; span=(0, 8), match='414-9229'>
>>> re.search(regex, '(712)414-9229')
<_sre.SRE_Match object; span=(0, 13), match='(712)414-9229'>
>>> re.search(regex, '(712) 414-9229')
<_sre.SRE_Match object; span=(0, 14), match='(712) 414-9229'>
But r'^((d{3}))?s*d{3}[-.]d{4}$'
is an eyeful, isn’t it? Using the VERBOSE
flag, you can write the same regex in Python like this instead:
>>>
>>> regex = r'''^ # Start of string
... ((d{3}))? # Optional area code
... s* # Optional whitespace
... d{3} # Three-digit prefix
... [-.] # Separator character
... d{4} # Four-digit line number
... $ # Anchor at end of string
... '''
>>> re.search(regex, '414.9229', re.VERBOSE)
<_sre.SRE_Match object; span=(0, 8), match='414.9229'>
>>> re.search(regex, '414-9229', re.VERBOSE)
<_sre.SRE_Match object; span=(0, 8), match='414-9229'>
>>> re.search(regex, '(712)414-9229', re.X)
<_sre.SRE_Match object; span=(0, 13), match='(712)414-9229'>
>>> re.search(regex, '(712) 414-9229', re.X)
<_sre.SRE_Match object; span=(0, 14), match='(712) 414-9229'>
The re.search()
calls are the same as those shown above, so you can see that this regex works the same as the one specified earlier. But it’s less difficult to understand at first glance.
Note that triple quoting makes it particularly convenient to include embedded newlines, which qualify as ignored whitespace in VERBOSE
mode.
When using the VERBOSE
flag, be mindful of whitespace that you do intend to be significant. Consider these examples:
>>>
1>>> re.search('foo bar', 'foo bar')
2<_sre.SRE_Match object; span=(0, 7), match='foo bar'>
3
4>>> print(re.search('foo bar', 'foo bar', re.VERBOSE))
5None
6
7>>> re.search('foo bar', 'foo bar', re.VERBOSE)
8<_sre.SRE_Match object; span=(0, 7), match='foo bar'>
9>>> re.search('foo[ ]bar', 'foo bar', re.VERBOSE)
10<_sre.SRE_Match object; span=(0, 7), match='foo bar'>
After all you’ve seen to this point, you may be wondering why on line 4 the regex foo bar
doesn’t match the string 'foo bar'
. It doesn’t because the VERBOSE
flag causes the parser to ignore the space character.
To make this match as expected, escape the space character with a backslash or include it in a character class, as shown on lines 7 and 9.
As with the DOTALL
flag, note that the VERBOSE
flag has a non-intuitive short name: re.X
, not re.V
.
re.DEBUG
Displays debugging information.
The DEBUG
flag causes the regex parser in Python to display debugging information about the parsing process to the console:
>>>
>>> re.search('foo.bar', 'fooxbar', re.DEBUG)
LITERAL 102
LITERAL 111
LITERAL 111
ANY None
LITERAL 98
LITERAL 97
LITERAL 114
<_sre.SRE_Match object; span=(0, 7), match='fooxbar'>
When the parser displays LITERAL nnn
in the debugging output, it’s showing the ASCII code of a literal character in the regex. In this case, the literal characters are 'f'
, 'o'
, 'o'
and 'b'
, 'a'
, 'r'
.
Here’s a more complicated example. This is the phone number regex shown in the discussion on the VERBOSE
flag earlier:
>>>
>>> regex = r'^((d{3}))?s*d{3}[-.]d{4}$'
>>> re.search(regex, '414.9229', re.DEBUG)
AT AT_BEGINNING
MAX_REPEAT 0 1
SUBPATTERN 1 0 0
LITERAL 40
MAX_REPEAT 3 3
IN
CATEGORY CATEGORY_DIGIT
LITERAL 41
MAX_REPEAT 0 MAXREPEAT
IN
CATEGORY CATEGORY_SPACE
MAX_REPEAT 3 3
IN
CATEGORY CATEGORY_DIGIT
IN
LITERAL 45
LITERAL 46
MAX_REPEAT 4 4
IN
CATEGORY CATEGORY_DIGIT
AT AT_END
<_sre.SRE_Match object; span=(0, 8), match='414.9229'>
This looks like a lot of esoteric information that you’d never need, but it can be useful. See the Deep Dive below for a practical application.
Deep Dive: Debugging Regular Expression Parsing
As you know from above, the metacharacter sequence
{m,n}
indicates a specific number of repetitions. It matches anywhere fromm
ton
repetitions of what precedes it:>>>
>>> re.search('x[123]{2,4}y', 'x222y') <_sre.SRE_Match object; span=(0, 5), match='x222y'>
You can verify this with the
DEBUG
flag:>>>
>>> re.search('x[123]{2,4}y', 'x222y', re.DEBUG) LITERAL 120 MAX_REPEAT 2 4 IN LITERAL 49 LITERAL 50 LITERAL 51 LITERAL 121 <_sre.SRE_Match object; span=(0, 5), match='x222y'>
MAX_REPEAT 2 4
confirms that the regex parser recognizes the metacharacter sequence{2,4}
and interprets it as a range quantifier.But, as noted previously, if a pair of curly braces in a regex in Python contains anything other than a valid number or numeric range, then it loses its special meaning.
You can verify this also:
>>>
>>> re.search('x[123]{foo}y', 'x222y', re.DEBUG) LITERAL 120 IN LITERAL 49 LITERAL 50 LITERAL 51 LITERAL 123 LITERAL 102 LITERAL 111 LITERAL 111 LITERAL 125 LITERAL 121
You can see that there’s no
MAX_REPEAT
token in the debug output. TheLITERAL
tokens indicate that the parser treats{foo}
literally and not as a quantifier metacharacter sequence.123
,102
,111
,111
, and125
are the ASCII codes for the characters in the literal string'{foo}'
.Information displayed by the
DEBUG
flag can help you troubleshoot by showing you how the parser is interpreting your regex.
Curiously, the re
module doesn’t define a single-letter version of the DEBUG
flag. You could define your own if you wanted to:
>>>
>>> import re
>>> re.D
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: module 're' has no attribute 'D'
>>> re.D = re.DEBUG
>>> re.search('foo', 'foo', re.D)
LITERAL 102
LITERAL 111
LITERAL 111
<_sre.SRE_Match object; span=(0, 3), match='foo'>
But this might be more confusing than helpful, as readers of your code might misconstrue it as an abbreviation for the DOTALL
flag. If you did make this assignment, it would be a good idea to document it thoroughly.
re.A
re.ASCII
re.U
re.UNICODE
re.L
re.LOCALE
Specify the character encoding used for parsing of special regex character classes.
Several of the regex metacharacter sequences (w
, W
, b
, B
, d
, D
, s
, and S
) require you to assign characters to certain classes like word, digit, or whitespace. The flags in this group determine the encoding scheme used to assign characters to these classes. The possible encodings are ASCII, Unicode, or according to the current locale.
You had a brief introduction to character encoding and Unicode in the tutorial on Strings and Character Data in Python, under the discussion of the ord()
built-in function. For more in-depth information, check out these resources:
- Unicode & Character Encodings in Python: A Painless Guide
- Python’s Unicode Support
Why is character encoding so important in the context of regexes in Python? Here’s a quick example.
You learned earlier that d
specifies a single digit character. The description of the d
metacharacter sequence states that it’s equivalent to the character class [0-9]
. That happens to be true for English and Western European languages, but for most of the world’s languages, the characters '0'
through '9'
don’t represent all or even any of the digits.
For example, here’s a string that consists of three Devanagari digit characters:
>>>
>>> s = 'u0967u096au096c'
>>> s
'१४६'
For the regex parser to properly account for the Devanagari script, the digit metacharacter sequence d
must match each of these characters as well.
The Unicode Consortium created Unicode to handle this problem. Unicode is a character-encoding standard designed to represent all the world’s writing systems. All strings in Python 3, including regexes, are Unicode by default.
So then, back to the flags listed above. These flags help to determine whether a character falls into a given class by specifying whether the encoding used is ASCII, Unicode, or the current locale:
re.U
andre.UNICODE
specify Unicode encoding. Unicode is the default, so these flags are superfluous. They’re mainly supported for backward compatibility.re.A
andre.ASCII
force a determination based on ASCII encoding. If you happen to be operating in English, then this is happening anyway, so the flag won’t affect whether or not a match is found.re.L
andre.LOCALE
make the determination based on the current locale. Locale is an outdated concept and isn’t considered reliable. Except in rare circumstances, you’re not likely to need it.
Using the default Unicode encoding, the regex parser should be able to handle any language you throw at it. In the following example, it correctly recognizes each of the characters in the string '१४६'
as a digit:
>>>
>>> s = 'u0967u096au096c'
>>> s
'१४६'
>>> re.search('d+', s)
<_sre.SRE_Match object; span=(0, 3), match='१४६'>
Here’s another example that illustrates how character encoding can affect a regex match in Python. Consider this string:
>>>
>>> s = 'schu00f6n'
>>> s
'schön'
'schön'
(the German word for pretty or nice) contains the 'ö'
character, which has the 16-bit hexadecimal Unicode value 00f6
. This character isn’t representable in traditional 7-bit ASCII.
If you’re working in German, then you should reasonably expect the regex parser to consider all of the characters in 'schön'
to be word characters. But take a look at what happens if you search s
for word characters using the w
character class and force an ASCII encoding:
>>>
>>> re.search('w+', s, re.ASCII)
<_sre.SRE_Match object; span=(0, 3), match='sch'>
When you restrict the encoding to ASCII, the regex parser recognizes only the first three characters as word characters. The match stops at 'ö'
.
On the other hand, if you specify re.UNICODE
or allow the encoding to default to Unicode, then all the characters in 'schön'
qualify as word characters:
>>>
>>> re.search('w+', s, re.UNICODE)
<_sre.SRE_Match object; span=(0, 5), match='schön'>
>>> re.search('w+', s)
<_sre.SRE_Match object; span=(0, 5), match='schön'>
The ASCII
and LOCALE
flags are available in case you need them for special circumstances. But in general, the best strategy is to use the default Unicode encoding. This should handle any world language correctly.
Combining <flags>
Arguments in a Function Call
Flag values are defined so that you can combine them using the bitwise OR (|
) operator. This allows you to specify several flags in a single function call:
>>>
>>> re.search('^bar', 'FOOnBARnBAZ', re.I|re.M)
<_sre.SRE_Match object; span=(4, 7), match='BAR'>
This re.search()
call uses bitwise OR to specify both the IGNORECASE
and MULTILINE
flags at once.
Setting and Clearing Flags Within a Regular Expression
In addition to being able to pass a <flags>
argument to most re
module function calls, you can also modify flag values within a regex in Python. There are two regex metacharacter sequences that provide this capability.
(?<flags>)
Sets flag value(s) for the duration of a regex.
Within a regex, the metacharacter sequence (?<flags>)
sets the specified flags for the entire expression.
The value of <flags>
is one or more letters from the set a
, i
, L
, m
, s
, u
, and x
. Here’s how they correspond to the re
module flags:
Letter | Flags |
---|---|
a |
re.A re.ASCII |
i |
re.I re.IGNORECASE |
L |
re.L re.LOCALE |
m |
re.M re.MULTILINE |
s |
re.S re.DOTALL |
u |
re.U re.UNICODE |
x |
re.X re.VERBOSE |
The (?<flags>)
metacharacter sequence as a whole matches the empty string. It always matches successfully and doesn’t consume any of the search string.
The following examples are equivalent ways of setting the IGNORECASE
and MULTILINE
flags:
>>>
>>> re.search('^bar', 'FOOnBARnBAZn', re.I|re.M)
<_sre.SRE_Match object; span=(4, 7), match='BAR'>
>>> re.search('(?im)^bar', 'FOOnBARnBAZn')
<_sre.SRE_Match object; span=(4, 7), match='BAR'>
Note that a (?<flags>)
metacharacter sequence sets the given flag(s) for the entire regex no matter where you place it in the expression:
>>>
>>> re.search('foo.bar(?s).baz', 'foonbarnbaz')
<_sre.SRE_Match object; span=(0, 11), match='foonbarnbaz'>
>>> re.search('foo.bar.baz(?s)', 'foonbarnbaz')
<_sre.SRE_Match object; span=(0, 11), match='foonbarnbaz'>
In the above examples, both dot metacharacters match newlines because the DOTALL
flag is in effect. This is true even when (?s)
appears in the middle or at the end of the expression.
As of Python 3.7, it’s deprecated to specify (?<flags>)
anywhere in a regex other than at the beginning:
>>>
>>> import sys
>>> sys.version
'3.8.0 (default, Oct 14 2019, 21:29:03) n[GCC 7.4.0]'
>>> re.search('foo.bar.baz(?s)', 'foonbarnbaz')
<stdin>:1: DeprecationWarning: Flags not at the start
of the expression 'foo.bar.baz(?s)'
<re.Match object; span=(0, 11), match='foonbarnbaz'>
It still produces the appropriate match, but you’ll get a warning message.
(?<set_flags>-<remove_flags>:<regex>)
Sets or removes flag value(s) for the duration of a group.
(?<set_flags>-<remove_flags>:<regex>)
defines a non-capturing group that matches against <regex>
. For the <regex>
contained in the group, the regex parser sets any flags specified in <set_flags>
and clears any flags specified in <remove_flags>
.
Values for <set_flags>
and <remove_flags>
are most commonly i
, m
, s
or x
.
In the following example, the IGNORECASE
flag is set for the specified group:
>>>
>>> re.search('(?i:foo)bar', 'FOObar')
<re.Match object; span=(0, 6), match='FOObar'>
This produces a match because (?i:foo)
dictates that the match against 'FOO'
is case insensitive.
Now contrast that with this example:
>>>
>>> print(re.search('(?i:foo)bar', 'FOOBAR'))
None
As in the previous example, the match against 'FOO'
would succeed because it’s case insensitive. But once outside the group, IGNORECASE
is no longer in effect, so the match against 'BAR'
is case sensitive and fails.
Here’s an example that demonstrates turning a flag off for a group:
>>>
>>> print(re.search('(?-i:foo)bar', 'FOOBAR', re.IGNORECASE))
None
Again, there’s no match. Although re.IGNORECASE
enables case-insensitive matching for the entire call, the metacharacter sequence (?-i:foo)
turns off IGNORECASE
for the duration of that group, so the match against 'FOO'
fails.
As of Python 3.7, you can specify u
, a
, or L
as <set_flags>
to override the default encoding for the specified group:
>>>
>>> s = 'schu00f6n'
>>> s
'schön'
>>> # Requires Python 3.7 or later
>>> re.search('(?a:w+)', s)
<re.Match object; span=(0, 3), match='sch'>
>>> re.search('(?u:w+)', s)
<re.Match object; span=(0, 5), match='schön'>
You can only set encoding this way, though. You can’t remove it:
>>>
>>> re.search('(?-a:w+)', s)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.8/re.py", line 199, in search
return _compile(pattern, flags).search(string)
File "/usr/lib/python3.8/re.py", line 302, in _compile
p = sre_compile.compile(pattern, flags)
File "/usr/lib/python3.8/sre_compile.py", line 764, in compile
p = sre_parse.parse(p, flags)
File "/usr/lib/python3.8/sre_parse.py", line 948, in parse
p = _parse_sub(source, state, flags & SRE_FLAG_VERBOSE, 0)
File "/usr/lib/python3.8/sre_parse.py", line 443, in _parse_sub
itemsappend(_parse(source, state, verbose, nested + 1,
File "/usr/lib/python3.8/sre_parse.py", line 805, in _parse
flags = _parse_flags(source, state, char)
File "/usr/lib/python3.8/sre_parse.py", line 904, in _parse_flags
raise source.error(msg)
re.error: bad inline flags: cannot turn off flags 'a', 'u' and 'L' at
position 4
u
, a
, and L
are mutually exclusive. Only one of them may appear per group.
Conclusion
This concludes your introduction to regular expression matching and Python’s re
module. Congratulations! You’ve mastered a tremendous amount of material.
You now know how to:
- Use
re.search()
to perform regex matching in Python - Create complex pattern matching searches with regex metacharacters
- Tweak regex parsing behavior with flags
But you’ve still seen only one function in the module: re.search()
! The re
module has many more useful functions and objects to add to your pattern-matching toolkit. The next tutorial in the series will introduce you to what else the regex module in Python has to offer.
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Regular Expressions and Building Regexes in Python
#статьи
- 5 окт 2022
-
0
Исчерпывающий гайд по работе с мощным инструментом для анализа и обработки строк.
Иллюстрация: Оля Ежак для SKillbox Media
Журналист, изучает Python. Любит разбираться в мелочах, общаться с людьми и понимать их.
Само словосочетание «регулярные выражения» звучит непонятно и выглядит страшно, но на самом деле ничего сложного в работе с ними нет. В этой статье мы познакомим вас с их логикой и основными принципами и научим разговаривать на языке шаблонов. В хорошем смысле слова.
Содержание:
- Что такое регулярные выражения
- Синтаксис регулярок
- Как ведётся поиск
- Квантификаторы и логическое ИЛИ при группировке
- Регулярные выражения в Python: модуль re и Match-объекты
- Жадный и ленивый пропуск
- Примеры и задачи
Представьте, что вы снова в школе, на уроке истории. Вам нужно решить итоговую контрольную работу по всем датам, которые проходили в четверти.
Но тут вас поджидает препятствие: все даты разбросаны по нескольким главам учебника по десятку страниц каждая. Читать полкниги в поисках нужных вам крупиц информации — такое себе удовольствие. Тем более когда каждая минута на счету.
К счастью, вы — человек неглупый (не зря же пошли в IT), тренированный и быстро соображающий. Поэтому моментально замечаете основные закономерности:
- даты обозначаются цифрами: арабскими, если это год и месяц, и римскими, если век;
- учебник — по истории позднего Средневековья и Нового времени, поэтому все даты, написанные арабскими цифрами, — четырёхсимвольные;
- после римских цифр всегда идёт слово «век».
Теперь у вас есть шаблон нужной информации. Остаётся лишь пролистать страницу за страницей и записать даты в смартфон (или себе на подкорку). Вуаля: пятёрка за четверть у вас в дневнике, а премия от родителей за отличную учёбу — в кармане.
По такому же принципу работают и регулярные выражения: они ведут поиск фрагментов текста по определённому шаблону. Если фрагмент совпадает с шаблоном — с ним можно работать.
Запишем логику поиска исторических дат в виде регулярных выражений (они ещё называются Regular Expressions, сокращённо regex или regexp). Выглядеть он будет так:
(?:d{4})|(?:[IVX]+ век)
Приятные новости: regex — настолько полезный и мощный инструмент, что поддерживается почти всеми современными языками программирования, в том числе и Python. Причём соответствующий синтаксис в разных языках очень схож. Так что, выучив его в одном языке, можно пользоваться им в других, практически не переучиваясь. Поехали.
С помощью regex можно искать как вполне конкретные выражения (например, слово «век» — последовательность букв «в», «е» и «к»), так и что-то более общее (например, любую букву или цифру).
Для обозначения второй категории существуют специальные символы. Вот некоторые из них:
Символ | Что означает | Пример использования шаблона | Пример вывода |
---|---|---|---|
. | Любой символ, кроме новой строки (n) | H.llo, .orld
20.. год |
Hello, world; Hallo, 2orld
2022 год, 2010 год |
[…] | Любой символ из указанных в скобках. Символы можно задавать как перечислением, так и указывая диапазон через дефис | [abc123]
[A-Z] [A-Za-z0-9] [А-ЯЁа-яё] |
а; 1
B; T A; s; 1 А; ё |
[^…] | Любой символ, кроме указанных в скобках | [^A-Za-z] | з, 4 |
^ | Начало строки | ^Добрый день, | 0 |
$ | Конец строки | До свидания!$ | 0 |
| | Логическое ИЛИ. Регулярное выражение будет искать один из нескольких вариантов | [0-9]|[IVXLCDM] — регулярное выражение будет находить совпадение, если цифра является либо арабской, либо римской | 5; V |
Экранирование. Помогает регулярным выражениям ориентироваться, является ли следующий за символ обычным или специальным | AdwZ — экранирование превращает буквы алфавита в спецсимволы.
[.] — экранирование превращает спецсимволы в обычные |
0 |
Важное замечание 1. Регулярные выражения зависимы от регистра, то есть «А» и «а» при поиске будут считаться разными символами.
Важное замечание 2. Буквы «Ё» и «ё» не входят в диапазон «А — Я» и «а — я». Так что, задавая русский алфавит, их нужно выписывать отдельно.
На экранировании остановимся подробнее. По умолчанию символы .^$*+? {}[]|() являются спецсимволами — то есть они выполняют определённые функции. Чтобы сделать спецсимволы обычными, их нужно экранировать .
Таким образом, . будет обозначать любой символ, а . — знак точки. Чтобы написать обратный слеш, его тоже нужно экранировать, то есть в регулярных выражениях он будет выглядеть так: \.
Обратная ситуация с некоторыми алфавитными символами. По умолчанию они считаются просто буквами, но при экранировании начинают играть роль спецсимволов.
Символ | Что означает |
---|---|
d | Любая цифра. То же самое, что [0-9] |
D | Любой символ, кроме цифры. То же самое, что [^0-9] |
w | Любая буква, цифра и нижнее подчёркивание |
W | Любой символ, кроме буквы, цифры и нижнего подчёркивания |
s | Любой пробельный символ (пробел, новая строка, табуляция, возврат каретки и тому подобное) |
S | Любой символ, кроме пробельного |
A | Начало строки. То же самое, что ^ |
Z | Конец строки. То же самое, что $ |
b | Начало или конец слова |
B | Середина слова |
n, t, r | Стандартные строковые обозначения: новая строка, табуляция, возврат каретки |
Важное замечание. A, Z, b и B указывают не на конкретный символ, а на положение других символов относительно друг друга. Можно сказать, что они указывают на пространство между символами.
Например, регулярное выражение b[А-ЯЁаяё]b будет искать только те буквы, которые отделены друг от друга пробелами или знаками препинания.
Часто при записи регулярного выражения какая-то часть шаблона должна повторяться определённое количество раз. Число вхождений в синтаксисе regex задают с помощью квантификаторов. Они всегда помещаются после той части шаблона, которую нужно повторить.
Символ | Что означает | Примеры шаблона | Примеры вывода |
---|---|---|---|
{} | Указывает количество вхождений, можно задавать единичным числом или диапазоном | d{4} — цифра, четыре подряд
d{1,4} — цифра, от одного до четырёх раз подряд d{2,} — цифра, от двух раз подряд d{,4} — цифра, от 0 до 4 раз подряд |
1243, 1876
1, 12, 176, 1589 22, 456, 988888 5, 15, 987, 1234 |
? | От нуля до одного вхождения. То же самое, что {0,1} | d? | 0 |
* | От нуля вхождений. То же самое, что {0,} | d* | 0 |
+ | От одного вхождения. То же самое, что {1,} | d+ | 0 |
Теперь давайте ещё раз посмотрим на наше регулярное выражение для поиска дат по учебнику истории:
(?:d{4})|(?:[IVX]+ век)
В нём есть несколько дополнительных символов, о которых рассказано ниже, но начинка этого выражения уже понятна.
- d{4} — цифра, четыре подряд
- | — логическое ИЛИ
- [IVX]+ век — символ I, V или X, одно или более вхождений, пробел, слово «век»
Попрактиковаться в составлении регулярных выражений можно на сайте regex101.com. А мы разберём основные приёмы их использования и решим несколько задач.
Уточним ещё несколько терминов regex.
Регулярные выражения — это инструмент для работы со строками, которые и являются основной их единицей.
Строка представляет собой как само регулярное выражение, так и текст, по которому ведётся поиск.
Найденные в тексте совпадения с шаблоном называются подстроками. Например, у нас есть регулярное выражение м. (буква «м», затем любой символ) и текст «Мама мыла раму». Применяя регулярное выражение к тексту, мы найдём подстроки «ма», «мы» и «му». Подстроку «Ма» наше выражение пропустит из-за разницы в регистре.
Есть и более мелкая единица, чем подстрока, — группа. Она представляет собой часть подстроки, которую мы попросили выделить специально. Группы выделяются круглыми скобками (…).
Возьмём ту же строку «Мама мыла раму» и применим к ней следующее регулярное выражение:
(w)(w{3})
Оно значит: буквенный символ, выделенный группой, и за ним ещё три буквенных символа, также выделенных группой. Итого весь шаблон представляет собой четыре буквенных символа.
В нашем тексте это выражение найдёт три совпадения, в каждом из которых выделит две группы:
Подстрока | Группа 1 | Группа 2 |
---|---|---|
Мама | М | ама |
мыла | м | ыла |
раму | р | аму |
Это помогает извлечь из найденной подстроки конкретную информацию, отбросив всё остальное. Например, мы нашли адрес, состоящий из названия улицы, номера дома и номера квартиры. Подстрока будет представлять собой адрес целиком, а в группы можно поместить отдельно каждый его структурный элемент — и потом обращаться к нему напрямую.
Группам можно давать имена с помощью такой формы: (? P<name>…)
Вот так будет выглядеть наш шаблон, ищущий четырёхбуквенные слова, если мы дадим имена группам:
?P<first_letter>w)(?P<rest_letters>w{3})
Уберём группы и упростим регулярное выражение, чтобы оно искало только подстроку:
w{4}
Немного изменим текст, по которому ищем совпадения: «Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке».
Регулярное выражение ищет четыре буквенных символа подряд, поэтому в качестве отдельных подстрок находит также «пило», «раме», «рабо», «тает», «лесо» и «пилк».
Исправьте регулярное выражение так, чтобы оно находило только четырёхбуквенные слова. То есть оно должно найти подстроки «мама», «мыла», «раму» и «папа» — и ничего больше.
Подсказка, если не можете решить задачу
Используйте символ b.
Важное замечание. При написании regex нужно помнить, что они ищут только непересекающиеся подстроки. Под шаблон w{4} в слове «работает» подходят не только подстроки «рабо» и «тает», но и «абот», «бота», «отае». Их регулярное выражение не находит, потому что тогда бы эти подстроки пересеклись с другими — а в regex так нельзя.
Нередко при использовании регулярных выражений требуется применить квантификатор либо логическое ИЛИ не к отдельному символу, а к целой группе. Именно так мы поступили в нашем шаблоне для поиска дат по учебнику истории:
(?:d{4})|(?:[IVX]+ век)
С помощью скобок мы сказали: выдайте совпадение, если в тексте присутствует хотя бы один из двух вариантов — либо год, либо век.
Важное замечание.? : в начале группы означает, что мы просим regex не запоминать эту группу. Если все группы открываются символами? :, то регулярные выражения вернут только подстроку и ни одной группы.
В Python это может быть полезно, потому что некоторые re-функции возвращают разные результаты в зависимости от того, запомнили ли регулярные выражения какие-то группы или нет.
Также к группам удобно применять квантификаторы. Например, имена многих дроидов в «Звёздных войнах» построены по принципу: буква — цифра — буква — цифра.
Вот так это выглядит без групп:
[A-Z]d[A-Z]d
И вот так с ними:
(?:[A-Z]d){2}
Особенно полезно использовать незапоминаемые группы со сложными шаблонами.
Чтобы работать с регулярными выражениями в Python, необходимо импортировать модуль re:
import re
Это даёт доступ к нескольким функциям. Вот их краткое описание.
Функция | Что делает | Если находит совпадение | Если не находит совпадение |
---|---|---|---|
re.match (pattern, string) | Ищет pattern в начале строки string | Возвращает Match-объект | Возвращает None |
re.search (pattern, string) | Ищет pattern по всей строке string | Возвращает Match-объект с первым совпадением, остальные не находит | Возвращает None |
re.finditer (pattern, string) | Ищет pattern по всей строке string | Возвращает итератор, содержащий Match-объекты для каждого найденного совпадения | Возвращает пустой итератор |
re.findall (pattern, string) | Ищет pattern по всей строке string | Возвращает список со всеми найденными совпадениями | Возвращает None |
re.split (pattern, string, [maxsplit=0]) | Разделяет строку string по подстрокам, соответствующим pattern | Возвращает список строк, на которые разделила исходную строку | Возвращает список строк, единственный элемент которого — неразделённая исходная строка |
re.sub (pattern, repl, string) | Заменяет в строке string все pattern на repl | Возвращает строку в изменённом виде | Возвращает строку в исходном виде |
re.compile (pattern) | Собирает регулярное выражение в объект для будущего использования в других re-функциях | Ничего не ищет, всегда возвращает Pattern-объект | 0 |
Важное замечание. Напоминаем, что регулярные выражения по умолчанию ищут только непересекающиеся подстроки.
Для написания регулярных выражений в Python используют r-строки (их называют сырыми, или необработанными). Это связано с тем, что написание знака требует экранирования не только в регулярных выражениях, но и в самом Python тоже.
Чтобы программистам не приходилось экранировать экранирование и писать нагромождения обратных слешей, и придумали r-строки. Синтаксически они обозначаются так:
r'...'
Перечислим самые популярные из них.
Находит совпадение только в том случае, если соответствующая шаблону подстрока находится в начале строки, по которой ведётся поиск:
print (re.match (r'Мама', 'Мама мыла раму')) >>> <re.Match object; span=(0, 4), match='Мама'> print (re.match (r'мыла', 'Мама мыла раму')) >>> None
Как видим, поиск по шаблону «Мама» нашёл совпадение и вернул Match-объект. Слово же «мыла», хотя и есть в строке, находится не в начале. Поэтому регулярное выражение ничего не находит и возвращается None.
Ищет совпадения по всему тексту:
print (re.search (r'Мама', 'Мама мыла раму')) >>> <re.Match object; span=(0, 4), match='Мама'> print (re.search (r'мыла', 'Мама мыла раму')) >>> <re.Match object; span=(5, 9), match='мыла'>
При этом re.search возвращает только первое совпадение, даже если в строке, по которой ведётся поиск, их больше. Проверим это:
print (re.search (r'мыла', 'Мама мыла раму, а потом ещё раз мыла, потому что не домыла')) >>> <re.Match object; span=(5, 9), match='мыла'>
Возвращает итератор с объектами, к которым можно обратиться через цикл:
results = re.finditer (r'мыла', 'Мама мыла раму, а потом ещё раз мыла, потому что не домыла') print (results) >>> <callable_iterator object at 0x000001C4CDE446D0> for match in results: print (match) >>> <re.Match object; span=(5, 9), match='мыла'> >>> <re.Match object; span=(32, 36), match='мыла'> >>> <re.Match object; span=(54, 58), match='мыла'>
Эта функция очень полезна, если вы хотите получить Match-объект для каждого совпадения.
В Match-объектах хранится много всего интересного. Посмотрим внимательнее на объект с подстрокой «Мама», который нашла функция re.match:
<re.Match object; span=(0, 4), match='Мама'>
span — это индекс начала и конца найденной подстроки в тексте, по которому мы искали совпадение. Обратите внимание, что второй индекс не включается в подстроку.
match — это собственно найденная подстрока. Если подстрока длинная, то она будет отображаться не целиком.
Это, конечно же, не всё, что можно получить от Match-объекта. Рассмотрим ещё несколько методов.
Возвращает найденную подстроку, если ему не передавать аргумент или передать аргумент 0. То же самое делает обращение к объекту по индексу 0:
match = re.match (r'Мама', 'Мама мыла раму') print (match.group()) >>> Мама print (match.group(0)) >>> Мама print (match[0]) >>> Мама
Если регулярное выражение поделено на группы, то, начиная с единицы, можно вызвать группу отдельно от строки:
match = re.match (r'(М)(ама)', 'Мама мыла раму') print (match.group(1)) print (match.group(2)) >>> М >>> ама print (match[1]) print (match[2]) >>> М >>> ама #Методом group также можно получить кортеж из нужных групп. print (match.group(1,2)) >>> ('М', 'ама')
Если группы поименованы, то в качестве аргумента метода group можно передавать их название:
match = re.match (r'(?P<first_letter>М)(?P<rest_letters>ама)', 'Мама мыла раму') print (match.group('first_letter')) print (match.group('rest_letters')) >>> М >>> ама
Если одна и та же группа соответствует шаблону несколько раз, то в группу запишется только последнее совпадение:
#Помещаем в группу один буквенный символ, при этом шаблон представляет собой четыре таких символа. match = re.match (r'(w){4}', 'Мама мыла раму') print (match.group(0)) >>> Мама print (match.group(1)) >>> а
Возвращает кортеж с группами:
match = re.match (r'(М)(ама)', 'Мама мыла раму') print (match.groups()) >>> ('М', 'ама')
Возвращает кортеж с индексом начала и конца подстроки в исходном тексте. Если мы хотим получить только первый индекс, можно использовать метод start, только последний — end:
match = re.search (r'мыла', 'Мама мыла раму') print (match.span()) >>> (5, 9) print (match.start()) >>> 5 print (match.end()) >>> 9
Возвращает просто список совпадений. Никаких Match-объектов, к которым нужно дополнительно обращаться:
#В этом примере в качестве регулярного выражения мы используем правильный ответ на задание 0. match_list = re.findall (r'bw{4}b', 'Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке.') print (match_list) >>> ['Мама', 'мыла', 'раму', 'папа']
Функция ведёт себя по-другому, если в регулярном выражении есть деление на группы. Тогда функция возвращает список кортежей с группами:
match_list = re.findall (r'b(w{1})(w{3})b', 'Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке.') print (match_list) >>> [('М', 'ама'), ('м', 'ыла'), ('р', 'аму'), ('п', 'апа')]
Аналог метода str.split. Делит исходную строку по шаблону, а сам шаблон исключает из результата:
#Поделим строку по запятой и пробелу после неё. split_string = re.split (r', ', 'Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке.') print (split_string) >>> ['Мама мыла раму', 'а папа был на пилораме', 'потому что работает на лесопилке.']
re.split также имеет дополнительный аргумент maxsplit — это максимальное количество частей, на которые функция может поделить строку. По умолчанию maxsplit равен нулю, то есть не устанавливает никаких ограничений:
#Приравняем аргумент maxsplit к единице. split_string = re.split (r', ', 'Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке.', maxsplit=1) print (split_string) >>> ['Мама мыла раму', 'а папа был на пилораме, потому что работает на лесопилке.']
Если в re.split мы указываем группы, то они попадают в список строк в качестве отдельных элементов. Для наглядности поделим исходную строку на слог «па»:
#Помещаем буквы «п» и «а» в одну группу. split_string = re.split (r'(па)', 'Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке.') print (split_string) >>> ['Мама мыла раму, а ', 'па', '', 'па', ' был на пилораме, потому что работает на лесопилке.'] #Помещаем буквы «п» и «а» в разные группы. split_string = re.split (r'(п)(а)', 'Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке.') print (split_string) >>> ['Мама мыла раму, а ', 'п', 'а', '', 'п', 'а', ' был на пилораме, потому что работает на лесопилке.']
Требует указания дополнительного аргумента в виде строки, на которую и будет заменять найденные совпадения:
new_string = re.sub (r'Мама', 'Дочка', 'Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке.') print (new_string) >>> Дочка мыла раму, а папа был на пилораме, потому что работает на лесопилке.
Дополнительные возможности у функции появляются при применении групп. В качестве аргумента замены ему можно передать не строку, а ссылку на номер группы в виде n. Тогда он подставит на нужное место соответствующую группу из шаблона. Это очень удобно, когда нужно поменять местами структурные элементы в тексте:
new_string = re.sub (r'(w+) (w+) (w+),', r'2 3 1 –', 'Бендер Остап Ибрагимович, директор ООО "Рога и копыта"') print (new_string) >>> Остап Ибрагимович Бендер — директор ООО "Рога и копыта"
Используется для ускорения и упрощения кода, когда одно и то же регулярное выражение применяется в нём несколько раз. Её синтаксис выглядит так:
pattern = re.compile (r'Мама') print (pattern.search ('Мама мыла раму')) >>> <re.Match object; span=(0, 4), match='Мама'> print (pattern.sub ('Дочка', 'Мама мыла раму')) >>> Дочка мыла раму
Нередко в регулярных выражениях нужно учесть сразу много вариантов и опций, из-за чего их структура усложняется. А regex даже простые и короткие читать нелегко, что уж говорить о длинных.
Чтобы хоть как-то облегчить чтение регулярок, в Python r-строки можно делить точно так же, как и обычные. Возьмём наше выражение для поиска дат по учебнику истории:
re.findall (r'(?:d{4})|(?:[IVX]+ век)', text)
Его же можно написать вот в таком виде:
re.findall (r'(?:d{4})' r'|' r'(?:[IVX]+ век)', text)
Часто при написании регулярных выражений приходится использовать квантификаторы, охватывающие диапазон значений. Например, d{1,4}. Как регулярные выражения решают, сколько цифр им захватить, одну или четыре? Это определяется пропуском квантификаторов.
По умолчанию все квантификаторы являются жадными, то есть стараются захватить столько подходящих под шаблон символов, сколько смогут.
В некоторых случаях это может стать проблемой. Например, возьмём часть оглавления поэмы Венедикта Ерофеева «Москва — Петушки», записанную в одну строку:
Фрязево — 61-й километр……….64 61-й километр — 65-й километр…68 65-й километр — Павлово-Посад…71 Павлово-Посад — Назарьево……..73 Назарьево — Дрезна……………77 Дрезна — 85-й километр………..80
Нужно написать регулярное выражение, которое выделит каждый пункт оглавления. Для этого определим признаки, по которым мы будем это делать:
- Каждый пункт начинается с буквы или цифры (для этого используем шаблон w).
- Он может содержать внутри себя любой набор символов: буквы, цифры, знаки препинания (для этого используем шаблон .+).
- Он заканчивается на точку, после которой следует от одной до трёх цифр (для этого используем шаблон .d{1,3}).
Посмотрим в конструкторе, как работает наше выражение:
Что же произошло? Почему найдено только одно совпадение, причем за него посчитали весь текст сразу? Всё дело в жадности квантификатора +, который старается захватить максимально возможное количество подходящих символов.
В итоге шаблон w находит совпадение с буквой «Ф» в начале текста, шаблон .d{1,3} находит совпадение с «.80» в конце текста, а всё, что между ними, покрывается шаблоном .+.
Чтобы квантификатор захватывал минимально возможное количество символов, его нужно сделать ленивым. В таком случае каждый раз, находя совпадение с шаблоном ., регулярное выражение будет спрашивать: «Подходят ли следующие символы в строке под оставшуюся часть шаблона?»
Если нет, то функция будет искать следующее совпадение с .. А если да, то . закончит свою работу и следующие символы строки будут сравниваться со следующей частью регулярного выражения: .d{1,3}.
Чтобы объявить квантификатор ленивым, после него надо поставить символ ?. Сделаем ленивым квантификатор + в нашем регулярном выражении для поиска строк в оглавлении:
Теперь, когда мы уверены в правильности работы нашего регулярного выражения, используем функцию re.findall, чтобы выписать оглавление построчно:
content = 'Фрязево — 61-й километр..........64 61-й километр — 65-й километр....68 65-й километр — Павлово-Посад....71 Павлово-Посад — Назарьево........73 Назарьево — Дрезна...............77 Дрезна — 85-й километр...........80' strings = re.findall (r'w.+?.d{1,3}', content) for string in strings: print (string) #Результат на экране. >>> Фрязево — 61-й километр..........64 >>> 61-й километр — 65-й километр....68 >>> 65-й километр — Павлово-Посад....71 >>> Павлово-Посад — Назарьево........73 >>> Назарьево — Дрезна...............77 >>> Дрезна — 85-й километр...........80
В некоторых случаях одну и ту же задачу можно решить разными способами, используя разные возможности регулярок. Попробуйте решить следующие задачи самостоятельно. Возможно, у вас даже получится сделать это более эффективно.
При обнародовании судебных решений из них извлекают персональные данные участников процесса — фамилии, имена и отчества. Каждое слово в Ф. И. О. начинается с заглавной буквы, при этом фамилия может быть двойная.
Напишите программу, которая заменит в тексте Ф. И. О. подсудимого на N.
Подсудимая Эверт-Колокольцева Елизавета Александровна в судебном заседании вину инкриминируемого правонарушения признала в полном объёме и суду показала, что 14 сентября 1876 года, будучи в состоянии алкогольного опьянения от безысходности, в связи с состоянием здоровья позвонила со своего стационарного телефона в полицию, сообщив о том, что у неё в квартире якобы заложена бомба. После чего приехали сотрудники полиции, скорая и пожарные, которым она сообщила, что бомба — это она.
«Подсудимая N в судебном заседании» и далее по тексту.
Подсказка
Используйте незапоминаемую опциональную группу вида (? : …)? , чтобы обозначить вторую часть фамилии после дефиса.
Решение
#Сначала кладём в переменную string текст строки, по которой ведём поиск.
print (re.sub (r'[А-ЯЁ]w*'
r'(?:-[А-ЯЁ]w*)?'
r'(?: [А-ЯЁ]w*){2}', 'N', string))
Большинство адресов состоит из трёх частей: название улицы, номер дома и номер квартиры. Название улицы может состоять из нескольких слов, каждое из которых пишется с заглавной буквы. Номер дома может содержать после себя букву.
Перед названием улицы может быть написано «Улица», «улица», «Ул.» или «ул.», перед номером дома — «дом» или «д.», перед номером квартиры — «квартира» или «кв.». Также номер дома и номер квартиры могут быть разделены дефисом без пробелов.
Дан текст, в нём нужно найти все адреса и вывести их в виде «Пушкина 32-135».
Для упрощения мы не будем учитывать дома, которые находятся не на улицах, а на площадях, набережных, бульварах и так далее.
Добрый день!
Сегодня на выезды потребуется отправить трёх-четырёх специалистов, остальных держите в офисе. Некоторые заявки пришли на конкретных людей, но можно вызвать и других, смотрите по ситуации, как лучше их отправить, чтобы всех объездить сегодня.
Петрову П. П. попросили выехать по адресам ул. Культуры 78 кв. 6, улица Мира дом 12Б квартира 144. Смирнова С. С. просят подъехать только по адресу: Восьмого Марта 106-19. Без предпочтений по специалистам пришли запросы с адресов: улица Свободы 54 6, Улица Шишкина дом 9 кв. 15, ул. Лермонтова 18 кв. 93.
Все адреса скопированы из заявок, корректность подтверждена.
Культуры 78-6
Мира 12Б-144
Восьмого Марта 106-19
Свободы 54-6
Шишкина 9-15
Лермонтова 18-93
Подсказка
Используйте деление на группы, чтобы удобно выстроить структуру выражения. Попросите regex запоминать только нужные вам части адреса, чтобы функция не возвращала вам лишние подгруппы.
Решение
#Сначала кладём в переменную string текст строки, по которой ведём поиск.
pattern = re.compile (r'(?:[Уу]л(?:.|ица) )?'
r'((?:[А-ЯЁ]w+)(?: [А-ЯЁ]w+)*)'
r' (?:дом |д. )?'
r'(d+w?)'
r'[ -](?:квартира |кв. )?'
r'(d+)')
addresses = pattern.findall (text)
for address in addresses:
print (f'{address[0]} {address[1]}-{address[2]}')
Структура этого регулярного выражения довольно сложная. Чтобы в нём разобраться, посмотрите на схему. Прямоугольники обозначают обязательные элементы, овалы — опциональные. Развилки символизируют разные варианты, которые допускает наш шаблон. Красным цветом очерчены группы, которые мы запоминаем.
Писатели в поиске собственного неповторимого стиля нередко изобретают оригинальные творческие приёмы и неукоснительно им следуют. Например, Сергей Довлатов следил за тем, чтобы слова в предложении не начинались с одной и той же буквы.
Даны несколько предложений. Программа должна проверить, встречаются ли в каждом из них слова на одинаковую букву. Если таких нет, она печатает: «Метод Довлатова соблюдён». А если есть: «Вы расстроили Сергея Донатовича».
Важно. Чтобы регулярные выражения не рассматривали заглавные и прописные буквы как разные символы, передайте re-функции дополнительный аргумент flags=re.I или flags=re.IGNORECASE.
Здесь все слова начинаются с разных букв.
А в этом предложении есть слова, которые всё-таки начинаются на одну и ту же букву.
А здесь совсем интересно: символ «а» однобуквенный.
Метод Довлатова соблюдён
Вы расстроили Сергея Донатовича
Вы расстроили Сергея Донатовича
Подсказка
Чтобы указать на начало слова, используйте символ b.
Чтобы в каждом совпадении regex не старалось захватить максимум, используйте ленивый пропуск.
Чтобы найти повторяющийся символ, используйте ссылку на группу в виде 1.
Решение
#Сначала кладём в переменную string текст строки, по которой ведём поиск.
pattern = r'b(w)w*.*?b1'
match = re.search (pattern, string, flags=re.I)
if match is None:
print ('Метод Довлатова соблюдён')
else:
print ('Вы расстроили Сергея Донатовича')
Вернёмся к регулярному выражению, которое ищет даты в учебнике истории: (? :d{4})|(? : [IVX]+ век).
Оно в целом справляется со своей задачей, но также находит много ненужных чисел. Например, количество человек, которые участвовали в битве, тоже может быть описано четырьмя цифрами подряд.
Чтобы не получать лишние результаты, обратим внимание на то, как именно могут быть записаны годы. Есть несколько вариантов записи: 1400 год, 1400 г., 1400–1500 годы, 1400–1500 гг., (1400), (1400–1500).
Чтобы немного упростить задачу и не раздувать регулярное выражение, мы не будем искать конструкции «с такого-то по такой-то год» и «между таким-то и таким-то годом».
Важное замечание. Не забывайте про экранирование, если хотите использовать точки и скобки в качестве обычных, а не специальных символов. Так программа правильно поймёт, что вы имеете в виду.
Началом Реформации принято считать 31 октября 1517 г. — день, когда Мартин Лютер (1483–1546) прибил к дверям виттенбергской Замковой церкви свои «95 тезисов», в которых выступил против злоупотреблений Католической церкви. Реформация охватила практически всю Европу и продолжалась в течение всего XVI века и первой половины XVII века. Одно из самых известных и кровавых событий Реформации — Варфоломеевская ночь во Франции, произошедшая в ночь на 24 августа 1572 года.
Точное число жертв так и не удалось установить достоверно. Погибли по меньшей мере 2000 гугенотов в Париже и 3000 — в провинциях. Герцог де Сюлли, сам едва избежавший смерти во время резни, говорил о 70 000 жертв. Для Парижа единственным точным числом остаётся 1100 погибших во время Варфоломеевской ночи.
Этому событию предшествовали три других, произошедшие в 1570–1572 годах: Сен-Жерменский мирный договор (1570), свадьба гугенота Генриха Наваррского и Маргариты Валуа (1572) и неудавшееся покушение на убийство адмирала Колиньи (1572).
[‘1517 г.’, ‘(1483–1546)’, ‘XVI век’, ‘XVII век’, ‘1572 год’, ‘1570–1572 годах’, ‘(1570)’, ‘(1572)’, ‘(1572)’]
Решение
#Сначала кладём в переменную string текст строки, по которой ведём поиск.
pattern = re.compile (r'(?:(d{4}(?:-d{4})?))'
r'|'
r'(?:'
r'(?:d{4}-)?d{4} '
r'(?:'
r'(?:год(?:ы|ах|ов)?)'
r'|'
r'(?:гг?.)'
r')'
r')'
r'|'
r'(?:[IVX]+ век)')
print (pattern.findall (string))
Если вам сложно разобраться в структуре этого выражения, то вот его схема:
Научитесь: Профессия Python-разработчик
Узнать больше