Документация 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. (Можно включить регистронезависимый режим, который позволит этому РВ сопоставляться также с Test или TEST; подробнее об этом позже.)

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

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

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

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

Метасимволы (кроме \) внутри классов неактивны. Например, [akm$] будет соответствовать любому из символов 'a', 'k', 'm' или '$'; '$' обычно является метасимволом, но внутри символьного класса он лишается своего специального значения.

Можно сопоставлять символы, не входящие в класс, дополняя набор. Это обозначается включением '^' в качестве первого символа класса. Например, [^5] будет соответствовать любому символу, кроме '5'. Если символ крышки (^) появляется в другом месте символьного класса, он не имеет специального значения. Например: [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') и так далее.

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

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

Шаг

Совпадение

Пояснение

1

a

a в РВ совпадает.

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 даёт верхнюю границу бесконечность.

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

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

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

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

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

>>> import re
>>> p = re.compile('ab*')
>>> p
re.compile('ab*')

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

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

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

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

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

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

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

Символы

Этап

\section

Строка для совпадения

\\section

Экранированный обратный слеш для re.compile()

"\\\\section"

Экранированные обратные слеши для строкового литерала

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

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

Кроме того, специальные escape-последовательности, которые допустимы в регулярных выражениях, но не допустимы как строковые литералы Python, теперь приводят к DeprecationWarning и со временем станут SyntaxError, то есть последовательности будут недействительны, если не использовать сырые строки или экранирование обратных косых черт.

Обычная строка

Сырая строка

"ab*"

r"ab*"

"\\\\section"

r"\\section"

"\\w+\\s+\\1"

r"\w+\s+\1"

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

Как только у вас есть объект, представляющий скомпилированное регулярное выражение, что с ним делать? Объекты Pattern имеют несколько методов и атрибутов. Здесь будут рассмотрены только самые важные; за полным списком обращайтесь к документации 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 может быть весьма полезен при отладке сложных регулярных выражений.

В данном 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'>

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

Метод/Атрибут

Назначение

group()

Возвращает строку, совпавшую с РВ

start()

Возвращает начальную позицию совпадения

end()

Возвращает конечную позицию совпадения

span()

Возвращает кортеж с (начало, конец) позициями совпадения

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

>>> m.group()
'tempo'
>>> m.start(), m.end()
(0, 5)
>>> m.span()
(0, 5)

group() возвращает подстроку, совпавшую с RE. 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']

The r prefix, making the literal a raw string literal, is needed in this example because escape sequences in a normal “cooked” string literal that are not recognized by Python, as opposed to regular expressions, now result in a DeprecationWarning and will eventually become a SyntaxError. See The Backslash Plague.

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

Необязательно создавать объект шаблона и вызывать его методы; модуль 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 '>

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

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

Флаги компиляции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] будет совпадать и со строчными буквами. Полное сопоставление Unicode также работает, если только флаг ASCII не отключает соответствие не-ASCII. Когда шаблоны Unicode [a-z] или [A-Z] используются в сочетании с флагом IGNORECASE, они будут совпадать с 52 ASCII-буквами и 4 дополнительными не-ASCII-буквами: ‘İ’ (U+0130, латинская заглавная буква I с точкой сверху), ‘ı’ (U+0131, латинская строчная буква без точки i), ‘ſ’ (U+017F, латинская строчная буква длинная s) и ‘K’ (U+212A, знак Кельвина). Spam будет совпадать с 'Spam', 'spam', 'spAM' или 'ſpam' (последнее совпадает только в режиме Unicode). Это приведение к нижнему регистру не учитывает текущую локаль; оно будет учитывать, если также установлен флаг LOCALE.

L
LOCALE

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

Локали – это функция библиотеки C, предназначенная для помощи в написании программ, учитывающих языковые различия. Например, если вы обрабатываете закодированный французский текст, вам может понадобиться написать \w+ для сопоставления слов, но \w сопоставляется только с классом символов [A-Za-z] в байтовых шаблонах; он не будет сопоставляться с байтами, соответствующими é или ç. Если ваша система настроена правильно и выбрана французская локаль, некоторые функции C сообщат программе, что байт, соответствующий é, также должен считаться буквой. Установка флага LOCALE при компиляции регулярного выражения заставит скомпилированный объект использовать эти функции C для \w; это медленнее, но также позволяет \w+ сопоставлять французские слова так, как вы ожидаете. Использование этого флага не рекомендуется в Python 3, поскольку механизм локали очень ненадёжен, одновременно обрабатывает только одну «культуру» и работает только с 8-битными локалями. Сопоставление с использованием Unicode уже включено по умолчанию в Python 3 для строковых (str) шаблонов, и оно способно обрабатывать разные локали/языки.

M
MULTILINE

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

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

S
DOTALL

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

A
ASCII

Заставляет \w, \W, \b, \B, \s и \S выполнять сопоставление только с ASCII вместо полного сопоставления с 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

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

Некоторые из оставшихся метасимволов – это утверждения нулевой длины. Они не заставляют движок продвигаться по строке; вместо этого они вообще не потребляют символов и просто возвращают успех или неудачу. Например, \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'))  
<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 – это символ backspace, ASCII-код 8. Если не используются сырые строки, Python преобразует \b в backspace, и RE не будет совпадать так, как ожидается. Следующий пример выглядит так же, как предыдущий RE, но в нём опущен 'r' перед строкой RE.

>>> 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 представляет символ 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, поэтому методы объекта match по умолчанию используют группу 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\b')
>>> p.search('Paris in the the spring').group()
'the the'

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

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

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

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

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

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

Именованные группы удобны, поскольку позволяют использовать легко запоминающиеся имена вместо запоминания номеров. Вот пример RE из модуля 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'

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

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

Поиск и замена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 является строкой, все escape-последовательности с обратной косой чертой обрабатываются. То есть \n преобразуется в символ новой строки, \r – в возврат каретки и так далее. Неизвестные escape-последовательности, такие как \&, остаются без изменений. Обратные ссылки, например \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>

RE находит '<' в '<html>', а .* поглощает остаток строки. Однако в RE остаётся ещё часть, и > не может совпасть в конце строки, поэтому движку регулярных выражений приходится возвращаться посимвольно, пока он не найдёт совпадение для >. Итоговое совпадение простирается от '<' в '<html>' до '>' в '</title>', что не то, что нужно.

В этом случае решение – использовать нежадные квантификаторы *?, +?, ?? или {m,n}?, которые захватывают как мало текста, насколько это возможно. В приведённом выше примере '>' проверяется сразу после совпадения первого '<', и когда это не удаётся, движок продвигается на один символ, заново проверяя '>' на каждом шаге. Это даёт правильный результат:

>>> print(re.match('<.*?>', s).group())
<html>

(Заметьте, что разбор HTML или XML с помощью регулярных выражений – дело мучительное. Быстрые и грязные шаблоны справятся с обычными случаями, но у HTML и XML есть особые случаи, которые сломают очевидное регулярное выражение; к тому времени, как вы напишете регулярное выражение, обрабатывающее все возможные случаи, шаблоны станут очень сложными. Используйте для таких задач модуль разбора HTML или XML.)

Использование re.VERBOSE Using re.VERBOSE

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

Для таких RE указание флага re.VERBOSE при компиляции регулярного выражения может быть полезно, поскольку позволяет форматировать регулярное выражение более чётко.

Флаг re.VERBOSE имеет несколько эффектов. Пробельные символы в регулярном выражении, которые не находятся внутри символьного класса, игнорируются. Это означает, что выражение вида dog | cat эквивалентно менее читаемому dog|cat, но [a b] по-прежнему будет соответствовать символам 'a', 'b' или пробелу. Кроме того, в RE можно вставлять комментарии; комментарии начинаются с символа # и продолжаются до следующей новой строки. При использовании с тройными кавычками это позволяет форматировать RE более аккуратно:

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. (В первом издании рассматривался модуль regex, который теперь удалён из Python, что вам не сильно поможет.) Рекомендуем взять её в библиотеке.