Практическое руководство по регулярным выражениям
- Автор:
-
A.M. Kuchling <amk@amk.ca>
Введение
Регулярные выражения (также называемые RE, regex или шаблонами regex) — это, по сути, небольшой узкоспециализированный язык программирования, встроенный в Python и доступный через модуль re. Используя этот небольшой язык, вы задаёте правила для набора строк, которые хотите найти; в этот набор могут входить предложения на русском языке, адреса электронной почты, команды TeX или что угодно ещё. Затем можно задавать вопросы, например: «Соответствует ли эта строка шаблону?» или «Есть ли в этой строке совпадение с шаблоном?». Кроме того, RE можно использовать для изменения строки или разделения её на части разными способами.
Шаблоны регулярных выражений компилируются в последовательность байт-кодов, которые затем выполняются написанным на C механизмом поиска совпадений. Для продвинутого использования может понадобиться внимательно изучить, как механизм выполняет заданное RE, и записать RE определённым образом, чтобы получить байт-код, работающий быстрее. В этом документе оптимизация не рассматривается, поскольку для неё необходимо хорошо понимать внутреннее устройство механизма поиска совпадений.
Язык регулярных выражений относительно мал и ограничен, поэтому с его помощью нельзя выполнить все возможные задачи по обработке строк. Есть также задачи, которые можно решать регулярными выражениями, но соответствующие выражения оказываются очень сложными. В таких случаях, возможно, лучше написать код на Python для обработки данных: хотя он будет работать медленнее сложного регулярного выражения, вероятно, его будет проще понять.
Простые шаблоны
Начнём с самых простых регулярных выражений. Поскольку регулярные выражения используются для работы со строками, начнём с наиболее распространённой задачи: поиска символов.
Подробное объяснение основ информатики, лежащих в основе регулярных выражений (детерминированных и недетерминированных конечных автоматов), можно найти практически в любом учебнике по разработке компиляторов.
Поиск символов
Большинство букв и символов просто соответствуют сами себе. Например, регулярное выражение test точно совпадёт со строкой test. (Можно включить режим без учёта регистра, в котором это RE также будет совпадать с Test или TEST; подробнее об этом далее.)
Из этого правила есть исключения: некоторые символы являются специальными метасимволами и не соответствуют сами себе. Вместо этого они указывают, что нужно искать нечто необычное, либо влияют на другие части RE, повторяя их или изменяя их значение. Большая часть этого руководства посвящена рассмотрению различных метасимволов и их назначения.
Ниже приведён полный список метасимволов; их значения будут рассмотрены в оставшейся части этого руководства.
. ^ $ * + ? { } [ ] \ | ( )
Первые метасимволы, которые мы рассмотрим, — [ и ]. Они используются для задания класса символов — набора символов, которые требуется найти. Символы можно перечислять по отдельности или задавать диапазон, указав два символа и разделив их '-'. Например, [abc] найдёт любой из символов a, b или c; это то же самое, что [a-c], где тот же набор символов задан с помощью диапазона. Если нужно найти только строчные буквы, RE будет выглядеть так: [a-z].
Метасимволы (кроме \) не действуют внутри классов. Например, [akm$] найдёт любой из символов 'a', 'k', 'm' или '$'; '$' обычно является метасимволом, но внутри класса символов утрачивает своё специальное значение.
Можно находить символы, не перечисленные в классе, инвертировав множество. Для этого в начало класса добавляют '^'. Например, [^5] найдёт любой символ, кроме '5'. Если символ ^ встречается в другом месте класса символов, он не имеет специального значения. Например: [5^] найдёт либо '5', либо '^'.
Пожалуй, самый важный метасимвол — обратная косая черта, \. Как и в строковых литералах Python, за обратной косой чертой могут следовать различные символы, образуя специальные последовательности. Она также используется для экранирования всех метасимволов, чтобы их по-прежнему можно было искать в шаблонах; например, если нужно найти [ или \, перед ними можно поставить обратную косую черту, чтобы отменить их специальное значение: \[ или \\.
Некоторые специальные последовательности, начинающиеся с '\', обозначают заранее определённые наборы часто нужных символов, например множество цифр, множество букв или множество всех символов, кроме пробельных.
Рассмотрим пример: \w соответствует любому буквенно-цифровому символу. Если шаблон regex задан в виде байтов, это эквивалентно классу [a-zA-Z0-9_]. Если шаблон regex является строкой, \w найдёт все символы, отмеченные как буквы в базе данных Unicode, доступной через модуль unicodedata. В строковом шаблоне можно использовать более узкое определение \w, передав флаг re.ASCII при компиляции регулярного выражения.
Приведённый ниже список специальных последовательностей неполон. Полный список последовательностей и развёрнутые определения классов для строковых шаблонов Unicode см. в заключительной части раздела Синтаксис регулярных выражений в справочнике стандартной библиотеки. В целом варианты для Unicode соответствуют любому символу, принадлежащему подходящей категории в базе данных Unicode.
-
\d -
Соответствует любой десятичной цифре; эквивалентен классу
[0-9]. -
\D -
Соответствует любому символу, кроме цифр; эквивалентен классу
[^0-9]. -
\s -
Соответствует любому пробельному символу; эквивалентен классу
[ \t\n\r\f\v]. -
\S -
Соответствует любому символу, кроме пробельных; эквивалентен классу
[^ \t\n\r\f\v]. -
\w -
Соответствует любому буквенно-цифровому символу; эквивалентен классу
[a-zA-Z0-9_]. -
\W -
Соответствует любому символу, кроме буквенно-цифровых; эквивалентен классу
[^a-zA-Z0-9_].
Эти последовательности можно включать в класс символов. Например, [\s,.] — это класс символов, который найдёт любой пробельный символ, а также ',' или '.'.
Последний метасимвол в этом разделе — .. Он соответствует любому символу, кроме символа новой строки; в альтернативном режиме (re.DOTALL) он соответствует и символу новой строки. . часто используют, когда нужно найти «любой символ».
Повторение
Возможность находить различные наборы символов — первая функция регулярных выражений, которой нет среди методов работы со строками. Однако если бы это было единственным дополнительным свойством regex, они не представляли бы собой большого шага вперёд. Ещё одна возможность — задавать, сколько раз должны повторяться части RE.
Первый метасимвол повторения, который мы рассмотрим, — *. * не соответствует самому символу '*'; вместо этого он указывает, что предыдущий символ может встретиться ноль или более раз, а не ровно один раз.
Например, ca*t найдёт 'ct' (0 символов 'a'), 'cat' (1 символ 'a'), 'caaat' (3 символа 'a') и так далее.
Повторы, такие как *, являются жадными: при повторении RE механизм поиска совпадений будет пытаться повторить его как можно больше раз. Если последующие части шаблона не совпадут, механизм вернётся назад и попробует ещё раз с меньшим количеством повторений.
Пошаговый пример поможет лучше это понять. Рассмотрим выражение a[bcd]*b. Оно соответствует букве 'a', нулю или более буквам из класса [bcd] и в конце — символу 'b'. Теперь представим, что мы ищем совпадение с этим RE в строке 'abcbd'.
Шаг | Совпадение | Пояснение |
|---|---|---|
1 |
| Совпадает |
2 |
| Механизм находит |
3 | Неудача | Механизм пытается найти |
4 |
| Механизм возвращается назад, чтобы |
5 | Неудача | Повторная попытка найти |
6 |
| Механизм снова возвращается назад, чтобы |
6 |
| Механизм снова пытается найти |
Теперь достигнут конец RE, и найдено совпадение 'abcb'. Этот пример показывает, что сначала механизм поиска совпадений продвигается настолько далеко, насколько возможно, а если совпадение не найдено, постепенно возвращается назад и снова и снова пытается найти совпадение с оставшейся частью RE. Он будет возвращаться назад, пока не проверит ноль повторений [bcd]*; если и после этого совпадение не будет найдено, механизм заключит, что строка совсем не соответствует RE.
Ещё один метасимвол повторения — +, который соответствует одному или нескольким вхождениям. Обратите внимание на разницу между * и +: * соответствует нулю или более вхождениям, поэтому повторяемый элемент может вообще отсутствовать, тогда как + требует как минимум одного вхождения. Например, ca+t найдёт 'cat' (1 'a'), 'caaat' (3 символа 'a'), но не найдёт 'ct'.
Есть ещё два оператора повторения, или квантификатора. Знак вопроса, ?, соответствует либо одному, либо нулю вхождений; его можно считать обозначением необязательного элемента. Например, home-?brew найдёт либо 'homebrew', либо 'home-brew'.
Самый сложный квантификатор — {m,n}, где m и n — десятичные целые числа. Этот квантификатор означает, что элемент должен повториться не менее m и не более n раз. Например, a/{1,3}b найдёт 'a/b', 'a//b' и 'a///b'. Он не найдёт 'ab', где нет косых черт, или 'a////b', где их четыре.
Можно опустить m или n; в таком случае для пропущенного значения будет принято разумное значение. Если опустить m, нижняя граница будет равна 0, а если опустить n, верхняя граница будет равна бесконечности.
Простейший случай {m} означает, что предшествующий элемент должен встретиться ровно m раз. Например, a/{2}b найдёт только 'a//b'.
Читатели, склонные к редукционизму, могут заметить, что три других квантификатора можно записать с помощью этого обозначения. {0,} эквивалентен *, {1,} эквивалентен +, а {0,1} — это то же самое, что ?. По возможности лучше использовать *, + или ? просто потому, что они короче и легче читаются.
Использование регулярных выражений
Теперь, когда мы рассмотрели несколько простых регулярных выражений, как же использовать их в Python? Модуль re предоставляет интерфейс к механизму регулярных выражений, позволяя компилировать регулярные выражения в объекты, а затем выполнять с их помощью сопоставления.
Компиляция регулярных выражений
Регулярные выражения компилируются в объекты шаблонов, у которых есть методы для различных операций, таких как поиск совпадений с шаблоном или замена строк.
>>> import re
>>> p = re.compile('ab*')
>>> p
re.compile('ab*')
re.compile() также принимает необязательный аргумент flags, который используется для включения различных специальных возможностей и вариантов синтаксиса. Доступные настройки мы рассмотрим позже, а пока достаточно одного примера:
>>> p = re.compile('ab*', re.IGNORECASE)
Регулярное выражение передаётся в re.compile() в виде строки. Регулярные выражения обрабатываются как строки, потому что они не являются частью основного языка Python и для их записи не был создан специальный синтаксис. (Некоторым приложениям регулярные выражения вообще не нужны, поэтому нет необходимости перегружать спецификацию языка, включая их в неё.) Вместо этого модуль re — это просто модуль расширения на C, поставляемый вместе с Python, как и модули socket или zlib.
Запись регулярных выражений в строках упрощает язык Python, но у этого подхода есть один недостаток, который будет рассмотрен в следующем разделе.
Проклятие обратной косой черты
Как упоминалось ранее, в регулярных выражениях символ обратной косой черты ('\') используется для обозначения специальных форм или для того, чтобы специальные символы можно было использовать без их особого значения. Это конфликтует с использованием того же символа в Python для той же цели в строковых литералах.
Предположим, вы хотите написать регулярное выражение, которое соответствует строке \section, которая может встретиться в файле LaTeX. Чтобы понять, что нужно написать в коде программы, начните с искомой строки. Затем необходимо экранировать все обратные косые черты и другие метасимволы, поставив перед ними обратную косую черту. В результате получится строка \\section. Полученную строку, которую нужно передать в re.compile(), необходимо записать как \\section. Однако для записи этого значения в виде строкового литерала Python обе обратные косые черты придётся экранировать ещё раз.
Символы | Этап |
|---|---|
| Текстовая строка, которой нужно сопоставить |
| Экранированная обратная косая черта для |
| Экранированные обратные косые черты в строковом литерале |
Короче говоря, чтобы сопоставить буквальную обратную косую черту, в качестве строки регулярного выражения нужно записать '\\\\', поскольку регулярное выражение должно быть \\, а каждую обратную косую черту внутри обычного строкового литерала Python необходимо записывать как \\. В регулярных выражениях, где обратные косые черты встречаются часто, это приводит к их многочисленным повторам и затрудняет понимание полученных строк.
Решение — использовать для регулярных выражений запись необработанных строк Python: обратные косые черты не обрабатываются специальным образом в строковом литерале с префиксом 'r', поэтому r"\n" — это строка из двух символов, содержащая '\' и 'n', тогда как "\n" — это строка из одного символа, содержащая символ новой строки. В коде Python регулярные выражения часто записывают именно в виде необработанных строк.
Кроме того, специальные escape-последовательности, допустимые в регулярных выражениях, но недопустимые в строковых литералах Python, теперь вызывают предупреждение DeprecationWarning, а в конечном итоге станут причиной ошибки SyntaxError. Это означает, что такие последовательности будут недопустимы, если не использовать запись необработанных строк или экранирование обратных косых черт.
Обычная строка | Необработанная строка |
|---|---|
|
|
|
|
|
|
Выполнение сопоставлений
Что делать после создания объекта, представляющего скомпилированное регулярное выражение? У объектов шаблонов есть несколько методов и атрибутов. Здесь будут рассмотрены только самые важные; полный список см. в документации модуля re.
Метод/атрибут | Назначение |
|---|---|
| Определяет, соответствует ли регулярное выражение началу строки. |
| Просматривает строку в поисках любого места, где этому регулярному выражению соответствует часть строки. |
| Находит все подстроки, соответствующие регулярному выражению, и возвращает их в виде списка. |
| Находит все подстроки, соответствующие регулярному выражению, и возвращает их в виде итератора. |
match() и search() возвращают None, если совпадение не найдено. В случае успеха возвращается экземпляр объекта сопоставления, содержащий сведения о совпадении: его начальную и конечную позиции, соответствующую подстроку и многое другое.
Разобраться в этом можно, экспериментируя с модулем re в интерактивном режиме.
В этом HOWTO в примерах используется стандартный интерпретатор Python. Сначала запустите интерпретатор Python, импортируйте модуль re и скомпилируйте регулярное выражение:
>>> import re
>>> p = re.compile('[a-z]+')
>>> p
re.compile('[a-z]+')
Теперь можно проверить соответствие различных строк регулярному выражению [a-z]+. Пустая строка не должна соответствовать ему, поскольку + означает «одно или несколько повторений». В этом случае match() должен вернуть None, поэтому интерпретатор ничего не выведет. Чтобы убедиться в этом, можно явно вывести результат match().
>>> p.match("")
>>> print(p.match(""))
None
Теперь проверим строку, которая должна ему соответствовать, например tempo. В этом случае match() вернёт объект сопоставления, поэтому результат следует сохранить в переменной для дальнейшего использования.
>>> m = p.match('tempo')
>>> m
<re.Match object; span=(0, 5), match='tempo'>
Теперь можно запросить у объекта сопоставления сведения о строке, соответствующей шаблону. У экземпляров объекта сопоставления также есть несколько методов и атрибутов; самые важные из них:
Метод/атрибут | Назначение |
|---|---|
| Возвращает строку, соответствующую регулярному выражению |
| Возвращает начальную позицию совпадения |
| Возвращает конечную позицию совпадения |
| Возвращает кортеж с позициями начала и конца совпадения |
Смысл этих методов вскоре станет понятен, если попробовать их вызвать:
>>> m.group() 'tempo' >>> m.start(), m.end() (0, 5) >>> m.span() (0, 5)
group() возвращает подстроку, соответствующую регулярному выражению. start() и end() возвращают начальный и конечный индексы совпадения. span() возвращает оба индекса — начала и конца — в одном кортеже. Поскольку метод match() проверяет, соответствует ли регулярное выражение началу строки, значение start() всегда будет равно нулю. Однако метод search() просматривает строку, поэтому в этом случае совпадение может начинаться не с нулевой позиции.
>>> print(p.match('::: message'))
None
>>> m = p.search('::: message'); print(m)
<re.Match object; span=(4, 11), match='message'>
>>> m.group()
'message'
>>> m.span()
(4, 11)
В реальных программах чаще всего объект сопоставления сохраняют в переменной, а затем проверяют, является ли он None. Обычно это выглядит так:
p = re.compile( ... )
m = p.match( 'string goes here' )
if m:
print('Match found: ', m.group())
else:
print('No match')
Два метода шаблона возвращают все совпадения с шаблоном. Метод findall() возвращает список соответствующих строк:
>>> p = re.compile(r'\d+')
>>> p.findall('12 drummers drumming, 11 pipers piping, 10 lords a-leaping')
['12', '11', '10']
В этом примере нужен префикс r, который превращает литерал в необработанный строковый литерал: escape-последовательности в обычной «обработанной» строке, которые не распознаются Python (в отличие от регулярных выражений), теперь вызывают предупреждение DeprecationWarning, а в конечном итоге станут причиной ошибки SyntaxError. См. раздел Проклятие обратной косой черты.
Методу findall() необходимо создать весь список, прежде чем вернуть его в качестве результата. Метод finditer() возвращает последовательность экземпляров объекта сопоставления в виде итератора:
>>> iterator = p.finditer('12 drummers drumming, 11 ... 10 ...')
>>> iterator
<callable_iterator object at 0x...>
>>> for match in iterator:
... print(match.span())
...
(0, 2)
(22, 24)
(29, 31)
Функции уровня модуля
Необязательно создавать объект шаблона и вызывать его методы: модуль re также предоставляет функции верхнего уровня с именами match(), search(), findall(), sub() и так далее. Эти функции принимают те же аргументы, что и соответствующий метод шаблона, но строка регулярного выражения добавляется первым аргументом. Они возвращают либо None, либо экземпляр объекта сопоставления.
>>> print(re.match(r'From\s+', 'Fromage amk')) None >>> re.match(r'From\s+', 'From amk Thu May 14 19:12:10 1998') <re.Match object; span=(0, 5), match='From '>
Внутри эти функции просто создают объект шаблона и вызывают его соответствующий метод. Они также сохраняют скомпилированный объект в кэше, поэтому при последующих вызовах с тем же регулярным выражением шаблон не придётся разбирать заново.
Стоит ли использовать эти функции уровня модуля или лучше получить объект шаблона и вызывать его методы самостоятельно? Если вы обращаетесь к регулярному выражению в цикле, предварительная компиляция позволит сэкономить несколько вызовов функций. За пределами циклов разница невелика благодаря внутреннему кэшу.
Флаги компиляции
Флаги компиляции позволяют изменять некоторые аспекты работы регулярных выражений. В модуле re флаги доступны под двумя именами: полным, например IGNORECASE, и коротким, состоящим из одной буквы, например I. (Если вы знакомы с модификаторами шаблонов Perl, короткие формы используют те же буквы; например, краткая форма re.VERBOSE — re.X.) Несколько флагов можно задать, объединив их побитовой операцией ИЛИ; например, re.I | re.M устанавливает флаги I и M.
Ниже приведена таблица доступных флагов, а затем — более подробное описание каждого из них.
Флаг | Значение |
|---|---|
Заставляет такие escape-последовательности, как | |
Заставляет | |
Выполняет сопоставление без учёта регистра. | |
Выполняет сопоставление с учётом локали. | |
Включает многострочное сопоставление, влияя на | |
Включает подробный режим для регулярных выражений, позволяющий оформлять их более аккуратно и понятно. |
- re.I
- re.IGNORECASE
-
Выполняет сопоставление без учёта регистра: классы символов и буквальные строки соответствуют буквам независимо от регистра. Например,
[A-Z]будет соответствовать и строчным буквам. Полное сопоставление в соответствии с Unicode также работает, если только флагASCIIне используется для отключения сопоставления не-ASCII-символов. Если шаблоны Unicode[a-z]или[A-Z]используются вместе с флагомIGNORECASE, они будут соответствовать 52 буквам ASCII и ещё четырём не-ASCII-буквам: «İ» (U+0130, заглавная латинская буква I с точкой сверху), «ı» (U+0131, строчная латинская буква без точки), «ſ» (U+017F, строчная латинская длинная s) и «K» (U+212A, знак кельвина).Spamбудет соответствовать'Spam','spam','spAM'или'ſpam'(последний вариант — только в режиме Unicode). При преобразовании регистра текущая локаль не учитывается; она будет учитываться, если также установить флагLOCALE.
- re.L
- re.LOCALE
-
Заставляет сопоставление с использованием
\w,\W,\b,\Bи без учёта регистра зависеть от текущей локали, а не от базы данных Unicode.Локали — это функция библиотеки C, предназначенная для создания программ, учитывающих различия между языками. Например, при обработке текста на французском языке в кодировке можно было бы использовать
\w+для поиска слов, однако\wв байтовых шаблонах соответствует только классу символов[A-Za-z]и не соответствует байтам, представляющимéилиç. Если система настроена правильно и выбрана французская локаль, некоторые функции C сообщат программе, что байт, соответствующийé, также следует считать буквой. Установка флагаLOCALEпри компиляции регулярного выражения заставит полученный скомпилированный объект использовать эти функции C для\w; это работает медленнее, но позволяет\w+соответствовать французским словам, как и ожидалось. В Python 3 использовать этот флаг не рекомендуется, поскольку механизм локалей очень ненадёжен: он поддерживает только одну «культуру» одновременно и работает только с 8-битными локалями. В Python 3 сопоставление Unicode уже включено по умолчанию для шаблонов Unicode (str) и умеет работать с различными локалями и языками.
- re.M
- re.MULTILINE
-
(Назначение
^и$ещё не объяснялось; они будут рассмотрены в разделе Дополнительные метасимволы.)Обычно
^соответствует только началу строки, а$— только концу строки и непосредственно перед символом новой строки (если он есть) в конце строки. Если задан этот флаг,^соответствует началу строки и началу каждой строки внутри неё, непосредственно после каждого символа новой строки. Аналогично, метасимвол$соответствует как концу строки, так и концу каждой строки (непосредственно перед каждым символом новой строки).
- re.S
- re.DOTALL
-
Заставляет специальный символ
'.'соответствовать любому символу, в том числе символу новой строки; без этого флага'.'соответствует любым символам, кроме символа новой строки.
- re.A
- re.ASCII
-
Заставляет
\w,\W,\b,\B,\sи\Sвыполнять сопоставление только по ASCII вместо полного сопоставления Unicode. Это имеет смысл только для шаблонов Unicode и игнорируется для байтовых шаблонов.
- re.X
- re.VERBOSE
-
Этот флаг позволяет записывать более читаемые регулярные выражения, предоставляя больше свободы в их оформлении. Если флаг задан, пробельные символы в строке регулярного выражения игнорируются, за исключением пробелов внутри класса символов или после неэкранированной обратной косой черты. Это позволяет нагляднее структурировать регулярное выражение и добавлять отступы. Флаг также позволяет добавлять в регулярное выражение комментарии, которые будут игнорироваться механизмом обработки; комментарии обозначаются символом
'#', который находится не внутри класса символов и которому не предшествует неэкранированная обратная косая черта.Например, вот регулярное выражение, в котором используется
re.VERBOSE; видите, насколько легче его читать?charref = re.compile(r""" &[#] # Start of a numeric entity reference ( 0[0-7]+ # Octal form | [0-9]+ # Decimal form | x[0-9a-fA-F]+ # Hexadecimal form ) ; # Trailing semicolon """, re.VERBOSE)Без подробного режима регулярное выражение выглядело бы так:
charref = re.compile("&#(0[0-7]+" "|[0-9]+" "|x[0-9a-fA-F]+);")В приведённом выше примере для разбиения регулярного выражения на более мелкие части использована автоматическая конкатенация строковых литералов в Python, но такой вариант всё равно сложнее для понимания, чем версия с использованием
re.VERBOSE.
Больше возможностей шаблонов
До сих пор мы рассмотрели лишь часть возможностей регулярных выражений. В этом разделе мы познакомимся с новыми метасимволами и узнаем, как использовать группы для извлечения частей совпавшего текста.
Дополнительные метасимволы
Есть метасимволы, которые мы ещё не рассматривали. Большинство из них будет рассмотрено в этом разделе.
Некоторые из оставшихся метасимволов, которые мы обсудим, — это утверждения нулевой ширины. Они не заставляют механизм продвигаться по строке: вместо этого они не поглощают символы и просто завершаются успешно или неуспешно. Например, \b — это утверждение о том, что текущая позиция находится на границе слова; позиция при этом совершенно не изменяется из-за \b. Это значит, что утверждения нулевой ширины никогда не следует повторять: если они совпали один раз в заданной позиции, очевидно, что они могут совпадать бесконечное число раз.
-
| -
Альтернация, или оператор «или». Если A и B — регулярные выражения,
A|Bбудет соответствовать любой строке, которая совпадает либо с A, либо с B. У|очень низкий приоритет, чтобы альтернация строк из нескольких символов работала ожидаемым образом.Crow|Servoбудет соответствовать либо'Crow', либо'Servo', но не'Cro', не'w'и не'S', а также'ervo'.Чтобы найти буквальный символ
'|', используйте\|или заключите его в символьный класс, как в[|]. -
^ -
Совпадает в начале строк. Если флаг
MULTILINEне установлен, совпадение будет только в начале строки. В режимеMULTILINEон также совпадает сразу после каждого символа новой строки в строке.Например, если нужно найти слово
Fromтолько в начале строки, следует использовать регулярное выражение^From.>>> print(re.search('^From', 'From Here to Eternity')) <re.Match object; span=(0, 4), match='From'> >>> print(re.search('^From', 'Reciting From Memory')) NoneЧтобы найти буквальный символ
'^', используйте\^. -
$ -
Совпадает в конце строки, который определяется как конец строки либо любая позиция, за которой следует символ новой строки.
>>> print(re.search('}$', '{block}')) <re.Match object; span=(6, 7), match='}'> >>> print(re.search('}$', '{block} ')) None >>> print(re.search('}$', '{block}\n')) <re.Match object; span=(6, 7), match='}'>Чтобы найти буквальный символ
'$', используйте\$или заключите его в символьный класс, как в[$]. -
\A -
Совпадает только в начале строки. Вне режима
MULTILINE\Aи^фактически означают одно и то же. В режимеMULTILINEони различаются:\Aпо-прежнему совпадает только в начале строки, а^может совпадать в любой позиции строки, следующей за символом новой строки. -
\Z -
Совпадает только в конце строки.
-
\b -
Граница слова. Это утверждение нулевой ширины, которое совпадает только в начале или конце слова. Слово определяется как последовательность буквенно-цифровых символов, поэтому конец слова обозначается пробельным или не буквенно-цифровым символом.
Следующий пример находит
class, только если это отдельное слово; он не совпадёт, если оно входит в состав другого слова.>>> p = re.compile(r'\bclass\b') >>> print(p.search('no class at all')) <re.Match object; span=(3, 8), match='class'> >>> print(p.search('the declassified algorithm')) None >>> print(p.search('one subclass is')) NoneПри использовании этой специальной последовательности следует помнить о двух нюансах. Во-первых, здесь возникает самое серьёзное столкновение между строковыми литералами Python и последовательностями регулярных выражений. В строковых литералах Python
\b— это символ возврата на одну позицию, ASCII-код 8. Если вы не используете необработанные строки, Python преобразует\bв символ возврата на одну позицию, и регулярное выражение не будет работать так, как ожидается. Следующий пример выглядит так же, как предыдущее регулярное выражение, но перед строкой регулярного выражения отсутствует'r'.>>> p = re.compile('\bclass\b') >>> print(p.search('no class at all')) None >>> print(p.search('\b' + 'class' + '\b')) <re.Match object; span=(0, 7), match='\x08class\x08'>Во-вторых, внутри символьного класса, где это утверждение не используется,
\bобозначает символ возврата на одну позицию для совместимости со строковыми литералами Python. -
\B -
Ещё одно утверждение нулевой ширины. Оно противоположно
\bи совпадает, только если текущая позиция не является границей слова.
Группировка
Часто необходимо получить больше сведений, чем просто информацию о том, совпало ли регулярное выражение. Регулярные выражения нередко используют для разбиения строк: регулярное выражение составляют из нескольких подгрупп, каждая из которых соответствует отдельному интересующему фрагменту. Например, строка заголовка RFC-822 делится на имя заголовка и значение, разделённые символом ':', вот так:
From: author@example.com User-Agent: Thunderbird 1.5.0.9 (X11/20061227) MIME-Version: 1.0 To: editor@example.com
Это можно сделать с помощью регулярного выражения, которое совпадает со всей строкой заголовка и содержит одну группу для имени заголовка и другую — для его значения.
Группы обозначаются метасимволами '(' и ')'. '(' и ')' имеют примерно тот же смысл, что и в математических выражениях: они объединяют заключённые в них выражения, а содержимое группы можно повторять с помощью квантификатора, например *, +, ? или {m,n}. Например, (ab)* будет соответствовать нулю или большему числу повторений ab.
>>> p = re.compile('(ab)*')
>>> print(p.match('ababababab').span())
(0, 10)
Группы, обозначенные с помощью '(' и ')', также сохраняют начальный и конечный индексы совпавшего текста. Их можно получить, передав аргумент методам group(), start(), end() и span(). Нумерация групп начинается с 0. Группа 0 есть всегда: это всё регулярное выражение, поэтому методы объекта совпадения используют группу 0 в качестве аргумента по умолчанию. Позже мы узнаем, как задавать группы, которые не сохраняют диапазон совпавшего текста.
>>> p = re.compile('(a)b')
>>> m = p.match('ab')
>>> m.group()
'ab'
>>> m.group(0)
'ab'
Подгруппы нумеруются слева направо, начиная с 1. Группы можно вкладывать друг в друга; чтобы определить номер группы, просто считайте открывающие скобки слева направо.
>>> p = re.compile('(a(b)c)d')
>>> m = p.match('abcd')
>>> m.group(0)
'abcd'
>>> m.group(1)
'abc'
>>> m.group(2)
'b'
Методу group() можно передать сразу несколько номеров групп. В этом случае он вернёт кортеж со значениями соответствующих групп.
>>> m.group(2,1,2)
('b', 'abc', 'b')
Метод groups() возвращает кортеж со строками всех подгрупп — от 1 до последней.
>>> m.groups()
('abc', 'b')
Обратные ссылки в шаблоне позволяют указать, что содержимое ранее найденной группы должно также присутствовать в текущей позиции строки. Например, \1 сработает, если в текущей позиции обнаружится содержимое группы 1, и не сработает в противном случае. Помните, что строковые литералы Python также используют обратную косую черту с последующими цифрами для включения в строку произвольных символов. Поэтому при использовании обратных ссылок в регулярном выражении обязательно используйте необработанную строку.
Например, следующее регулярное выражение находит повторяющиеся слова в строке.
>>> p = re.compile(r'\b(\w+)\s+\1\b')
>>> p.search('Paris in the the spring').group()
'the the'
Такие обратные ссылки нечасто полезны для простого поиска по строке — существует мало текстовых форматов, в которых данные повторяются таким образом, — однако вы скоро узнаете, что они очень полезны при замене строк.
Незахватывающие и именованные группы
Сложные регулярные выражения могут содержать много групп, как для захвата интересующих подстрок, так и для группировки и структурирования самого выражения. В сложных регулярных выражениях бывает трудно следить за номерами групп. Эту проблему помогают решить две возможности. Обе используют общий синтаксис расширений регулярных выражений, поэтому сначала рассмотрим его.
Perl 5 известен своими мощными дополнениями к стандартным регулярным выражениям. Для реализации этих новых возможностей разработчики Perl не могли выбрать новые одноклавишные метасимволы или новые специальные последовательности, начинающиеся с \, не сделав регулярные выражения Perl непонятно отличающимися от стандартных. Например, если бы они выбрали & в качестве нового метасимвола, старые выражения считали бы & обычным символом и не экранировали бы его с помощью \& или [&].
Разработчики Perl решили использовать (?...) в качестве синтаксиса расширений. ? сразу после скобки приводило к синтаксической ошибке, поскольку ? было бы нечего повторять, поэтому проблем с совместимостью не возникло. Символы сразу после ? указывают, какое расширение используется. Так, (?=foo) — это одно расширение (утверждение положительного просмотра вперёд), а (?:foo) — другое (незахватывающая группа, содержащая подвыражение foo).
Python поддерживает несколько расширений Perl и добавляет собственный синтаксис расширений к синтаксису расширений Perl. Если первым символом после вопросительного знака является P, значит, это расширение, специфичное для Python.
Теперь, когда мы рассмотрели общий синтаксис расширений, вернёмся к возможностям, упрощающим работу с группами в сложных регулярных выражениях.
Иногда группу нужно использовать для обозначения части регулярного выражения, но её содержимое не требуется извлекать. Это можно явно указать с помощью незахватывающей группы: (?:...), где вместо ... можно подставить любое другое регулярное выражение.
>>> m = re.match("([abc])+", "abc")
>>> m.groups()
('c',)
>>> m = re.match("(?:[abc])+", "abc")
>>> m.groups()
()
За исключением того, что содержимое совпавшей группы нельзя извлечь, незахватывающая группа ведёт себя точно так же, как захватывающая: внутри неё можно разместить что угодно, повторить её с помощью метасимвола повторения, например *, и вложить её в другие группы (захватывающие или незахватывающие). (?:...) особенно полезны при изменении существующего шаблона, поскольку позволяют добавлять новые группы, не меняя нумерацию остальных групп. Следует отметить, что при поиске захватывающие и незахватывающие группы работают одинаково быстро: ни одна из форм не быстрее другой.
Более существенная возможность — именованные группы: на группы можно ссылаться по имени, а не по номеру.
Синтаксис именованной группы — одно из расширений, специфичных для Python: (?P<name>...). Очевидно, name — это имя группы. Именованные группы ведут себя точно так же, как захватывающие, и дополнительно связывают группу с именем. Методы объекта совпадения, работающие с захватывающими группами, принимают как целые числа, обозначающие номер группы, так и строки с именем нужной группы. Именованным группам по-прежнему присваиваются номера, поэтому сведения о группе можно получить двумя способами:
>>> p = re.compile(r'(?P<word>\b\w+\b)')
>>> m = p.search( '(((( Lots of punctuation )))' )
>>> m.group('word')
'Lots'
>>> m.group(1)
'Lots'
Кроме того, именованные группы можно получить в виде словаря с помощью groupdict():
>>> m = re.match(r'(?P<first>\w+) (?P<last>\w+)', 'Jane Doe')
>>> m.groupdict()
{'first': 'Jane', 'last': 'Doe'}
Именованные группы удобны тем, что позволяют использовать легко запоминающиеся имена вместо номеров. Вот пример регулярного выражения из модуля imaplib:
InternalDate = re.compile(r'INTERNALDATE "'
r'(?P<day>[ 123][0-9])-(?P<mon>[A-Z][a-z][a-z])-'
r'(?P<year>[0-9][0-9][0-9][0-9])'
r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
r'"')
Очевидно, гораздо проще получить m.group('zonem'), чем запоминать, что нужно извлечь группу 9.
Синтаксис обратных ссылок в выражении, например (...)\1, указывает номер группы. Естественно, существует вариант, использующий имя группы вместо номера. Это ещё одно расширение Python: (?P=name) означает, что содержимое группы с именем name должно снова совпасть в текущей позиции. Регулярное выражение для поиска повторяющихся слов, \b(\w+)\s+\1\b, можно также записать как \b(?P<word>\w+)\s+(?P=word)\b:
>>> p = re.compile(r'\b(?P<word>\w+)\s+(?P=word)\b')
>>> p.search('Paris in the the spring').group()
'the the'
Утверждения просмотра вперёд
Ещё одно утверждение нулевой ширины — утверждение просмотра вперёд. Утверждения просмотра вперёд бывают положительными и отрицательными и выглядят так:
-
(?=...) -
Утверждение положительного просмотра вперёд. Оно успешно, если содержащееся в нём регулярное выражение, обозначенное здесь как
..., успешно совпадает в текущей позиции, и неуспешно в противном случае. Однако после проверки содержащегося выражения механизм сопоставления не продвигается по строке: остальная часть шаблона проверяется именно с той позиции, где началось утверждение. -
(?!...) -
Утверждение отрицательного просмотра вперёд. Оно противоположно положительному утверждению: оно успешно, если содержащееся выражение не совпадает в текущей позиции строки.
Чтобы лучше понять, как это работает, рассмотрим случай, в котором полезен просмотр вперёд. Возьмём простой шаблон для поиска имени файла и его разделения на имя и расширение, разделённые символом .. Например, в news.rc news — это имя файла, а rc — его расширение.
Шаблон для поиска такого имени довольно прост:
.*[.].*$
Обратите внимание, что . нужно обрабатывать особым образом, поскольку это метасимвол. Поэтому его помещают в символьный класс, чтобы он соответствовал только этому конкретному символу. Также обратите внимание на завершающий $: он добавлен, чтобы гарантировать, что всё оставшееся содержимое строки войдёт в расширение. Это регулярное выражение совпадает с foo.bar, autoexec.bat, sendmail.cf и printers.conf.
Теперь немного усложним задачу: что, если нужно находить имена файлов, расширение которых не равно bat? Вот несколько неверных попыток:
.*[.][^b].*$
В первой попытке выше исключить bat пытаются, указывая, что первый символ расширения не должен быть b. Это неверно, поскольку шаблон также не совпадёт с foo.bar.
.*[.]([^b]..|.[^a].|..[^t])$
Выражение становится ещё сложнее, когда вы пытаетесь исправить первое решение, требуя совпадения одного из следующих случаев: первый символ расширения не равен b; второй символ не равен a; или третий символ не равен t. Это позволяет находить foo.bar и исключает autoexec.bat, но требует расширения из трёх букв и не подходит для имени файла с двухбуквенным расширением, например sendmail.cf. Попробуем ещё усложнить шаблон и исправить его.
.*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$
В третьей попытке второй и третий символы сделаны необязательными, чтобы находить расширения короче трёх символов, например sendmail.cf.
Шаблон становится очень сложным, поэтому его трудно читать и понимать. Хуже того, если задача изменится и потребуется исключить расширения bat и exe, шаблон станет ещё сложнее и запутаннее.
От всей этой путаницы избавляет отрицательный просмотр вперёд:
.*[.](?!bat$)[^.]*$
Отрицательный просмотр вперёд означает: если выражение bat не совпадает в этой позиции, проверяем остальную часть шаблона; если bat$ совпадает, всё выражение не совпадёт. Завершающий $ необходим, чтобы разрешить такие случаи, как sample.batch, где расширение начинается с bat. [^.]* обеспечивает корректную работу шаблона, если в имени файла несколько точек.
Теперь легко исключить ещё одно расширение: просто добавьте его как альтернативу внутри утверждения. Следующий шаблон исключает имена файлов, оканчивающиеся на bat или exe:
.*[.](?!bat$|exe$)[^.]*$
Изменение строк
До этого момента мы выполняли поиск только по неизменяемой строке. Регулярные выражения также часто используют для изменения строк разными способами с помощью следующих методов шаблонов:
Метод/атрибут | Назначение |
|---|---|
| Разбивает строку на список частей в местах совпадения с регулярным выражением |
| Находит все подстроки, совпадающие с регулярным выражением, и заменяет их другой строкой |
| Выполняет то же, что и |
Разбиение строк
Метод split() шаблона разбивает строку в местах совпадения с регулярным выражением и возвращает список частей. Он похож на метод split() строк, но позволяет гораздо гибче задавать разделители: строковый метод split() поддерживает разделение только по пробельным символам или фиксированной строке. Как и следовало ожидать, существует также функция уровня модуля re.split().
- .split(string[, maxsplit=0])
-
Разбивает string по совпадениям с регулярным выражением. Если в регулярном выражении используются захватывающие скобки, их содержимое также будет возвращено как часть результирующего списка. Если maxsplit не равен нулю, выполняется не более maxsplit разбиений.
Количество разбиений можно ограничить, передав значение maxsplit. Если maxsplit не равен нулю, будет выполнено не более maxsplit разбиений, а остаток строки станет последним элементом списка. В следующем примере разделителем служит любая последовательность не буквенно-цифровых символов.
>>> p = re.compile(r'\W+')
>>> p.split('This is a test, short and sweet, of split().')
['This', 'is', 'a', 'test', 'short', 'and', 'sweet', 'of', 'split', '']
>>> p.split('This is a test, short and sweet, of split().', 3)
['This', 'is', 'a', 'test, short and sweet, of split().']
Иногда важно знать не только текст между разделителями, но и то, каким был сам разделитель. Если в регулярном выражении используются захватывающие скобки, их значения также возвращаются как часть списка. Сравните следующие вызовы:
>>> p = re.compile(r'\W+')
>>> p2 = re.compile(r'(\W+)')
>>> p.split('This... is a test.')
['This', 'is', 'a', 'test', '']
>>> p2.split('This... is a test.')
['This', '... ', 'is', ' ', 'a', ' ', 'test', '.', '']
Функция уровня модуля re.split() принимает используемое регулярное выражение в качестве первого аргумента, но в остальном работает так же.
>>> re.split(r'[\W]+', 'Words, words, words.') ['Words', 'words', 'words', ''] >>> re.split(r'([\W]+)', 'Words, words, words.') ['Words', ', ', 'words', ', ', 'words', '.', ''] >>> re.split(r'[\W]+', 'Words, words, words.', 1) ['Words', 'words, words.']
Поиск и замена
Ещё одна распространённая задача — найти все совпадения с шаблоном и заменить их другой строкой. Метод sub() принимает значение для замены — строку или функцию — и обрабатываемую строку.
- .sub(replacement, string[, count=0])
-
Возвращает строку, полученную заменой всех левых неперекрывающихся совпадений регулярного выражения в string на replacement. Если шаблон не найден, string возвращается без изменений.
Необязательный аргумент count задаёт максимальное количество заменяемых совпадений с шаблоном; count должен быть неотрицательным целым числом. Значение по умолчанию 0 означает, что заменяются все совпадения.
Вот простой пример использования метода sub(). Он заменяет названия цветов словом colour:
>>> p = re.compile('(blue|white|red)')
>>> p.sub('colour', 'blue socks and red shoes')
'colour socks and colour shoes'
>>> p.sub('colour', 'blue socks and red shoes', count=1)
'colour socks and red shoes'
Метод subn() выполняет ту же задачу, но возвращает 2-элементный кортеж с новой строкой и количеством выполненных замен:
>>> p = re.compile('(blue|white|red)')
>>> p.subn('colour', 'blue socks and red shoes')
('colour socks and colour shoes', 2)
>>> p.subn('colour', 'no colours at all')
('no colours at all', 0)
Пустые совпадения заменяются только в том случае, если они не соседствуют с предыдущим пустым совпадением.
>>> p = re.compile('x*')
>>> p.sub('-', 'abxd')
'-a-b--d-'
Если replacement — строка, все экранированные последовательности с обратной косой чертой в ней обрабатываются. То есть \n преобразуется в символ новой строки, \r — в возврат каретки и так далее. Неизвестные последовательности, например \&, остаются без изменений. Обратные ссылки, такие как \6, заменяются подстрокой, совпавшей с соответствующей группой регулярного выражения. Это позволяет включать части исходного текста в результирующую строку замены.
В этом примере находится слово section, за которым следует строка в {, }, и заменяет section на subsection:
>>> p = re.compile('section{ ( [^}]* ) }', re.VERBOSE)
>>> p.sub(r'subsection{\1}','section{First} section{second}')
'subsection{First} subsection{second}'
Также существует синтаксис для обращения к именованным группам, заданным с помощью синтаксиса (?P<name>...). \g<name> подставляет подстроку, совпавшую с группой с именем name, а \g<number> использует соответствующий номер группы. Поэтому \g<2> эквивалентно \2, но не вызывает неоднозначности в строке замены, например \g<2>0. (\20 интерпретировалось бы как ссылка на группу 20, а не на группу 2 с последующим буквальным символом '0'.) Следующие варианты замены эквивалентны, но используют все три формы строки замены.
>>> p = re.compile('section{ (?P<name> [^}]* ) }', re.VERBOSE)
>>> p.sub(r'subsection{\1}','section{First}')
'subsection{First}'
>>> p.sub(r'subsection{\g<1>}','section{First}')
'subsection{First}'
>>> p.sub(r'subsection{\g<name>}','section{First}')
'subsection{First}'
replacement также может быть функцией, что даёт ещё больше контроля. Если replacement — функция, она вызывается для каждого неперекрывающегося совпадения с pattern. При каждом вызове ей передаётся аргумент объект совпадения; функция может использовать эти сведения, вычислить нужную строку замены и вернуть её.
В следующем примере функция замены преобразует десятичные числа в шестнадцатеричные:
>>> def hexrepl(match): ... "Return the hex string for a decimal number" ... value = int(match.group()) ... return hex(value) ... >>> p = re.compile(r'\d+') >>> p.sub(hexrepl, 'Call 65490 for printing, 49152 for user code.') 'Call 0xffd2 for printing, 0xc000 for user code.'
При использовании функции уровня модуля re.sub() шаблон передаётся первым аргументом. Шаблон можно передать как объект или строку. Если нужно задать флаги регулярного выражения, необходимо либо передать объект шаблона в качестве первого параметра, либо использовать встроенные модификаторы в строке шаблона. Например, sub("(?i)b+", "x", "bbbb BBBB") возвращает 'x x'.
Распространённые проблемы
Регулярные выражения — мощный инструмент для некоторых приложений, однако их поведение не всегда интуитивно понятно, и порой они работают не так, как вы ожидаете. В этом разделе мы укажем на некоторые наиболее распространённые подводные камни.
Используйте методы строк
Иногда использовать модуль re — ошибка. Если вы ищете фиксированную строку или один класс символов и не используете возможности re, например флаг IGNORECASE, то полная мощь регулярных выражений может быть вам не нужна. У строк есть несколько методов для выполнения операций с фиксированными строками, и обычно они работают намного быстрее: их реализация представляет собой небольшой цикл на C, оптимизированный для этой задачи, а не крупный универсальный движок регулярных выражений.
Например, может потребоваться заменить одну фиксированную строку другой; допустим, вы хотите заменить word на deed. Для этого, кажется, подойдёт функция re.sub(), но рассмотрите метод replace(). Обратите внимание, что replace() также заменит word внутри слов, превратив swordfish в sdeedfish, но наивное регулярное выражение word поступило бы так же. (Чтобы не выполнять замену внутри слов, шаблон должен был бы выглядеть так: \bword\b, то есть требовать наличия границы слова с обеих сторон от word. Это уже выходит за пределы возможностей replace().)
Ещё одна распространённая задача — удалить из строки все вхождения одного символа или заменить его другим одиночным символом. Можно сделать это, например, с помощью re.sub('\n', ' ', S), но метод translate() умеет выполнять обе задачи и будет работать быстрее любой операции с регулярными выражениями.
Вкратце: прежде чем обращаться к модулю re, подумайте, нельзя ли решить вашу задачу с помощью более быстрого и простого строкового метода.
match() и search()
Функция match() проверяет, совпадает ли регулярное выражение с началом строки, тогда как search() просматривает строку в поисках совпадения. Важно помнить об этом различии. Помните: match() сообщает об успешном совпадении, только если оно начинается с позиции 0; если совпадение начинается не с нуля, match() не сообщит о нём.
>>> print(re.match('super', 'superstition').span())
(0, 5)
>>> print(re.match('super', 'insuperable'))
None
С другой стороны, search() просматривает строку и сообщает о первом найденном совпадении.
>>> print(re.search('super', 'superstition').span())
(0, 5)
>>> print(re.search('super', 'insuperable').span())
(2, 7)
Иногда возникает соблазн продолжать использовать re.match(), просто добавив .* в начало регулярного выражения. Не поддавайтесь ему и вместо этого используйте re.search(). Компилятор регулярных выражений анализирует выражения, чтобы ускорить поиск совпадений. Например, анализ позволяет определить, каким должен быть первый символ совпадения: так, шаблон, начинающийся с Crow, должен совпадать со строкой, начинающейся с 'C'. Благодаря этому движок может быстро просматривать строку в поисках начального символа и проверять полное совпадение, только если найден 'C'.
Добавление .* отключает эту оптимизацию: движку приходится просматривать строку до конца, а затем возвращаться назад, чтобы найти совпадение с остальной частью регулярного выражения. Вместо этого используйте re.search().
Жадные и нежадные квантификаторы
При повторении регулярного выражения, например в a*, оно стремится захватить как можно больше текста. Это часто создаёт проблемы, когда нужно сопоставить пару сбалансированных разделителей, например угловые скобки вокруг HTML-тега. Наивный шаблон для поиска одного HTML-тега не работает из-за жадного поведения .*.
>>> s = '<html><head><title>Title</title>'
>>> len(s)
32
>>> print(re.match('<.*>', s).span())
(0, 32)
>>> print(re.match('<.*>', s).group())
<html><head><title>Title</title>
Регулярное выражение находит '<' в '<html>', а .* захватывает остаток строки. Однако в регулярном выражении остаётся ещё один элемент, а > не может совпасть в конце строки, поэтому движку регулярных выражений приходится возвращаться назад по одному символу за раз, пока он не найдёт совпадение для >. В результате совпадение охватывает текст от '<' в '<html>' до '>' в '</title>', а это не то, что вам нужно.
В этом случае решение — использовать нежадные квантификаторы *?, +?, ?? или {m,n}?, которые совпадают с как можно меньшим количеством текста. В приведённом выше примере '>' проверяется сразу после первого совпадения с '<'; если совпадение не найдено, движок продвигается на один символ и повторяет проверку '>' на каждом шаге. Это даёт именно нужный результат:
>>> print(re.match('<.*?>', s).group())
<html>
(Обратите внимание: разбирать HTML или XML с помощью регулярных выражений — мучительно. Простые шаблоны на скорую руку справятся с распространёнными случаями, но в HTML и XML есть особые случаи, которые нарушают работу очевидного регулярного выражения. К тому времени, когда вы напишете регулярное выражение, обрабатывающее все возможные случаи, шаблон станет очень сложным. Для таких задач используйте модуль разбора HTML или XML.)
Использование re.VERBOSE
Вы, вероятно, уже заметили, что регулярные выражения записываются очень компактно, но их не так-то легко читать. Регулярные выражения средней сложности могут превращаться в длинные последовательности обратных косых черт, скобок и метасимволов, из-за чего их трудно читать и понимать.
Для таких выражений может быть полезно при компиляции указать флаг re.VERBOSE, позволяющий форматировать регулярное выражение более наглядно.
Флаг re.VERBOSE действует несколькими способами. Пробелы в регулярном выражении, которые не находятся внутри класса символов, игнорируются. Это означает, что выражение вроде dog | cat эквивалентно менее читаемому dog|cat, но [a b] по-прежнему будет совпадать с символами 'a', 'b' или пробелом. Кроме того, в регулярное выражение можно включать комментарии: они продолжаются от символа # до следующего перевода строки. Если использовать строки в тройных кавычках, регулярные выражения можно форматировать более аккуратно:
pat = re.compile(r"""
\s* # Skip leading whitespace
(?P<header>[^:]+) # Header name
\s* : # Whitespace, and a colon
(?P<value>.*?) # The header's value -- *? used to
# lose the following trailing whitespace
\s*$ # Trailing whitespace to end-of-line
""", re.VERBOSE)
Это гораздо удобнее для чтения, чем:
pat = re.compile(r"\s*(?P<header>[^:]+)\s*:(?P<value>.*?)\s*$")
Обратная связь
Регулярные выражения — сложная тема. Помог ли этот документ разобраться в них? Были ли в нём неясные места или проблемы, с которыми вы столкнулись, но которые здесь не рассмотрены? Если да, отправьте автору предложения по улучшению.
Самая полная книга о регулярных выражениях — почти наверняка «Mastering Regular Expressions» Джеффри Фридла, выпущенная издательством O’Reilly. К сожалению, в ней рассматриваются исключительно разновидности регулярных выражений Perl и Java, и совсем нет материалов по Python, поэтому она не подойдёт в качестве справочника по программированию на Python. (В первом издании рассматривался ныне удалённый модуль Python regex, но вряд ли он вам сильно поможет.) Попробуйте взять книгу в библиотеке.
© 2001–2025 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.13/howto/regex.html