Документация Python неофициальный перевод
Содержание страницы

Регулярные выражения: руководствоRegular Expression HOWTO

Автор:A.M. Kuchling <amk@amk.ca>

Аннотация

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

ВведениеIntroduction

Регулярные выражения (называемые RE, regex или regex-шаблоны) – это по сути крошечный, узкоспециализированный язык программирования, встроенный в Python и доступный через модуль re. С помощью этого маленького языка задаются правила для множества возможных строк, которые требуется сопоставить; это множество может содержать английские предложения, адреса электронной почты, команды TeX или что угодно. Затем можно задавать вопросы: «Соответствует ли эта строка шаблону?», или «Есть ли совпадение с шаблоном где-нибудь в этой строке?». Также можно использовать RE для изменения строки или её разделения различными способами.

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

Язык регулярных выражений относительно мал и ограничен, поэтому не все задачи обработки строк можно решить с помощью регулярных выражений. Существуют также задачи, которые можно выполнить с помощью регулярных выражений, но выражения оказываются очень сложными. В таких случаях, возможно, лучше написать код на Python для обработки; хотя код на Python будет медленнее, чем сложное регулярное выражение, он, вероятно, будет более понятным.

Простые шаблоныSimple Patterns

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

Подробное объяснение теоретических основ регулярных выражений (детерминированные и недетерминированные конечные автоматы) можно найти почти в любом учебнике по написанию компиляторов.

Сопоставление символовMatching Characters

Большинство букв и символов просто совпадают сами с собой. Например, регулярное выражение test точно совпадёт со строкой test. (Можно включить режим без учёта регистра, который позволит этому RE совпадать с Test или TEST ; подробнее об этом позже.)

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

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

. ^ $ * + ? { } [ ] \ | ( )

Первые метасимволы, которые мы рассмотрим, – это [ и ]. Они используются для указания класса символов – набора символов, которые требуется сопоставить. Символы можно перечислить по отдельности, или же диапазон символов можно указать, задав два символа и разделив их '-'. Например, [abc] совпадёт с любым из символов a, b или c; это то же самое, что [a-c], где используется диапазон для задания того же набора символов. Если требуется сопоставить только строчные буквы, RE будет [a-z].

Внутри классов метасимволы не активны. Например, [akm$] совпадёт с любым из символов 'a', 'k', 'm' или '$'; обычно '$' является метасимволом, но внутри класса символов он теряет свою особую природу.

Вы можете сопоставить символы, не перечисленные в классе, дополнив набор. Это обозначается включением '^' в качестве первого символа класса; '^' вне символьного класса просто соответствует '^' символу. Например, [^5] будет соответствовать любому символу, кроме '5'.

Пожалуй, самый важный метасимвол – обратная косая черта, \. Как и в строковых литералах Python, за обратной косой чертой могут следовать различные символы для обозначения специальных последовательностей. Она также используется для экранирования всех метасимволов, чтобы их можно было сопоставлять в шаблонах; например, если нужно сопоставить [ или \, можно поставить перед ними обратную косую черту, чтобы убрать их специальное значение: \[ или \\.

Некоторые специальные последовательности, начинающиеся с '\', представляют предопределённые наборы символов, которые часто бывают полезны: например, набор цифр, набор букв или набор всего, что не является пробельным символом.

Рассмотрим пример: \w совпадает с любым буквенно-цифровым символом. Если шаблон регулярного выражения задан в байтах, это эквивалентно классу [a-zA-Z0-9_]. Если шаблон – строка, \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), в котором он будет совпадать даже с символом новой строки. '.' часто используется, когда требуется сопоставить «любой символ».

Повторяющиеся элементыRepeating Things

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

Первый метасимвол для повторения, который мы рассмотрим, – это *. * не соответствует литеральному символу *; вместо этого он указывает, что предыдущий символ может быть сопоставлен ноль или более раз, а не ровно один раз.

Например, ca*t совпадёт с ct (0 символов a), cat (1 a), caaat (3 a символа) и так далее. Движок RE имеет различные внутренние ограничения, связанные с размером типа int в языке C, которые не позволяют ему совпадать с более чем 2 миллиардами символов a; шаблоны обычно не пишутся для сопоставления с таким объёмом данных.

Повторы, такие как *, являются жадными; при повторении RE движок сопоставления будет пытаться повторить его максимально возможное количество раз. Если последующие части шаблона не совпадают, движок сопоставления отступает назад и пробует снова с меньшим числом повторов.

Пошаговый пример прояснит это. Рассмотрим выражение a[bcd]*b. Оно совпадает с буквой 'a', нулём или более букв из класса [bcd] и, наконец, заканчивается 'b'. Представим сопоставление этого RE со строкой abcbd.

Шаг Совпадение Пояснение
1 a a в RE совпадает.
2 abcbd Движок сопоставляет [bcd]*, заходя так далеко, как может, то есть до конца строки.
3 Неудача Движок пытается сопоставить b, но текущая позиция находится в конце строки, поэтому это не удаётся.
4 abcb Отступить, чтобы [bcd]* совпадал на один символ меньше.
5 Неудача Попробовать b снова, но текущая позиция находится на последнем символе, которым является 'd'.
6 abc Снова отступить, чтобы [bcd]* совпадал только с bc.
6 abcb Попробуйте b снова. На этот раз символ в текущей позиции – 'b', поэтому он совпадает.

Достигнут конец РВ, и оно совпало с abcb. Это показывает, как движок сопоставления сначала проходит максимально далеко, и если совпадение не найдено, он постепенно отступает и снова и снова пробует остаток РВ. Он будет отступать, пока не попробует ноль совпадений для [bcd]*, и если это в итоге не удаётся, движок делает вывод, что строка вообще не соответствует РВ.

Ещё один повторяющийся метасимвол – +, который совпадает один или более раз. Обратите внимание на разницу между * и +; * совпадает ноль или более раз, поэтому повторяемый элемент может вообще отсутствовать, в то время как + требует как минимум одного вхождения. Для аналогичного примера, 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 приводит к верхней границе бесконечности – на самом деле верхняя граница – это упомянутый ранее предел в 2 миллиарда, но её можно считать бесконечностью.

Можно заметить, что три остальных квантификатора можно выразить через эту нотацию. {0,} – то же, что *, {1,} эквивалентен +, а {0,1} – то же, что ?. Лучше использовать *, + или ?, когда это возможно, просто потому что они короче и легче читаются.

Использование регулярных выраженийUsing Regular Expressions

Теперь, когда мы рассмотрели несколько простых регулярных выражений, как же их использовать в Python? Модуль re предоставляет интерфейс к движку регулярных выражений, позволяя компилировать РВ в объекты и затем выполнять с ними сопоставление.

Компиляция регулярных выраженийCompiling Regular Expressions

Регулярные выражения компилируются в объекты шаблонов, которые имеют методы для различных операций, таких как поиск совпадений с шаблоном или выполнение подстановок в строке.

>>> import re
>>> p = re.compile('ab*')
>>> p  
<_sre.SRE_Pattern object at 0x...>

re.compile() также принимает необязательный аргумент flags, используемый для включения различных специальных возможностей и синтаксических вариаций. Мы рассмотрим доступные настройки позже, а пока достаточно одного примера:

>>> p = re.compile('ab*', re.IGNORECASE)

РВ передаётся в re.compile() в виде строки. РВ обрабатываются как строки, потому что регулярные выражения не являются частью основного языка Python, и для их записи не было создано специального синтаксиса. (Есть приложения, которым РВ вообще не нужны, так что нет необходимости раздувать спецификацию языка, включая их.) Вместо этого модуль re – это просто модуль-расширение на C, входящий в состав Python, как и модули socket или zlib.

Размещение RE в строках сохраняет язык Python более простым, но имеет один недостаток, который является темой следующего раздела.

Проблема обратной косой чертыThe Backslash Plague

Как было сказано ранее, регулярные выражения используют символ обратной косой черты ('\') для обозначения специальных форм или для использования специальных символов без их специального значения. Это конфликтует с использованием того же символа в строковых литералах Python для той же цели.

Допустим, нужно написать РВ, которое совпадает со строкой \section, которая может встречаться в файле LaTeX. Чтобы понять, что писать в коде программы, начните с нужной строки для сопоставления. Затем нужно экранировать все обратные слэши и другие метасимволы, предваряя их обратным слэшем, в результате получится строка \\section. Результирующая строка, которую нужно передать в re.compile(), должна быть \\section. Однако, чтобы выразить это в виде строкового литерала Python, оба обратных слэша необходимо экранировать снова.

Символы Этап
\section Строка для совпадения
\\section Экранированный обратный слэш для re.compile()
"\\\\section" Экранированные обратные слеши для строкового литерала

Короче говоря, для сопоставления с литералом обратной косой черты нужно записать '\\\\' в качестве строки RE, потому что регулярное выражение должно быть \\, а каждый обратный слеш должен быть представлен как \\ внутри обычного строкового литерала Python. В RE, где обратные слеши встречаются многократно, это приводит к множеству повторных слешей и делает итоговые строки трудными для понимания.

Решение – использовать в Python сырые строки для регулярных выражений; обратные слэши не обрабатываются особым образом в строковом литерале с префиксом 'r', поэтому r"\n" – это двухсимвольная строка, содержащая '\' и 'n', в то время как "\n" – односимвольная строка, содержащая символ новой строки. Регулярные выражения часто записываются в коде Python с использованием этой нотации сырых строк.

Обычная строка Сырая строка
"ab*" r"ab*"
"\\\\section" r"\\section"
"\\w+\\s+\\1" r"\w+\s+\1"

Выполнение поиска совпаденийPerforming Matches

Когда у вас есть объект, представляющий скомпилированное регулярное выражение, что с ним делать? Объекты-шаблоны имеют несколько методов и атрибутов. Здесь будут рассмотрены только самые важные; обратитесь к документации re за полным списком.

Метод/Атрибут Назначение
match() Определяет, совпадает ли RE в начале строки.
search() Сканирует строку в поисках любого места, где данное регулярное выражение совпадает.
findall() Находит все подстроки, совпадающие с регулярным выражением, и возвращает их в виде списка.
finditer() Find all substrings where the RE matches, and returns them as an iterator.

match() и search() возвращают None, если совпадение не найдено. Если они успешны, возвращается экземпляр объекта совпадения, содержащий информацию о совпадении: где оно начинается и заканчивается, подстроку, с которой совпало, и другое.

Вы можете узнать об этом, интерактивно экспериментируя с модулем re. Если у вас есть tkinter, возможно, вы также захотите посмотреть на Tools/demo/redemo.py – демонстрационную программу, входящую в состав дистрибутива Python. Она позволяет вводить РВ и строки и отображает, совпадает РВ или нет. redemo.py может быть весьма полезна при отладке сложного РВ. Phil Schwartz's Kodos – также интерактивный инструмент для разработки и тестирования шаблонов РВ.

В этом HOWTO используется стандартный интерпретатор Python для примеров. Сначала запустите интерпретатор Python, импортируйте модуль re и скомпилируйте РВ:

>>> import re
>>> p = re.compile('[a-z]+')
>>> p  
<_sre.SRE_Pattern object at 0x...>

Теперь можно попробовать сопоставить различные строки с РВ [a-z]+. Пустая строка вообще не должна совпадать, так как + означает «одно или более повторений». match() в этом случае должна вернуть None, что приведёт к тому, что интерпретатор не выведет ничего. Можно явно напечатать результат match(), чтобы это стало очевидно.

>>> p.match("")
>>> print(p.match(""))
None

Теперь попробуем на строке, которая должна совпасть, например tempo. В этом случае match() вернёт объект совпадения, поэтому следует сохранить результат в переменной для дальнейшего использования.

>>> m = p.match('tempo')
>>> m  
<_sre.SRE_Match object at 0x...>

Теперь можно запросить у объекта match информацию о соответствующей строке. Объекты match также имеют несколько методов и атрибутов; наиболее важные из них:

Метод/Атрибут Назначение
group() Возвращает строку, совпавшую с РВ
start() Возвращает начальную позицию совпадения
end() Возвращает конечную позицию совпадения
span() Возвращает кортеж с (начало, конец) позициями совпадения

Применение этих методов быстро прояснит их смысл:

>>> 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)  
<_sre.SRE_Match object at 0x...>
>>> 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('\d+')
>>> p.findall('12 drummers drumming, 11 pipers piping, 10 lords a-leaping')
['12', '11', '10']

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)

Функции уровня модуляModule-Level Functions

You don’t have to create a pattern object and call its methods; the re module also provides top-level functions called match(), search(), findall(), sub(), and so forth. These functions take the same arguments as the corresponding pattern method with the RE string added as the first argument, and still return either None or a match object instance.

>>> print(re.match(r'From\s+', 'Fromage amk'))
None
>>> re.match(r'From\s+', 'From amk Thu May 14 19:12:10 1998')  
<_sre.SRE_Match object at 0x...>

Под капотом эти функции просто создают объект шаблона и вызывают на нём соответствующий метод. Они также сохраняют скомпилированный объект в кеш, так что последующие вызовы с тем же РВ не будут каждый раз разбирать шаблон заново.

Следует ли использовать функции уровня модуля или получать шаблон и вызывать его методы самостоятельно? При доступе к регулярному выражению внутри цикла предварительная компиляция позволит сэкономить несколько вызовов функций. Вне циклов, благодаря внутреннему кешу, разница невелика.

Флаги компиляцииCompilation Flags

Флаги компиляции позволяют изменять некоторые аспекты работы регулярных выражений. Флаги доступны в модуле re под двумя именами: длинным, например IGNORECASE, и коротким, однобуквенным, например I. (Если вы знакомы с модификаторами шаблонов в Perl, то однобуквенные формы используют те же буквы; например, короткая форма re.VERBOSE – это re.X.) Несколько флагов можно задать с помощью побитового ИЛИ: например, re.I | re.M устанавливает оба флага I и M.

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

Флаг Значение
ASCII, A Заставляет несколько управляющих последовательностей, таких как \\w, \\b, \\s и \\d, сопоставляться только с ASCII-символами, обладающими соответствующим свойством.
DOTALL, S Заставляет . соответствовать любому символу, включая символы новой строки
IGNORECASE, I Выполняет регистронезависимые сопоставления
LOCALE, L Выполняет сопоставление с учётом локали
MULTILINE, M Многострочное сопоставление, затрагивающее ^ и $
VERBOSE, X (от ‘extended’) Включает подробные (verbose) регулярные выражения, которые можно организовать более чисто и понятно.
I
IGNORECASE

Выполняет сопоставление без учёта регистра; классы символов и строковые литералы будут сопоставляться с буквами, игнорируя регистр. Например, [A-Z] будет также находить строчные буквы, а Spam будет соответствовать Spam, spam или spAM. Это приведение к нижнему регистру не учитывает текущую локаль; оно будет учитывать, если также установить флаг LOCALE.

L
LOCALE

Делает \\w, \\W, \\b и \\B зависимыми от текущей локали вместо базы данных Unicode.

Локали – это возможность библиотеки C, предназначенная для написания программ, учитывающих языковые различия. Например, при обработке французского текста может понадобиться написать \\w+ для поиска слов, но \\w соответствует только классу символов [A-Za-z]; он не будет находить 'é' или 'ç'. Если система настроена правильно и выбрана французская локаль, некоторые функции C сообщат программе, что 'é' также следует считать буквой. Установка флага LOCALE при компиляции регулярного выражения заставит результирующий скомпилированный объект использовать эти функции C для \\w; это медленнее, но позволяет \\w+ находить французские слова так, как и ожидается.

M
MULTILINE

(^ и $ пока не объяснены; они будут представлены в разделе Дополнительные метасимволы.)

Обычно ^ сопоставляется только в начале строки, а $ – только в конце строки и непосредственно перед символом новой строки (если он есть) в конце строки. Когда указан этот флаг, ^ сопоставляется в начале строки и в начале каждой строки внутри строки, сразу после каждого символа новой строки. Аналогично, метасимвол $ сопоставляется в конце строки и в конце каждой строки (непосредственно перед каждым символом новой строки).

S
DOTALL

Заставляет специальный символ '.' сопоставляться с любым символом, включая символ новой строки; без этого флага '.' будет сопоставляться с любым символом, кроме символа новой строки.

A
ASCII

Заставляет \\w, \\W, \\b, \\B, \\s и \\S выполнять сопоставление только по ASCII вместо полного сопоставления Unicode. Это имеет смысл только для шаблонов Unicode и игнорируется для байтовых шаблонов.

X
VERBOSE

Этот флаг позволяет писать более читаемые регулярные выражения, предоставляя больше гибкости в их форматировании. Когда этот флаг указан, пробельные символы внутри строки регулярного выражения игнорируются, за исключением случаев, когда пробел находится внутри символьного класса или перед ним стоит неэкранированная обратная косая черта; это позволяет более четко организовывать и расставлять отступы в регулярном выражении. Этот флаг также позволяет помещать комментарии внутри регулярного выражения, которые будут игнорироваться движком; комментарии обозначаются символом '#', который не находится внутри символьного класса и не предваряется неэкранированной обратной косой чертой.

Например, вот RE, использующий 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)

Без режима verbose регулярное выражение выглядело бы так:

charref = re.compile("&#(0[0-7]+"
                     "|[0-9]+"
                     "|x[0-9a-fA-F]+);")

В приведённом выше примере использовалась автоматическая конкатенация строковых литералов Python для разбиения RE на более мелкие части, но её по-прежнему сложнее понять, чем версию с re.VERBOSE.

Дополнительные возможности шаблоновMore Pattern Power

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

Дополнительные метасимволыMore Metacharacters

Остались некоторые метасимволы, которые мы ещё не рассмотрели. Большинство из них будет рассмотрено в этом разделе.

Некоторые из оставшихся метасимволов, которые предстоит обсудить, – это проверки с нулевой шириной (zero-width assertions). Они не заставляют движок продвигаться по строке; вместо этого они вообще не потребляют символы и просто успешно проходят или нет. Например, \\b – это проверка того, что текущая позиция находится на границе слова; позиция \\b вообще не меняется. Это означает, что проверки с нулевой шириной никогда не следует повторять, потому что если они один раз совпадают в данной позиции, то, очевидно, могут совпадать бесконечное количество раз.

|

Альтернатива, или оператор «или». Если A и B – регулярные выражения, то A|B будет соответствовать любой строке, которая соответствует либо A, либо B. | имеет очень низкий приоритет, чтобы разумно работать при альтернативе многозначных строк. Crow|Servo будет соответствовать либо Crow, либо Servo, а не Cro, 'w' или 'S', и ervo.

Чтобы сопоставить литерал '|', используйте \| или заключите его в символьный класс, как в [|].

^

Соответствует началу строк. Если не установлен флаг MULTILINE, будет соответствовать только началу всей строки. В режиме MULTILINE также соответствует сразу после каждого символа новой строки внутри строки.

Например, если нужно найти слово From только в начале строки, следует использовать RE ^From.

>>> print(re.search('^From', 'From Here to Eternity'))  
<_sre.SRE_Match object at 0x...>
>>> print(re.search('^From', 'Reciting From Memory'))
None
$

Совпадение в конце строки, которая определяется как конец строки или любая позиция, за которой следует символ перевода строки.

>>> print(re.search('}$', '{block}'))  
<_sre.SRE_Match object at 0x...>
>>> print(re.search('}$', '{block} '))
None
>>> print(re.search('}$', '{block}\n'))  
<_sre.SRE_Match object at 0x...>

Чтобы сопоставить литерал '$', используйте \$ или заключите его в символьный класс, как в [$].

\A
Совпадает только в начале строки. Если не используется режим MULTILINE, \A и ^ по сути одно и то же. В режиме MULTILINE они различаются: \A по-прежнему совпадает только в начале строки, а ^ может совпадать в любом месте строки после символа новой строки.
\Z
Совпадение только в конце строки.
\b

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

Следующий пример находит class только когда это целое слово; не будет находить, когда оно является частью другого слова.

>>> p = re.compile(r'\bclass\b')
>>> print(p.search('no class at all'))  
<_sre.SRE_Match object at 0x...>
>>> print(p.search('the declassified algorithm'))
None
>>> print(p.search('one subclass is'))
None

При использовании этой специальной последовательности нужно помнить о двух тонкостях. Первая: это самое серьёзное столкновение между строковыми литералами Python и последовательностями регулярных выражений. В строковых литералах Python \b – это символ backspace (ASCII 8). Если не используются сырые строки, Python преобразует \b в backspace, и регулярное выражение будет работать не так, как ожидается. Следующий пример выглядит так же, как предыдущее регулярное выражение, но перед строкой RE нет 'r'.

>>> p = re.compile('\bclass\b')
>>> print(p.search('no class at all'))
None
>>> print(p.search('\b' + 'class' + '\b'))  
<_sre.SRE_Match object at 0x...>

Внутри символьного класса, где эта проверка не нужна, \b представляет символ backspace (для совместимости со строковыми литералами Python).

\B
Ещё одна проверка с нулевой длиной; противоположность \b. Совпадает, когда текущая позиция не находится на границе слова.

ГруппировкаGrouping

Часто требуется получить больше информации, чем просто – совпало RE или нет. Регулярные выражения часто используются для разбора строк с помощью RE, разделённого на несколько подгрупп, каждая из которых соответствует интересующему компоненту. Например, строка заголовка 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 присутствует всегда; это всё RE, поэтому методы объекта совпадения по умолчанию принимают группу 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 также используют обратную косую черту с числами для включения произвольных символов в строку, поэтому обязательно используйте сырую строку при включении обратных ссылок в RE.

Например, следующее RE обнаруживает удвоенные слова в строке.

>>> p = re.compile(r'(\b\w+)\s+\1')
>>> p.search('Paris in the the spring').group()
'the the'

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

Незахватывающие и именованные группыNon-capturing and Named Groups

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

Perl 5 широко известен своими мощными дополнениями к стандартным регулярным выражениям.\nДля этих новых возможностей разработчики Perl не могли выбрать новые метасимволы, состоящие из одного символа,\nили новые специальные последовательности, начинающиеся с \, не делая регулярные выражения Perl\nзапутанно отличными от стандартных RE. Если бы они выбрали & в качестве\nнового метасимвола, например, старые выражения предполагали бы, что & – это\nобычный символ, и не экранировали бы его, записывая \& или [&].

Разработчики Perl выбрали (?...) в качестве синтаксиса расширений. ? сразу после открывающей скобки было синтаксической ошибкой, потому что ? не к чему было бы применять повтор, так что никаких проблем совместимости это не вызвало. Символы сразу после ? указывают, какое расширение используется: например, (?=foo) – одно (положительная опережающая проверка), а (?:foo) – другое (незахватывающая группа с подвыражением foo).

Python поддерживает несколько расширений Perl и добавляет свой синтаксис расширений к синтаксису Perl. Если после вопросительного знака первым символом идёт P, значит, это расширение, специфичное для Python.

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

Иногда требуется использовать группу для обозначения части регулярного выражения, но нет необходимости извлекать содержимое группы. Это можно явно указать с помощью незахватывающей группы: (?:...), где ... можно заменить любым другим регулярным выражением.

>>> 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'

Именованные группы удобны, потому что позволяют использовать легко запоминающиеся имена вместо запоминания номеров. Вот пример регулярного выражения из модуля 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, можно также записать как (?P<word>\b\w+)\s+(?P=word):

>>> p = re.compile(r'(?P<word>\b\w+)\s+(?P=word)')
>>> p.search('Paris in the the spring').group()
'the the'

Опережающие проверкиLookahead Assertions

Ещё одна проверка нулевой ширины – опережающая проверка. Опережающие проверки бывают положительными и отрицательными и выглядят так:

(?=...)
Положительная опережающая проверка. Она успешна, если вложенное регулярное выражение, представленное здесь как ..., успешно совпадает в текущей позиции, и неуспешна в противном случае. Но как только вложенное выражение проверено, движок сопоставления не продвигается; остальная часть шаблона проверяется прямо с того места, где началась проверка.
(?!...)
Отрицательная опережающая проверка. Это противоположность положительной проверке; она успешна, если вложенное выражение не совпадает в текущей позиции строки.

Чтобы стало понятнее, рассмотрим случай, где полезна опережающая проверка. Возьмём простой шаблон для сопоставления имени файла и разделения его на имя базы и расширение, разделённые точкой .. Например, в 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$).*$

Изменение строкModifying Strings

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

Метод/Атрибут Назначение
split() Разделяет строку на список, разбивая её везде, где совпадает RE
sub() Находит все подстроки, совпадающие с RE, и заменяет их на другую строку
subn() Делает то же самое, что и sub(), но возвращает новую строку и количество замен

Разделение строкSplitting Strings

Метод split() шаблона разделяет строку везде, где встречается регулярное выражение, и возвращает список фрагментов. Он похож на метод split() строк, но предоставляет гораздо больше возможностей для выбора разделителей; строковый метод split() поддерживает только разбиение по пробельным символам или по фиксированной строке. Как и следовало ожидать, существует также функция уровня модуля re.split().

.split(string[, maxsplit=0])

Разделяет строку по совпадениям регулярного выражения. Если в RE используются захватывающие круглые скобки, то их содержимое также будет возвращено как часть результирующего списка. Если 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().']

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

>>> 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('[\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.']

Поиск и заменаSearch and Replace

Еще одна распространенная задача – найти все совпадения с шаблоном и заменить их другой строкой. Метод sub() принимает значение замены, которое может быть строкой или функцией, и обрабатываемую строку.

.sub(replacement, string[, count=0])

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

Необязательный аргумент 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() делает то же самое, но возвращает кортеж из двух элементов, содержащий новую строку и количество выполненных замен:

>>> 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 – в символ возврата каретки и так далее. Неизвестные управляющие последовательности, такие как \j, остаются без изменений. Обратные ссылки, например \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'.

Распространенные проблемыCommon Problems

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

Применение строковых методовUse String Methods

Иногда использование модуля re является ошибкой. Если вы сопоставляете фиксированную строку или одиночный класс символов и не используете никаких возможностей re, таких как флаг IGNORECASE, то полная мощь регулярных выражений может не понадобиться. У строк есть несколько методов для выполнения операций с фиксированными строками, и они обычно гораздо быстрее, поскольку реализация представляет собой один небольшой цикл на C, оптимизированный для этой цели, вместо большого, более универсального движка регулярных выражений.

Один из примеров – замена одной фиксированной строки на другую; например, можно заменить word на deed. re.sub() кажется подходящей функцией для этого, но рассмотрите метод replace(). Обратите внимание, что replace() также заменит word внутри слов, превратив swordfish в sdeedfish, но наивное RE word сделало бы то же самое. (Чтобы избежать замены в частях слов, шаблон должен быть \bword\b, чтобы требовалось наличие границы слова с обеих сторон от word. Это выходит за рамки возможностей replace().)

Еще одна распространенная задача – удаление всех вхождений одного символа из строки или замена его другим символом. Это можно сделать с помощью чего-то вроде re.sub('\n', ' ', S), но translate() способен выполнить обе задачи и будет быстрее любой операции с регулярными выражениями.

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

Жадный и нежадный режимыGreedy versus Non-Greedy

При повторении регулярного выражения, как в 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 Using re.VERBOSE

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

Для таких регулярных выражений может быть полезно указать флаг 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*$")

Обратная связь Feedback

Регулярные выражения – сложная тема. Помог ли вам этот документ понять их? Были ли части, которые остались неясными, или проблемы, с которыми вы столкнулись и которые не были здесь рассмотрены? Если да, отправьте предложения по улучшению автору.

Самая полная книга по регулярным выражениям – это, почти наверняка, «Mastering Regular Expressions» Джеффри Фридла, издательство O'Reilly. К сожалению, она полностью сосредоточена на реализациях регулярных выражений в Perl и Java и не содержит никакого материала по Python, поэтому она не пригодится в качестве справочника для программирования на Python. (Первое издание охватывало ныне удаленный модуль Python regex, что вам не очень поможет.) Подумайте о том, чтобы взять ее в библиотеке.