> **Источник:** https://python-all.ru/3.16/howto/regex.html
>
> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.

---

# HOWTO по регулярным выражениям

Аннотация

Этот документ – вводное руководство по использованию регулярных выражений в Python с модулем [`re`](https://python-all.ru/3.16/library/re.html#module-re). Оно даёт более плавное введение, чем соответствующий раздел в справочнике по библиотеке.

## Введение

Регулярные выражения (называемые RE, или regex, или regex-шаблоны) – это, по сути, крошечный, узкоспециализированный язык программирования, встроенный в Python и доступный через модуль [`re`](https://python-all.ru/3.16/library/re.html#module-re). С помощью этого небольшого языка вы задаёте правила для множества возможных строк, которые хотите сопоставить; это множество может содержать английские предложения, адреса электронной почты, команды TeX или что угодно. Затем можно задавать вопросы вроде «Соответствует ли эта строка шаблону?» или «Есть ли совпадение с шаблоном где-либо в этой строке?». С помощью RE можно также изменять строку или разделять её различными способами.

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

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

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

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

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

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

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

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

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

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

Первые метасимволы, которые мы рассмотрим, – это `[` и `]`. Они используются для определения символьного класса – набора символов, которые нужно сопоставить. Символы можно перечислить по отдельности, или указать диапазон символов, задав два символа и разделив их дефисом `'-'`. Например, `[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`](https://python-all.ru/3.16/library/unicodedata.html#module-unicodedata). Можно использовать более ограниченное определение `\w` в строковом шаблоне, указав флаг [`re.ASCII`](https://python-all.ru/3.16/library/re.html#re.ASCII) при компиляции регулярного выражения.

Следующий список специальных последовательностей неполон. Полный список последовательностей и расширенных определений классов для строковых шаблонов Unicode см. в последней части [Синтаксис регулярных выражений](https://python-all.ru/3.16/library/re.html#re-syntax) в справочнике стандартной библиотеки. В общем, версии 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`](https://python-all.ru/3.16/library/re.html#re.DOTALL)), в котором он будет соответствовать даже новой строке. `.` часто используется, когда нужно сопоставить «любой символ».

### Повторение

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

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

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

Простейший случай `{m}` совпадает с предыдущим элементом ровно *m* раз. Например, `a/{2}b` совпадёт только с `'a//b'`.

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

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

Теперь, когда мы рассмотрели некоторые простые регулярные выражения, как же их использовать в Python? Модуль [`re`](https://python-all.ru/3.16/library/re.html#module-re) предоставляет интерфейс к движку регулярных выражений, позволяя компилировать регулярные выражения в объекты и затем выполнять с ними сопоставление.

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

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

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

[`re.compile()`](https://python-all.ru/3.16/library/re.html#re.compile) также принимает необязательный аргумент *flags*, используемый для включения различных специальных возможностей и вариантов синтаксиса. Мы рассмотрим доступные настройки позже, а пока достаточно одного примера:

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

RE передаётся в [`re.compile()`](https://python-all.ru/3.16/library/re.html#re.compile) в виде строки. RE обрабатываются как строки, поскольку регулярные выражения не являются частью ядра языка Python, и для их записи не был создан специальный синтаксис. (Существуют приложения, которым RE вообще не нужны, поэтому нет необходимости раздувать спецификацию языка, включая их.) Вместо этого модуль [`re`](https://python-all.ru/3.16/library/re.html#module-re) – это просто модуль-расширение на C, поставляемый с Python, точно так же, как модули [`socket`](https://python-all.ru/3.16/library/socket.html#module-socket) или [`zlib`](https://python-all.ru/3.16/library/zlib.html#module-zlib).

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

### Чума обратных слешей

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

Допустим, нужно написать RE, которое бы совпадало со строкой `\section`, которая может встретиться в файле LaTeX. Чтобы понять, что написать в коде программы, начнём с желаемой строки для совпадения. Затем необходимо экранировать все обратные слеши и другие метасимволы, поставив перед ними обратную косую черту, в результате получится строка `\\section`. Итоговая строка, которую нужно передать в [`re.compile()`](https://python-all.ru/3.16/library/re.html#re.compile), должна быть `\\section`. Однако, чтобы представить это в виде строкового литерала Python, оба обратных слеша нужно экранировать *снова*.

| Символы | Этап |
| --- | --- |
| `\section` | Строка для совпадения |
| `\\section` | Экранированный обратный слеш для [`re.compile()`](https://python-all.ru/3.16/library/re.html#re.compile) |
| `"\\\\section"` | Экранированные обратные слеши для строкового литерала |

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

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

Кроме того, специальные escape-последовательности, которые допустимы в регулярных выражениях, но не допустимы как строковые литералы Python, теперь приводят к [`SyntaxWarning`](https://python-all.ru/3.16/library/exceptions.html#SyntaxWarning) и со временем станут [`SyntaxError`](https://python-all.ru/3.16/library/exceptions.html#SyntaxError), то есть последовательности будут недействительны, если не использовать сырые строки или экранирование обратных косых черт.

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

### Выполнение сопоставлений

Как только у вас есть объект, представляющий скомпилированное регулярное выражение, что с ним делать? Объекты Pattern имеют несколько методов и атрибутов. Здесь будут рассмотрены только самые важные; за полным списком обращайтесь к документации [`re`](https://python-all.ru/3.16/library/re.html#module-re).

| Метод / Атрибут | Назначение |
| --- | --- |
| `search()` | Сканирует строку в поисках любого места, где данное регулярное выражение совпадает. |
| `prefixmatch()` | Определяет, совпадает ли РВ с началом строки. Ранее назывался [match()](https://python-all.ru/3.16/library/re.html#prefixmatch-vs-match). |
| `findall()` | Находит все подстроки, где совпадает РВ, и возвращает их в виде списка. |
| `finditer()` | Находит все подстроки, где совпадает РВ, и возвращает их в виде [итератора](https://python-all.ru/3.16/glossary.html#term-iterator). |

[`search()`](https://python-all.ru/3.16/library/re.html#re.Pattern.search) и [`prefixmatch()`](https://python-all.ru/3.16/library/re.html#re.Pattern.prefixmatch) возвращают `None`, если совпадение не найдено. В случае успеха возвращается экземпляр [объекта совпадения](https://python-all.ru/3.16/library/re.html#match-objects), содержащий информацию о совпадении: где оно начинается и заканчивается, совпавшую подстроку и другое.

Это можно изучить, интерактивно экспериментируя с модулем [`re`](https://python-all.ru/3.16/library/re.html#module-re).

В данном HOWTO используется стандартный интерпретатор Python для примеров. Сначала запустите интерпретатор Python, импортируйте модуль [`re`](https://python-all.ru/3.16/library/re.html#module-re) и скомпилируйте регулярное выражение:

```python
>>> import re
>>> p = re.compile('[a-z]+')
>>> p
re.compile('[a-z]+')
```

Теперь можно попробовать сопоставить различные строки с РВ `[a-z]+`. Пустая строка не должна совпадать, поскольку `+` означает «одно или более повторений». [`search()`](https://python-all.ru/3.16/library/re.html#re.Pattern.search) в этом случае должен вернуть `None`, что заставит интерпретатор ничего не выводить. Можно явно напечатать результат `search()`, чтобы прояснить это.

```python
>>> p.search("")
>>> print(p.search(""))
None
```

Теперь попробуем на строке, которая должна совпадать, например `tempo`. В этом случае [`search()`](https://python-all.ru/3.16/library/re.html#re.Pattern.search) вернёт [объект совпадения](https://python-all.ru/3.16/library/re.html#match-objects), так что сохраните результат в переменную для последующего использования.

```python
>>> m = p.search('tempo')
>>> m
<re.Match object; span=(0, 5), match='tempo'>
```

Теперь можно запросить у [объекта совпадения](https://python-all.ru/3.16/library/re.html#match-objects) информацию о совпавшей строке. Экземпляры объекта совпадения также имеют несколько методов и атрибутов; наиболее важные:

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

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

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

[`group()`](https://python-all.ru/3.16/library/re.html#re.Match.group) возвращает подстроку, которая совпала с РВ. [`start()`](https://python-all.ru/3.16/library/re.html#re.Match.start) и [`end()`](https://python-all.ru/3.16/library/re.html#re.Match.end) возвращают начальный и конечный индекс совпадения. [`span()`](https://python-all.ru/3.16/library/re.html#re.Match.span) возвращает оба индекса в одном кортеже. Метод [`search()`](https://python-all.ru/3.16/library/re.html#re.Pattern.search) у шаблонов сканирует строку, поэтому совпадение может начинаться не с нуля. Однако метод [`prefixmatch()`](https://python-all.ru/3.16/library/re.html#re.Pattern.prefixmatch) проверяет только совпадение РВ с началом строки, так что `start()` в этом случае всегда будет равен нулю.

```python
>>> m = p.search('::: message'); print(m)
<re.Match object; span=(4, 11), match='message'>
>>> m.group()
'message'
>>> m.span()
(4, 11)
>>> print(p.prefixmatch('::: message'))
None
```

В реальных программах наиболее распространённый подход – сохранить [объект совпадения](https://python-all.ru/3.16/library/re.html#match-objects) в переменную, а затем проверить, был ли он `None`. Обычно это выглядит так:

```python
p = re.compile( ... )
m = p.search( 'string goes here' )
if m:
    print('Match found: ', m.group())
else:
    print('No match')
```

Два метода шаблонов возвращают все совпадения для шаблона. [`findall()`](https://python-all.ru/3.16/library/re.html#re.Pattern.findall) возвращает список совпавших строк:

```python
>>> p = re.compile(r'\d+')
>>> p.findall('12 drummers drumming, 11 pipers piping, 10 lords a-leaping')
['12', '11', '10']
```

Префикс `r`, делающий литерал сырой строкой, необходим в этом примере, потому что escape-последовательности в обычном «вареном» строковом литерале, которые не распознаются Python (в отличие от регулярных выражений), теперь приводят к [`SyntaxWarning`](https://python-all.ru/3.16/library/exceptions.html#SyntaxWarning) и со временем станут [`SyntaxError`](https://python-all.ru/3.16/library/exceptions.html#SyntaxError). См. [Проблему обратной косой черты](https://python-all.ru/3.16/howto/regex.html#the-backslash-plague).

[`findall()`](https://python-all.ru/3.16/library/re.html#re.Pattern.findall) должен создать весь список, прежде чем его можно будет вернуть как результат. Метод [`finditer()`](https://python-all.ru/3.16/library/re.html#re.Pattern.finditer) возвращает последовательность экземпляров [объектов совпадения](https://python-all.ru/3.16/library/re.html#match-objects) в виде [итератора](https://python-all.ru/3.16/glossary.html#term-iterator):

```python
>>> 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`](https://python-all.ru/3.16/library/re.html#module-re) также предоставляет функции верхнего уровня, называемые [`search()`](https://python-all.ru/3.16/library/re.html#re.search), [`prefixmatch()`](https://python-all.ru/3.16/library/re.html#re.prefixmatch), [`findall()`](https://python-all.ru/3.16/library/re.html#re.findall), [`sub()`](https://python-all.ru/3.16/library/re.html#re.sub) и так далее. Эти функции принимают те же аргументы, что и соответствующий метод шаблона, с добавлением строки РВ в качестве первого аргумента, и по-прежнему возвращают либо `None`, либо экземпляр [объекта совпадения](https://python-all.ru/3.16/library/re.html#match-objects).

```python
>>> print(re.prefixmatch(r'From\s+', 'Fromage amk'))
None
>>> re.prefixmatch(r'From\s+', 'From amk Thu May 14 19:12:10 1998')
<re.Match object; span=(0, 5), match='From '>
```

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

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

### Флаги компиляции

Флаги компиляции позволяют изменять некоторые аспекты работы регулярных выражений. Флаги доступны в модуле [`re`](https://python-all.ru/3.16/library/re.html#module-re) под двумя именами: длинным, например [`IGNORECASE`](https://python-all.ru/3.16/library/re.html#re.IGNORECASE), и коротким, однобуквенным, например [`I`](https://python-all.ru/3.16/library/re.html#re.I). (Для знакомых с модификаторами шаблонов Perl: однобуквенные формы используют те же буквы; например, короткая форма [`re.VERBOSE`](https://python-all.ru/3.16/library/re.html#re.VERBOSE) – это [`re.X`](https://python-all.ru/3.16/library/re.html#re.X).) Несколько флагов можно указать с помощью побитового ИЛИ; например, `re.I | re.M` устанавливает оба флага `I` и [`M`](https://python-all.ru/3.16/library/re.html#re.M).

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

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

#### `re.I`

#### `re.IGNORECASE`

Выполняет регистронезависимое сопоставление; классы символов и строковые литералы будут находить соответствия буквам без учёта регистра. Например, `[A-Z]` будет совпадать и со строчными буквами. Полное сопоставление Unicode также работает, если только флаг [`ASCII`](https://python-all.ru/3.16/library/re.html#re.ASCII) не отключает соответствие не-ASCII. Когда шаблоны Unicode `[a-z]` или `[A-Z]` используются в сочетании с флагом [`IGNORECASE`](https://python-all.ru/3.16/library/re.html#re.IGNORECASE), они будут совпадать с 52 ASCII-буквами и 4 дополнительными не-ASCII-буквами: ‘İ’ (U+0130, латинская заглавная буква I с точкой сверху), ‘ı’ (U+0131, латинская строчная буква без точки i), ‘ſ’ (U+017F, латинская строчная буква длинная s) и ‘K’ (U+212A, знак Кельвина). `Spam` будет совпадать с `'Spam'`, `'spam'`, `'spAM'` или `'ſpam'` (последнее совпадает только в режиме Unicode). Это приведение к нижнему регистру не учитывает текущую локаль; оно будет учитывать, если также установлен флаг [`LOCALE`](https://python-all.ru/3.16/library/re.html#re.LOCALE).

#### `re.L`

#### `re.LOCALE`

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

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

#### `re.M`

#### `re.MULTILINE`

(`^` и `$` ещё не были объяснены; они будут представлены в разделе [Другие метасимволы](https://python-all.ru/3.16/howto/regex.html#more-metacharacters).)

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

#### `re.S`

#### `re.DOTALL`

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

#### `re.A`

#### `re.ASCII`

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

#### `re.X`

#### `re.VERBOSE`

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

Например, вот RE, использующий [`re.VERBOSE`](https://python-all.ru/3.16/library/re.html#re.VERBOSE); посмотрите, насколько его легче читать.

```python
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 регулярное выражение выглядело бы так:

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

В примере выше автоматическая конкатенация строковых литералов Python использовалась, чтобы разбить RE на более мелкие части, но он всё равно сложнее для понимания, чем версия с [`re.VERBOSE`](https://python-all.ru/3.16/library/re.html#re.VERBOSE).

## Больше возможностей шаблонов

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

### Ещё метасимволы

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

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

**`|`**

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

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

**`^`**

Совпадение в начале строк. Если не установлен флаг [`MULTILINE`](https://python-all.ru/3.16/library/re.html#re.MULTILINE), соответствует только в начале строки. В режиме `MULTILINE` также совпадает сразу после каждого перевода строки внутри строки.

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

```python
>>> print(re.search('^From', 'From Here to Eternity'))
<re.Match object; span=(0, 4), match='From'>
>>> print(re.search('^From', 'Reciting From Memory'))
None
```

Чтобы сопоставить литерал `'^'`, используйте `\^`.

**`$`**

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

```python
>>> 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`](https://python-all.ru/3.16/library/re.html#re.MULTILINE) `\A` и `^` фактически одинаковы. В режиме `MULTILINE` они различаются: `\A` по-прежнему совпадает только в начале строки, а `^` может совпадать в любом месте строки после символа перевода строки.

**`\z`**

Совпадение только в конце строки.

**`\Z`**

То же, что и `\z`. Для совместимости со старыми версиями Python.

**`\b`**

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

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

```python
>>> 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.

```python
>>> 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`, совпадает только когда текущая позиция не находится на границе слова.

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

Часто требуется получить больше информации, чем просто – совпало RE или нет. Регулярные выражения часто используются для разбора строк с помощью RE, разделённого на несколько подгрупп, каждая из которых соответствует интересующему компоненту. Например, строка заголовка RFC-822 разделена на имя заголовка и значение, разделённые `':'`, так:

```text
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`.

```python
>>> p = re.compile('(ab)*')
>>> print(p.search('ababababab').span())
(0, 10)
```

Группы, обозначенные `'('`, `')'`, также захватывают начальный и конечный индексы сопоставленного текста; их можно получить, передав аргумент методам [`group()`](https://python-all.ru/3.16/library/re.html#re.Match.group), [`start()`](https://python-all.ru/3.16/library/re.html#re.Match.start), [`end()`](https://python-all.ru/3.16/library/re.html#re.Match.end) и [`span()`](https://python-all.ru/3.16/library/re.html#re.Match.span). Группы нумеруются начиная с 0. Группа 0 присутствует всегда; это всё RE, поэтому методы [объекта match](https://python-all.ru/3.16/library/re.html#match-objects) по умолчанию используют группу 0. Позже мы увидим, как задавать группы, которые не захватывают диапазон сопоставленного текста.

```python
>>> p = re.compile('(a)b')
>>> m = p.search('ab')
>>> m.group()
'ab'
>>> m.group(0)
'ab'
```

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

```python
>>> p = re.compile('(a(b)c)d')
>>> m = p.search('abcd')
>>> m.group(0)
'abcd'
>>> m.group(1)
'abc'
>>> m.group(2)
'b'
```

[`group()`](https://python-all.ru/3.16/library/re.html#re.Match.group) можно передать несколько номеров групп одновременно, и в этом случае она вернёт кортеж, содержащий соответствующие значения для этих групп.

```python
>>> m.group(2,1,2)
('b', 'abc', 'b')
```

Метод [`groups()`](https://python-all.ru/3.16/library/re.html#re.Match.groups) возвращает кортеж, содержащий строки всех подгрупп, от 1 до их количества.

```python
>>> m.groups()
('abc', 'b')
```

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

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

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

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

### Незахватывающие и именованные группы

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

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

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

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

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

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

```python
>>> m = re.search("([abc])+", "abc")
>>> m.groups()
('c',)
>>> m = re.search("(?:[abc])+", "abc")
>>> m.groups()
()
```

За исключением того, что нельзя извлечь содержимое того, что сопоставила группа, незахватывающая группа ведёт себя точно так же, как захватывающая: в неё можно поместить что угодно, повторить её с помощью метасимвола повторения, такого как `*`, и вложить в другие группы (захватывающие или незахватывающие). `(?:...)` особенно полезен при изменении существующего шаблона, поскольку можно добавлять новые группы, не меняя нумерацию всех остальных групп. Стоит упомянуть, что нет разницы в производительности поиска между захватывающими и незахватывающими группами; ни одна из форм не быстрее другой.

Более значимая возможность – именованные группы: вместо обращения по номерам группы можно ссылаться по имени.

Синтаксис именованной группы – одно из расширений, специфичных для Python: `(?P<name>...)`. *name* – это, очевидно, имя группы. Именованные группы ведут себя точно так же, как захватывающие, и дополнительно связывают имя с группой. Методы [объекта match](https://python-all.ru/3.16/library/re.html#match-objects), которые работают с захватывающими группами, принимают как целые числа, обозначающие группу по номеру, так и строки, содержащие имя нужной группы. Именованные группы по-прежнему имеют номера, поэтому информацию о группе можно получить двумя способами:

```python
>>> p = re.compile(r'(?P<word>\b\w+\b)')
>>> m = p.search( '(((( Lots of punctuation )))' )
>>> m.group('word')
'Lots'
>>> m.group(1)
'Lots'
```

Кроме того, можно получить именованные группы в виде словаря с помощью [`groupdict()`](https://python-all.ru/3.16/library/re.html#re.Match.groupdict):

```python
>>> m = re.search(r'(?P<first>\w+) (?P<last>\w+)', 'Jane Doe')
>>> m.groupdict()
{'first': 'Jane', 'last': 'Doe'}
```

Именованные группы удобны, потому что позволяют использовать легко запоминающиеся имена вместо запоминания номеров. Вот пример RE из модуля [`imaplib`](https://python-all.ru/3.16/library/imaplib.html#module-imaplib):

```python
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`:

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

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

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

**`(?=...)`**

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

**`(?!...)`**

Отрицательная опережающая проверка. Это противоположность положительной проверке; она успешна, если вложенное выражение *не* совпадает в текущей позиции строки.

Чтобы сделать это конкретным, рассмотрим случай, где опережающая проверка полезна. Рассмотрим простой шаблон для сопоставления имени файла и разделения его на базовое имя и расширение, разделённых точкой `.`. Например, в `news.rc` `news` – это базовое имя, а `rc` – расширение файла.

Шаблон для сопоставления этого довольно прост:

`.*[.].*$`

Обратите внимание, что `.` нужно обрабатывать особым образом, потому что это метасимвол, поэтому он помещён в символьный класс, чтобы сопоставлять только этот конкретный символ. Также обратите внимание на завершающий `$`; он добавлен, чтобы гарантировать, что вся оставшаяся часть строки входит в расширение. Это регулярное выражение сопоставляется с `foo.bar`, `autoexec.bat`, `sendmail.cf` и `printers.conf`.

Теперь усложним задачу: что, если нужно сопоставить имена файлов, расширение которых не равно `bat`? Некоторые неверные попытки:

`.*[.][^b].*$`

Первая попытка выше пытается исключить `bat`, требуя, чтобы первый символ расширения не был `b`. Это неправильно, потому что шаблон также не сопоставляется с `foo.bar`.

`.*[.]([^b]..|.[^a].|..[^t])$`

Выражение становится более запутанным, если попытаться исправить первое решение, потребовав совпадения одного из следующих случаев: первый символ расширения не `b`; второй символ не `a`; или третий символ не `t`. Это принимает `foo.bar` и отвергает `autoexec.bat`, но требует трёхбуквенного расширения и не принимает имя файла с двухбуквенным расширением, например `sendmail.cf`. Мы снова усложним шаблон, пытаясь это исправить.

`.*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$`

В третьей попытке вторая и третья буквы сделаны необязательными, чтобы разрешить сопоставление расширений короче трёх символов, например `sendmail.cf`.

Шаблон становится очень сложным, что затрудняет его чтение и понимание. Хуже того, если задача изменится и потребуется исключить как `bat`, так и `exe` в качестве расширений, шаблон станет ещё более запутанным и сложным.

Отрицательная опережающая проверка разрешает всю эту путаницу:

`.*[.](?!bat$)[^.]*$`

Отрицательная опережающая проверка означает: если выражение `bat` не совпадает в этой точке, то проверяется остальная часть шаблона; если `bat$` совпадает, весь шаблон завершится неудачей. Завершающий `$` нужен, чтобы гарантировать, что такие случаи, как `sample.batch`, где расширение только начинается с `bat`, будут разрешены. `[^.]*` обеспечивает работу шаблона, когда в имени файла несколько точек.

Исключить другое расширение файла теперь легко: просто добавьте его как альтернативу внутри проверки. Следующий шаблон исключает имена файлов, которые заканчиваются на `bat` или `exe`:

`.*[.](?!bat$|exe$)[^.]*$`

## Изменение строк

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

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

### Разбиение строк

Метод [`split()`](https://python-all.ru/3.16/library/re.html#re.Pattern.split) шаблона разделяет строку везде, где совпадает RE, возвращая список частей. Он похож на метод [`split()`](https://python-all.ru/3.16/library/stdtypes.html#str.split) строк, но предоставляет гораздо большую гибкость в разделителях, по которым можно разбивать; строковый `split()` поддерживает разбиение только по пробельным символам или по фиксированной строке. Как и следовало ожидать, существует также функция уровня модуля [`re.split()`](https://python-all.ru/3.16/library/re.html#re.split).

#### `.split(string[, maxsplit=0])`

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

Количество выполняемых разбиений можно ограничить, передав значение для *maxsplit*. Если *maxsplit* не равно нулю, будет выполнено не более *maxsplit* разбиений, а остаток строки возвращается как последний элемент списка. В следующем примере разделителем является любая последовательность неалфавитно-цифровых символов.

```python
>>> 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 используются захватывающие круглые скобки, то их значения также возвращаются как часть списка. Сравните следующие вызовы:

```python
>>> 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()`](https://python-all.ru/3.16/library/re.html#re.split) добавляет RE, используемый как первый аргумент, но в остальном идентична.

```python
>>> 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()`](https://python-all.ru/3.16/library/re.html#re.Pattern.sub) принимает значение замены, которое может быть строкой или функцией, и обрабатываемую строку.

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

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

Необязательный аргумент *count* – максимальное количество заменяемых вхождений шаблона; *count* должно быть неотрицательным целым числом. Значение по умолчанию 0 означает замену всех вхождений.

Вот простой пример использования метода [`sub()`](https://python-all.ru/3.16/library/re.html#re.Pattern.sub). Он заменяет названия цветов словом `colour`:

```python
>>> 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()`](https://python-all.ru/3.16/library/re.html#re.Pattern.subn) делает то же самое, но возвращает кортеж из двух элементов, содержащий новую строку и количество выполненных замен:

```python
>>> 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)
```

Пустые совпадения заменяются только тогда, когда они не примыкают к предыдущему пустому совпадению.

```python
>>> p = re.compile('x*')
>>> p.sub('-', 'abxd')
'-a-b--d-'
```

Если *replacement* является строкой, все escape-последовательности с обратной косой чертой обрабатываются. То есть `\n` преобразуется в символ новой строки, `\r` – в возврат каретки и так далее. Неизвестные escape-последовательности, такие как `\&`, остаются без изменений. Обратные ссылки, например `\6`, заменяются подстрокой, совпавшей с соответствующей группой в регулярном выражении. Это позволяет включать части исходного текста в результирующую строку замены.

В этом примере ищется слово `section`, за которым следует строка, заключённая в `{`, `}`, и `section` заменяется на `subsection`:

```python
>>> 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'`.) Следующие подстановки эквивалентны, но используют все три варианта строки замены.

```python
>>> 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*. При каждом вызове функции передаётся аргумент [объект совпадения](https://python-all.ru/3.16/library/re.html#match-objects) и она может использовать эту информацию для вычисления нужной строки замены и возврата её.

В следующем примере функция замены преобразует десятичные числа в шестнадцатеричные:

```python
>>> 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()`](https://python-all.ru/3.16/library/re.html#re.sub) шаблон передаётся как первый аргумент. Шаблон может быть предоставлен как объект или как строка; если нужно указать флаги регулярного выражения, необходимо либо использовать объект шаблона в качестве первого параметра, либо использовать встроенные модификаторы в строке шаблона, например `sub("(?i)b+", "x", "bbbb BBBB")` возвращает `'x x'`.

## Распространённые проблемы

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

### Использование строковых методов

Иногда использование модуля [`re`](https://python-all.ru/3.16/library/re.html#module-re) является ошибкой. Если производится сопоставление с фиксированной строкой или одиночным классом символов и не используются никакие возможности `re`, такие как флаг [`IGNORECASE`](https://python-all.ru/3.16/library/re.html#re.IGNORECASE), то вся мощь регулярных выражений может не понадобиться. Строки имеют несколько методов для выполнения операций с фиксированными строками, и они обычно намного быстрее, поскольку реализация представляет собой один небольшой цикл на C, оптимизированный для этой цели, а не большой, более обобщённый движок регулярных выражений.

Примером может быть замена одной фиксированной строки на другую; например, можно заменить `word` на `deed`. [`re.sub()`](https://python-all.ru/3.16/library/re.html#re.sub) кажется подходящей функцией для этого, но рассмотрите метод [`replace()`](https://python-all.ru/3.16/library/stdtypes.html#str.replace). Обратите внимание, что `replace()` также заменит `word` внутри слов, превратив `swordfish` в `sdeedfish`, но наивное RE `word` сделало бы то же самое. (Чтобы избежать замены в частях слов, шаблон должен быть `\bword\b`, чтобы требовать, чтобы `word` имело границу слова с обеих сторон. Это выходит за рамки возможностей `replace()`.)

Ещё одна распространённая задача – удаление всех вхождений одного символа из строки или замена его другим символом. Можно сделать это с помощью чего-то вроде `re.sub('\n', ' ', S)`, но [`translate()`](https://python-all.ru/3.16/library/stdtypes.html#str.translate) способен выполнить обе задачи и будет быстрее любой операции с регулярными выражениями.

Короче говоря, прежде чем обращаться к модулю [`re`](https://python-all.ru/3.16/library/re.html#module-re), подумайте, не решается ли ваша задача более быстрым и простым строковым методом.

### prefixmatch() (также известный как match) и search()

[`prefixmatch()`](https://python-all.ru/3.16/library/re.html#re.prefixmatch) был добавлен в Python 3.15 как [предпочтительное имя](https://python-all.ru/3.16/library/re.html#prefixmatch-vs-match) для [`match()`](https://python-all.ru/3.16/library/re.html#re.match). До этого он был известен только как `match()`, и различие с [`search()`](https://python-all.ru/3.16/library/re.html#re.search) часто неправильно понимали.

`prefixmatch()` (также `match()`) проверяет только, совпадает ли RE в начале строки, тогда как `search()` просматривает строку вперёд в поисках совпадения.

```python
>>> print(re.prefixmatch('super', 'superstition').span())
(0, 5)
>>> print(re.prefixmatch('super', 'insuperable'))
None
```

С другой стороны, [`search()`](https://python-all.ru/3.16/library/re.html#re.search) просматривает строку вперёд, сообщая о первом найденном совпадении.

```python
>>> print(re.search('super', 'superstition').span())
(0, 5)
>>> print(re.search('super', 'insuperable').span())
(2, 7)
```

Это различие важно помнить при использовании старого имени [`match()`](https://python-all.ru/3.16/library/re.html#re.match) в коде, требующем совместимости со старыми версиями Python.

### Жадные и нежадные квантификаторы

При повторении регулярного выражения, как в `a*`, результатом является потребление как можно большей части шаблона. Этот факт часто подводит, когда пытаются сопоставить пару сбалансированных разделителей, например, угловые скобки, окружающие HTML-тег. Наивный шаблон для сопоставления одного HTML-тега не работает из-за жадной природы `.*`.

```python
>>> s = '<html><head><title>Title</title>'
>>> len(s)
32
>>> print(re.prefixmatch('<.*>', s).span())
(0, 32)
>>> print(re.prefixmatch('<.*>', s).group())
<html><head><title>Title</title>
```

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

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

```python
>>> print(re.prefixmatch('<.*?>', s).group())
<html>
```

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

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

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

Для таких RE указание флага [`re.VERBOSE`](https://python-all.ru/3.16/library/re.html#re.VERBOSE) при компиляции регулярного выражения может быть полезно, поскольку позволяет форматировать регулярное выражение более чётко.

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

```python
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)
```

Это гораздо читаемее, чем:

```python
pat = re.compile(r"\s*(?P<header>[^:]+)\s*:(?P<value>.*?)\s*$")
```

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

Регулярные выражения – сложная тема. Помог ли вам этот документ понять их? Были ли неясные части или проблемы, которые здесь не были рассмотрены? Если да, пожалуйста, отправьте предложения по улучшению в [трекер проблем](https://python-all.ru/3.16/bugs.html#using-the-tracker).

Наиболее полной книгой по регулярным выражениям, пожалуй, является \*Mastering Regular Expressions\* Джеффри Фридла, издательство O’Reilly. К сожалению, она сосредоточена исключительно на разновидностях регулярных выражений Perl и Java и вообще не содержит материала по Python, так что она не пригодится в качестве справочника по программированию на Python. (В первом издании рассматривался модуль `regex`, который теперь удалён из Python, что вам не сильно поможет.) Рекомендуем взять её в библиотеке.
