Руководство по регулярным выражениям
- Автор:
-
A.M. Kuchling <amk@amk.ca>
Введение
Регулярные выражения (называемые RE, regex или шаблонами regex) — это, по сути, небольшой специализированный язык программирования, встроенный в Python и доступный через модуль re. С помощью этого небольшого языка вы задаёте правила для набора строк, которые хотите сопоставить; в этот набор могут входить предложения на английском языке, адреса электронной почты, команды TeX или всё, что угодно. Затем можно задавать вопросы вроде «Соответствует ли эта строка шаблону?» или «Есть ли в этой строке совпадение с шаблоном?». Также можно использовать RE для изменения строки или разделения её на части различными способами.
Шаблоны регулярных выражений компилируются в последовательность байт-кодов, которые затем выполняются написанным на C механизмом сопоставления. При углублённом использовании может потребоваться внимательно изучить, как механизм выполняет заданное RE, и написать RE определённым образом, чтобы получить байт-код, работающий быстрее. Оптимизация в этом документе не рассматривается, поскольку для неё необходимо хорошо понимать внутреннее устройство механизма сопоставления.
Язык регулярных выражений относительно невелик и ограничен, поэтому с помощью регулярных выражений нельзя решить все возможные задачи обработки строк. Есть также задачи, которые можно решить с помощью регулярных выражений, но выражения при этом оказываются очень сложными. В таких случаях, возможно, лучше написать код на Python для выполнения обработки: хотя он будет медленнее сложного регулярного выражения, скорее всего, его будет проще понять.
Простые шаблоны
Начнём с изучения простейших регулярных выражений. Поскольку регулярные выражения используются для работы со строками, начнём с самой распространённой задачи: сопоставления символов.
Подробное объяснение лежащей в основе регулярных выражений информатики (детерминированных и недетерминированных конечных автоматов) можно найти почти в любом учебнике по написанию компиляторов.
Сопоставление символов
Большинство букв и символов просто соответствуют сами себе. Например, регулярное выражение test точно сопоставится со строкой test. (Можно включить режим без учёта регистра, в котором это RE будет также сопоставляться с Test или TEST; об этом подробнее далее.)
Из этого правила есть исключения: некоторые символы являются специальными метасимволами и не соответствуют сами себе. Вместо этого они указывают на то, что нужно сопоставить нечто особенное, или влияют на другие части RE, повторяя их или изменяя их значение. Значительная часть этого руководства посвящена различным метасимволам и их назначению.
Ниже приведён полный список метасимволов; их значения обсуждаются в оставшейся части этого руководства.
. ^ $ * + ? { } [ ] \ | ( )
Первые метасимволы, которые мы рассмотрим, — [ и ]. Они используются для задания символьного класса — набора символов, которые нужно сопоставить. Символы можно перечислить по отдельности или указать диапазон, задав два символа и разделив их символом '-'. Например, [abc] сопоставится с любым из символов a, b или c; это то же самое, что [a-c], где для выражения того же набора символов используется диапазон. Если нужно сопоставлять только строчные буквы, ваше RE будет таким: [a-z].
Метасимволы (кроме \) внутри символьных классов не действуют. Например, [akm$] сопоставится с любым из символов 'a', 'k', 'm' или '$'; '$' обычно является метасимволом, но внутри символьного класса утрачивает своё специальное значение.
Можно сопоставлять символы, не перечисленные в классе, задав дополнение множества. Для этого '^' ставится первым символом класса. Например, [^5] сопоставится с любым символом, кроме '5'. Если каретка находится в другом месте символьного класса, она не имеет специального значения. Например: [5^] сопоставится либо с '5', либо с '^'.
Возможно, самый важный метасимвол — обратная косая черта, \. Как и в строковых литералах Python, за обратной косой чертой могут следовать различные символы, обозначающие специальные последовательности. Она также используется для экранирования всех метасимволов, чтобы их можно было сопоставлять в шаблонах; например, если нужно сопоставить [ или \, перед ними можно поставить обратную косую черту, чтобы убрать их специальное значение: \[ или \\.
Некоторые специальные последовательности, начинающиеся с '\', обозначают заранее заданные наборы символов, которые часто бывают полезны, например набор цифр, набор букв или набор символов, не являющихся пробельными.
Рассмотрим пример: \w сопоставляется с любым буквенно-цифровым символом. Если шаблон регулярного выражения представлен байтами, это эквивалентно классу [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), в котором он сопоставляется даже с символом новой строки. . часто используется, когда нужно сопоставить «любой символ».
Повторение элементов
Возможность сопоставлять различные наборы символов — первое преимущество регулярных выражений, недоступное при использовании методов строк. Однако если бы это было единственным дополнительным свойством регулярных выражений, они не давали бы особых преимуществ. Ещё одна возможность — указать, что определённые части RE должны повторяться заданное число раз.
Первый метасимвол повторения, который мы рассмотрим, — *. * не сопоставляется с буквальным символом '*'; вместо этого он указывает, что предыдущий символ может сопоставляться ноль или более раз, а не ровно один раз.
Например, ca*t сопоставится с 'ct' (0 символов 'a'), 'cat' (1 символ 'a'), 'caaat' (3 символа 'a') и так далее.
Повторы, такие как *, являются жадными: повторяя RE, механизм сопоставления пытается выполнить повтор как можно больше раз. Если последующие части шаблона не совпадают, механизм сопоставления возвращается назад и пытается выполнить сопоставление снова с меньшим числом повторов.
Пошаговый пример поможет лучше понять это. Рассмотрим выражение a[bcd]*b. Оно сопоставляется с буквой 'a', нулём или более букв из класса [bcd] и, наконец, заканчивается символом 'b'. Теперь представим, что это RE сопоставляется со строкой 'abcbd'.
Шаг | Сопоставлено | Объяснение |
|---|---|---|
1 |
| Сопоставляется |
2 |
| Механизм сопоставляет |
3 | Неудача | Механизм пытается сопоставить |
4 |
| Вернуться назад, чтобы |
5 | Неудача | Снова попробовать |
6 |
| Снова вернуться назад, чтобы |
7 |
| Снова попробовать |
Теперь достигнут конец RE, и оно сопоставилось с 'abcb'. Это показывает, что сначала механизм сопоставления продвигается как можно дальше, а если совпадение не найдено, постепенно возвращается назад и снова и снова пытается сопоставить оставшуюся часть RE. Он будет возвращаться назад, пока не попробует ноль повторов [bcd]*; если и после этого сопоставление не удастся, механизм сделает вывод, что строка вовсе не соответствует RE.
Другой метасимвол повторения — +, который сопоставляется один или более раз. Обратите особое внимание на разницу между * и +: * сопоставляется ноль или более раз, поэтому повторяемый элемент может вообще отсутствовать, тогда как + требует как минимум одного вхождения. В похожем примере ca+t сопоставится с 'cat' (1 'a'), 'caaat' (3 символа 'a'), но не сопоставится с 'ct'.
Есть ещё два оператора повторения, или квантификатора. Знак вопроса, ?, сопоставляется либо один раз, либо ноль раз; можно считать, что он делает элемент необязательным. Например, home-?brew сопоставится либо с 'homebrew', либо с 'home-brew'.
Самый сложный квантификатор — {m,n}, где m и n — десятичные целые числа. Этот квантификатор означает, что элемент должен повторяться не менее m и не более n раз. Например, a/{1,3}b сопоставится с 'a/b', 'a//b' и 'a///b'. Он не сопоставится с 'ab', где нет косых черт, или с 'a////b', где их четыре.
Можно опустить m или n; в этом случае для пропущенного значения используется разумное значение по умолчанию. Пропуск m трактуется как нижняя граница 0, а пропуск n задаёт верхнюю границу, равную бесконечности.
Простейший случай {m} означает, что предшествующий элемент должен повториться ровно m раз. Например, a/{2}b сопоставится только с 'a//b'.
Читатели, склонные к редукционизму, могут заметить, что все три других квантификатора можно выразить с помощью этой записи. {0,} эквивалентно *, {1,} эквивалентно +, а {0,1} — это то же самое, что ?. По возможности лучше использовать *, + или ? просто потому, что они короче и легче читаются.
Использование регулярных выражений
Теперь, когда мы рассмотрели несколько простых регулярных выражений, как использовать их в Python? Модуль re предоставляет интерфейс к механизму регулярных выражений, позволяя компилировать регулярные выражения в объекты, а затем выполнять с их помощью сопоставления.
Компиляция регулярных выражений
Регулярные выражения компилируются в объекты шаблонов, методы которых позволяют выполнять различные операции, например искать совпадения с шаблоном или выполнять подстановки в строках.
>>> import re
>>> p = re.compile('ab*')
>>> p
re.compile('ab*')
re.compile() также принимает необязательный аргумент flags, который используется для включения различных специальных возможностей и вариантов синтаксиса. Позже мы рассмотрим доступные настройки, а пока достаточно одного примера:
>>> p = re.compile('ab*', re.IGNORECASE)
Регулярное выражение передаётся в re.compile() в виде строки. Регулярные выражения обрабатываются как строки, поскольку они не являются частью основного языка Python и для их записи не был создан специальный синтаксис. (Есть приложения, которым регулярные выражения вообще не нужны, поэтому нет необходимости перегружать спецификацию языка, добавляя их.) Вместо этого модуль re — это просто модуль расширения на C, включённый в состав Python, как и модули socket или zlib.
Запись регулярных выражений в строках упрощает язык Python, но у этого есть один недостаток, который рассматривается в следующем разделе.
Проблема обратной косой черты
Как уже упоминалось, в регулярных выражениях символ обратной косой черты ('\') используется для обозначения специальных форм или для того, чтобы специальные символы можно было использовать без вызова их особого значения. Это конфликтует с использованием того же символа в Python для той же цели в строковых литералах.
Предположим, вы хотите записать регулярное выражение, которое соответствует строке \section, встречающейся, например, в файле LaTeX. Чтобы понять, что нужно написать в коде программы, начните с желаемой строки, которой должно соответствовать выражение. Затем необходимо экранировать все обратные косые черты и другие метасимволы, добавив перед ними обратную косую черту, в результате получится строка \\section. Полученную строку, которую нужно передать в re.compile(), следует записать как \\section. Однако для представления её в виде строкового литерала Python обе обратные косые черты нужно экранировать ещё раз.
Символы | Этап |
|---|---|
| Текстовая строка, которой нужно найти соответствие |
| Экранированная обратная косая черта для |
| Экранированные обратные косые черты для строкового литерала |
Короче говоря, чтобы найти соответствие для буквальной обратной косой черты, в качестве строки регулярного выражения нужно написать '\\\\', поскольку регулярное выражение должно быть \\, а каждую обратную косую черту внутри обычного строкового литерала Python нужно записать как \\. В регулярных выражениях, где обратные косые черты встречаются часто, это приводит к большому количеству повторяющихся обратных косых черт и затрудняет понимание полученных строк.
Решение — использовать в Python для регулярных выражений запись необработанных строк: обратные косые черты никак особо не обрабатываются в строковом литерале с префиксом 'r', поэтому r"\n" — это строка из двух символов, содержащая '\' и 'n', тогда как "\n" — это строка из одного символа, содержащая символ новой строки. В коде Python регулярные выражения часто записывают именно в таком формате необработанных строк.
Кроме того, специальные escape-последовательности, допустимые в регулярных выражениях, но недопустимые в строковых литералах Python, теперь вызывают предупреждение SyntaxWarning и со временем станут ошибкой SyntaxError. Это означает, что такие последовательности будут недопустимы, если не использовать запись необработанных строк или экранирование обратных косых черт.
Обычная строка | Необработанная строка |
|---|---|
|
|
|
|
|
|
Выполнение сопоставлений
Что делать с объектом, представляющим скомпилированное регулярное выражение? У объектов шаблонов есть несколько методов и атрибутов. Здесь будут рассмотрены только самые важные из них; полный список см. в документации модуля re.
Метод/атрибут | Назначение |
|---|---|
| Определяет, соответствует ли регулярное выражение началу строки. |
| Просматривает строку в поисках позиции, в которой этому регулярному выражению соответствует её часть. |
| Находит все подстроки, соответствующие регулярному выражению, и возвращает их в виде списка. |
| Находит все подстроки, соответствующие регулярному выражению, и возвращает их в виде итератора. |
match() и search() возвращают None, если совпадение найти не удалось. При успешном поиске возвращается экземпляр объекта совпадения, содержащий сведения о совпадении: его начальную и конечную позиции, найденную подстроку и многое другое.
Это можно изучить, экспериментируя с модулем re в интерактивном режиме.
В этом HOWTO в примерах используется стандартный интерпретатор Python. Сначала запустите интерпретатор Python, импортируйте модуль re и скомпилируйте регулярное выражение:
>>> import re
>>> p = re.compile('[a-z]+')
>>> p
re.compile('[a-z]+')
Теперь можно проверить разные строки на соответствие регулярному выражению [a-z]+. Пустая строка не должна ему соответствовать, поскольку + означает «одно или более повторений». В этом случае match() должна вернуть None, поэтому интерпретатор ничего не выведет. Чтобы прояснить ситуацию, можно явно вывести результат match().
>>> p.match("")
>>> print(p.match(""))
None
Теперь попробуем строку, которая должна соответствовать выражению, например tempo. В этом случае match() вернёт объект совпадения, поэтому результат следует сохранить в переменной для дальнейшего использования.
>>> m = p.match('tempo')
>>> m
<re.Match object; span=(0, 5), match='tempo'>
Теперь можно запросить у объекта совпадения сведения о строке, соответствующей шаблону. У экземпляров объекта совпадения также есть несколько методов и атрибутов; наиболее важные из них:
Метод/атрибут | Назначение |
|---|---|
| Возвращает строку, соответствующую регулярному выражению |
| Возвращает начальную позицию совпадения |
| Возвращает конечную позицию совпадения |
| Возвращает кортеж с начальной и конечной позициями совпадения |
Попробуйте вызвать эти методы, чтобы быстро понять их значение:
>>> m.group() 'tempo' >>> m.start(), m.end() (0, 5) >>> m.span() (0, 5)
group() возвращает подстроку, которой соответствует регулярное выражение. start() и end() возвращают начальный и конечный индексы совпадения. span() возвращает оба индекса, начала и конца, в одном кортеже. Поскольку метод match() проверяет, соответствует ли регулярное выражение началу строки, значение start() всегда будет равно нулю. Однако метод search() у шаблонов просматривает всю строку, поэтому в этом случае совпадение может начинаться не с нулевой позиции.
>>> print(p.match('::: message'))
None
>>> m = p.search('::: message'); print(m)
<re.Match object; span=(4, 11), match='message'>
>>> m.group()
'message'
>>> m.span()
(4, 11)
В настоящих программах чаще всего объект совпадения сохраняют в переменной, а затем проверяют, был ли он равен None. Обычно это выглядит так:
p = re.compile( ... )
m = p.match( 'string goes here' )
if m:
print('Match found: ', m.group())
else:
print('No match')
Два метода шаблона возвращают все совпадения с шаблоном. findall() возвращает список совпавших строк:
>>> p = re.compile(r'\d+')
>>> p.findall('12 drummers drumming, 11 pipers piping, 10 lords a-leaping')
['12', '11', '10']
В этом примере нужен префикс r, превращающий литерал в необработанный строковый литерал, поскольку escape-последовательности в обычных «обработанных» строковых литералах, которые Python не распознаёт (в отличие от регулярных выражений), теперь вызывают предупреждение SyntaxWarning и со временем станут ошибкой SyntaxError. См. раздел Проблема обратной косой черты.
findall() должен создать весь список, прежде чем вернуть его в качестве результата. Метод finditer() возвращает последовательность экземпляров объекта совпадения в виде итератора:
>>> iterator = p.finditer('12 drummers drumming, 11 ... 10 ...')
>>> iterator
<callable_iterator object at 0x...>
>>> for match in iterator:
... print(match.span())
...
(0, 2)
(22, 24)
(29, 31)
Функции уровня модуля
Не обязательно создавать объект шаблона и вызывать его методы: модуль re также предоставляет функции верхнего уровня с именами match(), search(), findall(), sub() и так далее. Эти функции принимают те же аргументы, что и соответствующие методы шаблонов, но строка регулярного выражения передаётся первой, и возвращают либо None, либо экземпляр объекта совпадения.
>>> print(re.match(r'From\s+', 'Fromage amk')) None >>> re.match(r'From\s+', 'From amk Thu May 14 19:12:10 1998') <re.Match object; span=(0, 5), match='From '>
Внутри эти функции просто создают для вас объект шаблона и вызывают соответствующий метод. Кроме того, они сохраняют скомпилированный объект в кэше, поэтому при последующих вызовах с тем же регулярным выражением шаблон не придётся разбирать снова и снова.
Что использовать: эти функции уровня модуля или объект шаблона и его методы? Если вы обращаетесь к регулярному выражению в цикле, предварительная компиляция позволит сэкономить несколько вызовов функций. Вне циклов разница невелика благодаря внутреннему кэшу.
Флаги компиляции
Флаги компиляции позволяют изменять некоторые аспекты работы регулярных выражений. В модуле re флаги доступны под двумя именами: полным, например IGNORECASE, и коротким, состоящим из одной буквы, например I. (Если вы знакомы с модификаторами шаблонов Perl, то знаете, что в коротких формах используются те же буквы; например, короткая форма для re.VERBOSE — re.X.) Можно указать несколько флагов, объединив их побитовой операцией ИЛИ; например, re.I | re.M устанавливает флаги I и M.
Ниже приведена таблица доступных флагов, а затем более подробное описание каждого из них.
Флаг | Значение |
|---|---|
Заставляет некоторые escape-последовательности, например | |
Заставляет | |
Выполняет сопоставление без учёта регистра. | |
Выполняет сопоставление с учётом локали. | |
Многострочное сопоставление, влияющее на | |
Включает подробный режим записи регулярных выражений, позволяющий структурировать их более понятно и аккуратно. |
- re.I
- re.IGNORECASE
-
Выполняет сопоставление без учёта регистра; символьные классы и строковые литералы будут соответствовать буквам независимо от регистра. Например,
[A-Z]будет соответствовать и строчным буквам. Полное сопоставление Юникод также работает, если только флагASCIIне используется для отключения сопоставления символов не из ASCII. Если шаблоны Юникода[a-z]или[A-Z]используются вместе с флагомIGNORECASE, они будут соответствовать 52 буквам ASCII и ещё четырём символам не из ASCII: ‘İ’ (U+0130, заглавная латинская I с точкой сверху), ‘ı’ (U+0131, строчная латинская i без точки), ‘ſ’ (U+017F, строчная длинная латинская s) и ‘K’ (U+212A, знак кельвина).Spamбудет соответствовать'Spam','spam','spAM'или'ſpam'(последний символ учитывается только в режиме Юникод). При преобразовании к нижнему регистру текущая локаль не учитывается; она будет учитываться, если также установить флагLOCALE.
- re.L
- re.LOCALE
-
Заставляет
\w,\W,\b,\Bи сопоставление без учёта регистра зависеть от текущей локали, а не от базы данных Юникод.Локали — это возможность библиотеки C, предназначенная для создания программ, учитывающих различия между языками. Например, при обработке закодированного французского текста вам может понадобиться использовать
\w+для поиска слов, однако\wв шаблонах байтов соответствует только символьному классу[A-Za-z]; эта последовательность не будет соответствовать байтам, представляющимéилиç. Если ваша система настроена правильно и выбрана французская локаль, некоторые функции C сообщат программе, что байт, соответствующийé, также следует считать буквой. При установке флагаLOCALEво время компиляции регулярного выражения скомпилированный объект будет использовать эти функции C для\w; это работает медленнее, но также позволяет\w+находить французские слова так, как вы ожидаете. Использовать этот флаг в Python 3 не рекомендуется: механизм локалей очень ненадёжен, одновременно обрабатывает только одну «культуру» и работает только с 8-битными локалями. В Python 3 сопоставление Юникод уже включено по умолчанию для шаблонов Юникод (str) и умеет обрабатывать разные локали и языки.
- re.M
- re.MULTILINE
-
(
^и$ещё не объяснялись; они будут представлены в разделе Дополнительные метасимволы.)Обычно
^соответствует только началу строки, а$— только концу строки и непосредственно перед символом новой строки (если он есть) в конце строки. Если указан этот флаг,^соответствует началу строки и началу каждой строки внутри неё, непосредственно после каждого символа новой строки. Аналогично, метасимвол$соответствует концу строки и концу каждой строки (непосредственно перед каждым символом новой строки).
- re.S
- re.DOTALL
-
Заставляет специальный символ
'.'соответствовать любому символу, включая символ новой строки; без этого флага'.'соответствует любому символу, кроме символа новой строки.
- re.A
- re.ASCII
-
Заставляет
\w,\W,\b,\B,\sи\Sвыполнять сопоставление только с ASCII вместо полного сопоставления Юникод. Это имеет смысл только для шаблонов Юникод и игнорируется для шаблонов байтов.
- re.X
- re.VERBOSE
-
Этот флаг позволяет писать более понятные регулярные выражения, предоставляя больше свободы в их форматировании. Если флаг указан, пробельные символы в строке регулярного выражения игнорируются, кроме случаев, когда они находятся внутри символьного класса или перед ними стоит неэкранированная обратная косая черта; это позволяет нагляднее структурировать регулярное выражение и расставлять отступы. Флаг также позволяет добавлять в регулярное выражение комментарии, которые будут игнорироваться механизмом; комментарий начинается с символа
'#', который не находится внутри символьного класса и перед которым не стоит неэкранированная обратная косая черта.Например, вот регулярное выражение с флагом
re.VERBOSE; посмотрите, насколько легче его читать:charref = re.compile(r""" &[#] # Start of a numeric entity reference ( 0[0-7]+ # Octal form | [0-9]+ # Decimal form | x[0-9a-fA-F]+ # Hexadecimal form ) ; # Trailing semicolon """, re.VERBOSE)Без подробного режима это регулярное выражение выглядело бы так:
charref = re.compile("&#(0[0-7]+" "|[0-9]+" "|x[0-9a-fA-F]+);")В примере выше для разбиения регулярного выражения на более мелкие части используется автоматическая конкатенация строковых литералов в Python, но понять его всё равно сложнее, чем вариант с флагом
re.VERBOSE.
Больше возможностей шаблонов
До сих пор мы рассмотрели лишь часть возможностей регулярных выражений. В этом разделе мы познакомимся с некоторыми новыми метасимволами и узнаем, как использовать группы для извлечения фрагментов найденного текста.
Другие метасимволы
Есть метасимволы, которые мы ещё не рассмотрели. Большинство из них будет разобрано в этом разделе.
Некоторые из оставшихся метасимволов, которые мы обсудим, — это утверждения нулевой ширины. Они не заставляют механизм продвигаться по строке: вместо этого они не потребляют ни одного символа и просто завершаются успешно или неуспешно. Например, \b — это утверждение о том, что текущая позиция находится на границе слова; \b никак не меняет эту позицию. Это означает, что утверждения нулевой ширины не следует повторять: если они совпали в каком-либо месте, то, очевидно, могут совпадать там бесконечное число раз.
-
| -
Альтернация, или оператор «или». Если A и B — регулярные выражения,
A|Bбудет соответствовать любой строке, которая совпадает либо с A, либо с B.|имеет очень низкий приоритет, чтобы корректно работать при чередовании строк из нескольких символов.Crow|Servoбудет соответствовать либо'Crow', либо'Servo', а не'Cro','w'или'S', и'ervo'.Чтобы найти буквальный символ
'|', используйте\|или поместите его внутрь символьного класса, как в[|]. -
^ -
Совпадает в начале строк. Если флаг
MULTILINEне установлен, совпадение будет только в начале строки. В режимеMULTILINEэто также совпадение сразу после каждого символа новой строки внутри строки.Например, если нужно найти слово
Fromтолько в начале строки, следует использовать 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 -
Совпадает только в конце строки.
-
\Z -
То же, что и
\z. Для совместимости со старыми версиями Python. -
\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в символ возврата на одну позицию, и RE не будет находить совпадения так, как вы ожидаете. Следующий пример выглядит так же, как предыдущая RE, но перед строкой RE отсутствует'r'.>>> p = re.compile('\bclass\b') >>> print(p.search('no class at all')) None >>> print(p.search('\b' + 'class' + '\b')) <re.Match object; span=(0, 7), match='\x08class\x08'>Во-вторых, внутри символьного класса, где это утверждение не используется,
\bобозначает символ возврата на одну позицию — для совместимости со строковыми литералами Python. -
\B -
Ещё одно утверждение нулевой ширины. Оно противоположно
\bи совпадает только тогда, когда текущая позиция не является границей слова.
Группировка
Часто нужно получить больше информации, чем просто узнать, совпало ли 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() возвращает кортеж со строками всех подгрупп — от первой до последней.
>>> 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'
Подобные обратные ссылки редко полезны для простого поиска по строке: мало текстовых форматов повторяют данные таким образом. Однако вскоре вы узнаете, что они очень полезны при замене строк.
Незахватывающие и именованные группы
Сложные RE могут использовать множество групп — как для захвата нужных подстрок, так и для группировки и структурирования самого выражения. В сложных 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 — это имя группы. Именованные группы ведут себя точно так же, как захватывающие, но дополнительно связывают группу с именем. Методы объекта совпадения, работающие с захватывающими группами, принимают либо целые числа, обозначающие номер группы, либо строки с именем нужной группы. Именованным группам также присваиваются номера, поэтому получить информацию о группе можно двумя способами:
>>> 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'
Утверждения опережающей проверки
Ещё одно утверждение нулевой ширины — утверждение опережающей проверки. Утверждения опережающей проверки бывают положительными и отрицательными и выглядят так:
-
(?=...) -
Положительное утверждение опережающей проверки. Оно завершается успешно, если заключённое в нём регулярное выражение, обозначенное здесь как
..., успешно совпадает в текущей позиции, и не завершается успешно в противном случае. Однако после проверки заключённого выражения механизм поиска не продвигается дальше: оставшаяся часть шаблона проверяется именно там, где началось утверждение. -
(?!...) -
Отрицательное утверждение опережающей проверки. Оно противоположно положительному: завершается успешно, если заключённое выражение не совпадает с текстом в текущей позиции строки.
Чтобы разобраться на конкретном примере, рассмотрим ситуацию, в которой полезна опережающая проверка. Возьмём простой шаблон для поиска имени файла и разделения его на базовое имя и расширение, отделённое символом .. Например, в 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$)[^.]*$
Изменение строк
До этого момента мы выполняли поиск только по неизменяемой строке. Регулярные выражения также часто используют для изменения строк различными способами с помощью следующих методов шаблона:
Метод/атрибут | Назначение |
|---|---|
| Разбивает строку на список, разделяя её в местах совпадения с RE |
| Находит все подстроки, совпадающие с RE, и заменяет их другой строкой |
| Выполняет то же, что и |
Разбиение строк
Метод split() шаблона разбивает строку в местах совпадения с RE и возвращает список полученных фрагментов. Он похож на строковый метод split(), но позволяет использовать гораздо более разнообразные разделители: строковый метод split() поддерживает разбиение только по пробельным символам или по фиксированной строке. Как и следовало ожидать, существует также функция уровня модуля re.split().
- .split(string[, maxsplit=0])
-
Разбивает string по совпадениям с регулярным выражением. Если в 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.']
Поиск и замена
Ещё одна распространённая задача — найти все совпадения с шаблоном и заменить их другой строкой. Метод sub() принимает значение для замены, которым может быть строка или функция, а также обрабатываемую строку.
- .sub(replacement, string[, count=0])
-
Возвращает строку, полученную заменой всех неперекрывающихся совпадений RE в string, начиная с самого левого, на значение replacement. Если шаблон не найден, строка string возвращается без изменений.
Необязательный аргумент count задаёт максимальное количество заменяемых совпадений с шаблоном; count должен быть неотрицательным целым числом. Значение 0 по умолчанию означает, что заменяются все совпадения.
Вот простой пример использования метода sub(). Он заменяет названия цветов словом colour:
>>> p = re.compile('(blue|white|red)')
>>> p.sub('colour', 'blue socks and red shoes')
'colour socks and colour shoes'
>>> p.sub('colour', 'blue socks and red shoes', count=1)
'colour socks and red shoes'
Метод subn() выполняет ту же работу, но возвращает кортеж из двух элементов: нового значения строки и количества выполненных замен:
>>> p = re.compile('(blue|white|red)')
>>> p.subn('colour', 'blue socks and red shoes')
('colour socks and colour shoes', 2)
>>> p.subn('colour', 'no colours at all')
('no colours at all', 0)
Пустые совпадения заменяются только тогда, когда они не соседствуют с предыдущим пустым совпадением.
>>> p = re.compile('x*')
>>> p.sub('-', 'abxd')
'-a-b--d-'
Если replacement — строка, все управляющие последовательности с обратной косой чертой в ней обрабатываются. То есть \n преобразуется в один символ новой строки, \r — в возврат каретки и так далее. Неизвестные последовательности, например \&, остаются без изменений. Обратные ссылки, такие как \6, заменяются подстрокой, найденной соответствующей группой RE. Это позволяет включать фрагменты исходного текста в результирующую строку замены.
В этом примере находится слово section, за которым следует строка, заключённая в { и }, а затем section заменяется на subsection:
>>> p = re.compile('section{ ( [^}]* ) }', re.VERBOSE)
>>> p.sub(r'subsection{\1}','section{First} section{second}')
'subsection{First} subsection{second}'
Существует также синтаксис ссылок на именованные группы, заданные с помощью синтаксиса (?P<name>...). \g<name> использует подстроку, найденную группой с именем name, а \g<number> использует соответствующий номер группы. Поэтому \g<2> эквивалентно \2, но не допускает неоднозначности в строке замены, например \g<2>0. (\20 будет интерпретировано как ссылка на группу 20, а не как ссылка на группу 2, за которой следует буквальный символ '0'.) Следующие варианты замены равнозначны, но используют все три варианта записи строки замены.
>>> p = re.compile('section{ (?P<name> [^}]* ) }', re.VERBOSE)
>>> p.sub(r'subsection{\1}','section{First}')
'subsection{First}'
>>> p.sub(r'subsection{\g<1>}','section{First}')
'subsection{First}'
>>> p.sub(r'subsection{\g<name>}','section{First}')
'subsection{First}'
replacement также может быть функцией, что даёт ещё больше возможностей управления. Если replacement — функция, она вызывается для каждого неперекрывающегося совпадения с pattern. При каждом вызове функции передаётся аргумент объект совпадения; используя содержащуюся в нём информацию, функция может вычислить нужную строку замены и вернуть её.
В следующем примере функция замены преобразует десятичные числа в шестнадцатеричные:
>>> def hexrepl(match): ... "Return the hex string for a decimal number" ... value = int(match.group()) ... return hex(value) ... >>> p = re.compile(r'\d+') >>> p.sub(hexrepl, 'Call 65490 for printing, 49152 for user code.') 'Call 0xffd2 for printing, 0xc000 for user code.'
При использовании функции уровня модуля re.sub() шаблон передаётся первым аргументом. Шаблон можно передать в виде объекта или строки; если нужно указать флаги регулярного выражения, необходимо передать объект шаблона первым параметром либо использовать встроенные модификаторы в строке шаблона. Например, sub("(?i)b+", "x", "bbbb BBBB") возвращает 'x x'.
Распространённые проблемы
Регулярные выражения — мощный инструмент для решения некоторых задач, однако их поведение не всегда интуитивно понятно, и порой они работают не так, как вы могли бы ожидать. В этом разделе описаны некоторые наиболее распространённые подводные камни.
Используйте методы строк
Иногда использовать модуль re — ошибка. Если вы ищете фиксированную строку или один класс символов и не используете возможности re, такие как флаг IGNORECASE, то вся мощь регулярных выражений может быть не нужна. У строк есть несколько методов для выполнения операций с фиксированными строками, и обычно они работают намного быстрее, поскольку реализованы в виде небольшого цикла на C, оптимизированного для этой задачи, а не с помощью крупного универсального движка регулярных выражений.
Например, можно заменить одну фиксированную строку другой; скажем, заменить word на deed. Для этого, вероятно, подойдёт функция re.sub(), но рассмотрите также метод replace(). Учтите, что replace() также заменит word внутри слов, превратив swordfish в sdeedfish, но наивное регулярное выражение word поступило бы так же. (Чтобы не выполнять замену внутри слов, шаблон должен быть \bword\b, то есть требовать, чтобы по обе стороны от word находилась граница слова. Это выходит за рамки возможностей replace().)
Ещё одна распространённая задача — удаление всех вхождений одного символа из строки или его замена другим одиночным символом. Это можно сделать, например, с помощью re.sub('\n', ' ', S), однако метод translate() справляется с обеими задачами и работает быстрее любой операции с регулярными выражениями.
Иными словами, прежде чем обращаться к модулю re, подумайте, можно ли решить вашу задачу более быстрым и простым методом строк.
match() и search()
Функция match() проверяет, совпадает ли регулярное выражение с началом строки, тогда как search() просматривает строку в поисках совпадения. Важно помнить об этом различии. Помните: match() сообщает об успешном совпадении, только если оно начинается с позиции 0; если совпадение начинается не с нуля, match() о нём не сообщит.
>>> print(re.match('super', 'superstition').span())
(0, 5)
>>> print(re.match('super', 'insuperable'))
None
С другой стороны, search() просматривает строку и сообщает о первом найденном совпадении.
>>> print(re.search('super', 'superstition').span())
(0, 5)
>>> print(re.search('super', 'insuperable').span())
(2, 7)
Иногда может возникнуть соблазн продолжать использовать re.match(), просто добавив .* в начало регулярного выражения. Не поддавайтесь этому соблазну и вместо этого используйте re.search(). Компилятор регулярных выражений анализирует регулярные выражения, чтобы ускорить поиск совпадений. В ходе такого анализа определяется, каким должен быть первый символ совпадения; например, шаблон, начинающийся с Crow, должен совпадать с 'C'. Благодаря этому движок может быстро просматривать строку в поисках начального символа и пытаться проверить полное совпадение, только если найден 'C'.
Добавление .* отключает эту оптимизацию: приходится просматривать строку до конца, а затем возвращаться назад, чтобы найти совпадение для оставшейся части регулярного выражения. Вместо этого используйте re.search().
Жадные и нежадные квантификаторы
При повторении регулярного выражения, как в a*, оно старается захватить как можно большую часть шаблона. Это часто приводит к проблемам, когда вы пытаетесь найти пару сбалансированных разделителей, например угловые скобки вокруг HTML-тега. Наивный шаблон для поиска одного HTML-тега не работает из-за жадного поведения .*.
>>> s = '<html><head><title>Title</title>'
>>> len(s)
32
>>> print(re.match('<.*>', s).span())
(0, 32)
>>> print(re.match('<.*>', s).group())
<html><head><title>Title</title>
Регулярное выражение находит '<' в '<html>', а .* захватывает остаток строки. Однако в регулярном выражении остаётся ещё одна часть, и > не может найти совпадение в конце строки, поэтому движку регулярных выражений приходится возвращаться по одному символу за раз, пока он не найдёт совпадение для >. В итоге найденное совпадение начинается с '<' в '<html>' и заканчивается на '>' в '</title>', что не соответствует желаемому результату.
В этом случае решение состоит в использовании нежадных квантификаторов *?, +?, ?? или {m,n}?, которые находят как можно меньше текста. В приведённом выше примере '>' проверяется сразу после того, как срабатывает первое '<'. Если совпадения нет, движок продвигается на один символ и на каждом шаге снова проверяет '>'. В результате получается именно то, что нужно:
>>> print(re.match('<.*?>', s).group())
<html>
(Обратите внимание: разбирать HTML или XML с помощью регулярных выражений очень трудно. Простые шаблоны на скорую руку справятся с распространёнными случаями, но в HTML и XML есть особые случаи, которые нарушат работу очевидного регулярного выражения. К тому времени, когда вы напишете регулярное выражение, учитывающее все возможные случаи, шаблоны станут очень сложными. Для таких задач используйте модуль для разбора HTML или XML.)
Использование re.VERBOSE
Вы, вероятно, уже заметили, что регулярные выражения записываются очень компактно, но их не слишком легко читать. Регулярные выражения средней сложности могут превратиться в длинные последовательности обратных косых черт, скобок и метасимволов, из-за чего их трудно читать и понимать.
Для таких регулярных выражений при компиляции может быть полезно указать флаг re.VERBOSE, поскольку он позволяет сделать запись регулярного выражения более понятной.
Флаг re.VERBOSE имеет несколько эффектов. Пробельные символы в регулярном выражении, которые не находятся внутри класса символов, игнорируются. Это означает, что выражение dog | cat эквивалентно менее читаемому dog|cat, но [a b] по-прежнему будет находить символы 'a', 'b' или пробел. Кроме того, в регулярное выражение можно добавлять комментарии: комментарий начинается с символа # и продолжается до следующей новой строки. В сочетании с многострочными строками это позволяет записывать регулярные выражения более аккуратно:
pat = re.compile(r"""
\s* # Skip leading whitespace
(?P<header>[^:]+) # Header name
\s* : # Whitespace, and a colon
(?P<value>.*?) # The header's value -- *? used to
# lose the following trailing whitespace
\s*$ # Trailing whitespace to end-of-line
""", re.VERBOSE)
Это гораздо удобнее для чтения, чем:
pat = re.compile(r"\s*(?P<header>[^:]+)\s*:(?P<value>.*?)\s*$")
Обратная связь
Регулярные выражения — сложная тема. Помог ли этот документ вам разобраться в них? Были ли какие-либо неясные разделы или проблемы, которые здесь не рассмотрены? Если да, отправьте предложения по улучшению в систему отслеживания ошибок.
Самая полная книга о регулярных выражениях — почти наверняка «Mastering Regular Expressions» Джеффри Фридла, опубликованная издательством O’Reilly. К сожалению, в ней рассматриваются исключительно разновидности регулярных выражений Perl и Java, и нет никакого материала по Python, поэтому она не подойдёт в качестве справочника по программированию на Python. (В первом издании рассматривался уже удалённый модуль Python regex, который вряд ли будет вам полезен.) Попробуйте взять эту книгу в библиотеке.
© 2001 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.14/howto/regex.html