regex.md
1> **Источник:** https://python-all.ru/3.2/howto/regex.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# Регулярные выражения: руководство89| Автор: | A.M. Kuchling \<[amk@amk.ca](https://python-all.ru/3.2/howto/regex.html)\> |10| --- | --- |1112Аннотация1314Этот документ представляет собой вводное руководство по использованию регулярных выражений в Python с модулем [`re`](https://python-all.ru/3.2/library/re.html#module-re). Он даёт более мягкое введение, чем соответствующий раздел в справочнике библиотеки.1516## Введение1718Регулярные выражения (называемые RE, regex или regex-шаблоны) – это по сути крошечный, узкоспециализированный язык программирования, встроенный в Python и доступный через модуль [`re`](https://python-all.ru/3.2/library/re.html#module-re). С помощью этого маленького языка задаются правила для множества возможных строк, которые требуется сопоставить; это множество может содержать английские предложения, адреса электронной почты, команды TeX или что угодно. Затем можно задавать вопросы: «Соответствует ли эта строка шаблону?», или «Есть ли совпадение с шаблоном где-нибудь в этой строке?». Также можно использовать RE для изменения строки или её разделения различными способами.1920Шаблоны регулярных выражений компилируются в набор байткодов, которые затем выполняются механизмом сопоставления, написанным на C. Для продвинутого использования может потребоваться тщательно следить за тем, как механизм будет выполнять данное RE, и писать RE определённым образом, чтобы получить байткод, работающий быстрее. Оптимизация не рассматривается в этом документе, поскольку она требует хорошего понимания внутреннего устройства механизма сопоставления.2122Язык регулярных выражений относительно мал и ограничен, поэтому не все задачи обработки строк можно решить с помощью регулярных выражений. Существуют также задачи, которые *можно* выполнить с помощью регулярных выражений, но выражения оказываются очень сложными. В таких случаях, возможно, лучше написать код на Python для обработки; хотя код на Python будет медленнее, чем сложное регулярное выражение, он, вероятно, будет более понятным.2324## Простые шаблоны2526Начнём с изучения самых простых регулярных выражений. Поскольку регулярные выражения используются для работы со строками, начнём с самой распространённой задачи: сопоставления символов.2728Подробное объяснение теоретических основ регулярных выражений (детерминированные и недетерминированные конечные автоматы) можно найти почти в любом учебнике по написанию компиляторов.2930### Сопоставление символов3132Большинство букв и символов просто совпадают сами с собой. Например, регулярное выражение `test` точно совпадёт со строкой `test`. (Можно включить режим без учёта регистра, который позволит этому RE совпадать с `Test` или `TEST` ; подробнее об этом позже.)3334Из этого правила есть исключения: некоторые символы являются специальными *метасимволами* и не сопоставляются сами с собой. Вместо этого они указывают, что должно быть сопоставлено что-то необычное, или влияют на другие части РВ, повторяя их или меняя их значение. Значительная часть этого документа посвящена обсуждению различных метасимволов и их действий.3536Вот полный список метасимволов; их значения будут рассмотрены в остальной части этого HOWTO.3738```python39. ^ $ * + ? { } [ ] \ | ( )40```4142Первые метасимволы, которые мы рассмотрим, – это `[` и `]`. Они используются для указания класса символов – набора символов, которые требуется сопоставить. Символы можно перечислить по отдельности, или же диапазон символов можно указать, задав два символа и разделив их `'-'`. Например, `[abc]` совпадёт с любым из символов `a`, `b` или `c`; это то же самое, что `[a-c]`, где используется диапазон для задания того же набора символов. Если требуется сопоставить только строчные буквы, RE будет `[a-z]`.4344Внутри классов метасимволы не активны. Например, `[akm$]` совпадёт с любым из символов `'a'`, `'k'`, `'m'` или `'$'`; обычно `'$'` является метасимволом, но внутри класса символов он теряет свою особую природу.4546Вы можете сопоставить символы, не перечисленные в классе, *дополнив* набор. Это обозначается включением `'^'` в качестве первого символа класса; `'^'` вне символьного класса просто соответствует `'^'` символу. Например, `[^5]` будет соответствовать любому символу, кроме `'5'`.4748Пожалуй, самый важный метасимвол – обратная косая черта, `\`. Как и в строковых литералах Python, за обратной косой чертой могут следовать различные символы для обозначения специальных последовательностей. Она также используется для экранирования всех метасимволов, чтобы их можно было сопоставлять в шаблонах; например, если нужно сопоставить `[` или `\`, можно поставить перед ними обратную косую черту, чтобы убрать их специальное значение: `\[` или `\\`.4950Некоторые специальные последовательности, начинающиеся с `'\'`, представляют предопределённые наборы символов, которые часто бывают полезны: например, набор цифр, набор букв или набор всех символов, не являющихся пробельными. Следующие предопределённые специальные последовательности – подмножество доступных. Эквивалентные классы предназначены для шаблонов в байтах. Полный список последовательностей и расширенных определений классов для строковых шаблонов Unicode см. в последней части [*Regular Expression Syntax*](https://python-all.ru/3.2/library/re.html#re-syntax).5152**`\d`**5354Соответствует любой десятичной цифре; эквивалентно классу5556`[0-9]`5758.5960**`\D`**6162Соответствует любому нецифровому символу; эквивалентно классу6364`[^0-9]`6566.6768**`\s`**6970Совпадает с любым пробельным символом; это эквивалентно классу7172`[ \t\n\r\f\v]`7374.7576**`\S`**7778Совпадает с любым непробельным символом; это эквивалентно классу7980`[^ \t\n\r\f\v]`8182.8384**`\w`**8586Совпадает с любым буквенно-цифровым символом; это эквивалентно классу8788`[a-zA-Z0-9_]`8990.9192**`\W`**9394Совпадает с любым не буквенно-цифровым символом; это эквивалентно классу9596`[^a-zA-Z0-9_]`9798.99100Эти последовательности можно включать внутрь класса символов. Например, `[\s,.]` – это класс символов, который совпадёт с любым пробельным символом, или `','`, или `'.'`.101102Последний метасимвол в этом разделе – `.`. Он совпадает с любым символом, кроме символа новой строки, и существует альтернативный режим (`re.DOTALL`), в котором он будет совпадать даже с символом новой строки. `'.'` часто используется, когда требуется сопоставить «любой символ».103104### Повторяющиеся элементы105106Возможность сопоставлять различные наборы символов – это первое, что регулярные выражения умеют делать, чего уже нельзя сделать с помощью методов строк. Однако если бы это была единственная дополнительная возможность регулярных выражений, они не были бы большим достижением. Ещё одна возможность – указывать, что части РВ должны повторяться определённое количество раз.107108Первый метасимвол для повторения, который мы рассмотрим, – это `*`. `*` не соответствует литеральному символу `*`; вместо этого он указывает, что предыдущий символ может быть сопоставлен ноль или более раз, а не ровно один раз.109110Например, `ca*t` найдет совпадение с `ct` (0 символов `a`), `cat` (1 символ `a`), `caaat` (3 символа `a`) и так далее. Движок регулярных выражений имеет различные внутренние ограничения, связанные с размером типа `int` в C, которые не позволят найти совпадение с более чем 2 миллиардами символов `a`; для построения такой длинной строки вряд ли хватит памяти, поэтому с этим ограничением вы не столкнетесь.111112Повторы, такие как `*`, являются *жадными*; при повторении RE движок сопоставления будет пытаться повторить его максимально возможное количество раз. Если последующие части шаблона не совпадают, движок сопоставления отступает назад и пробует снова с меньшим числом повторов.113114Пошаговый пример прояснит это. Рассмотрим выражение `a[bcd]*b`. Оно совпадает с буквой `'a'`, нулём или более букв из класса `[bcd]` и, наконец, заканчивается `'b'`. Представим сопоставление этого RE со строкой `abcbd`.115116| Шаг | Совпадение | Пояснение |117| --- | --- | --- |118| 1 | `a` | `a` в RE совпадает. |119| 2 | `abcbd` | Движок сопоставляет `[bcd]*`, заходя так далеко, как может, то есть до конца строки. |120| 3 | *Неудача* | Движок пытается сопоставить `b`, но текущая позиция находится в конце строки, поэтому это не удаётся. |121| 4 | `abcb` | Отступить, чтобы `[bcd]*` совпадал на один символ меньше. |122| 5 | *Неудача* | Попробовать `b` снова, но текущая позиция находится на последнем символе, которым является `'d'`. |123| 6 | `abc` | Снова отступить, чтобы `[bcd]*` совпадал только с `bc`. |124| 6 | `abcb` | Попробуйте `b` снова. На этот раз символ в текущей позиции – `'b'`, поэтому он совпадает. |125126Достигнут конец РВ, и оно совпало с `abcb`. Это показывает, как движок сопоставления сначала проходит максимально далеко, и если совпадение не найдено, он постепенно отступает и снова и снова пробует остаток РВ. Он будет отступать, пока не попробует ноль совпадений для `[bcd]*`, и если это в итоге не удаётся, движок делает вывод, что строка вообще не соответствует РВ.127128Ещё один повторяющийся метасимвол – `+`, который совпадает один или более раз. Обратите внимание на разницу между `*` и `+`; `*` совпадает *ноль* или более раз, поэтому повторяемый элемент может вообще отсутствовать, в то время как `+` требует как минимум *одного* вхождения. Для аналогичного примера, `ca+t` совпадёт с `cat` (1 `a`), `caaat` (3 `a`), но не совпадёт с `ct`.129130Есть ещё два повторяющихся квантификатора. Символ вопросительного знака, `?`, совпадает один раз или ноль раз; можно считать, что он помечает что-то как необязательное. Например, `home-?brew` совпадёт либо с `homebrew`, либо с `home-brew`.131132Самый сложный повторяющийся квантификатор – `{m,n}`, где *m* и *n* – десятичные целые числа. Этот квантификатор означает, что должно быть как минимум *m* повторений и не более *n*. Например, `a/{1,3}b` совпадёт с `a/b`, `a//b` и `a///b`. Он не совпадёт с `ab` (без слэшей) или с `a////b` (четыре слэша).133134Можно опустить либо *m*, либо *n*; в этом случае подразумевается разумное значение для отсутствующего параметра. Пропуск *m* интерпретируется как нижняя граница 0, а пропуск *n* приводит к верхней границе бесконечности – на самом деле верхняя граница – это упомянутый ранее предел в 2 миллиарда, но её можно считать бесконечностью.135136Можно заметить, что три остальных квантификатора можно выразить через эту нотацию. `{0,}` – то же, что `*`, `{1,}` эквивалентен `+`, а `{0,1}` – то же, что `?`. Лучше использовать `*`, `+` или `?`, когда это возможно, просто потому что они короче и легче читаются.137138## Использование регулярных выражений139140Теперь, когда мы рассмотрели несколько простых регулярных выражений, как же их использовать в Python? Модуль [`re`](https://python-all.ru/3.2/library/re.html#module-re) предоставляет интерфейс к движку регулярных выражений, позволяя компилировать РВ в объекты и затем выполнять с ними сопоставление.141142### Компиляция регулярных выражений143144Регулярные выражения компилируются в объекты шаблонов, которые имеют методы для различных операций, таких как поиск совпадений с шаблоном или выполнение подстановок в строке.145146```python147>>> import re148>>> p = re.compile('ab*')149>>> p 150<_sre.SRE_Pattern object at 0x...>151```152153[`re.compile()`](https://python-all.ru/3.2/library/re.html#re.compile) также принимает необязательный аргумент *flags*, используемый для включения различных специальных возможностей и синтаксических вариаций. Мы рассмотрим доступные настройки позже, а пока достаточно одного примера:154155```python156>>> p = re.compile('ab*', re.IGNORECASE)157```158159РВ передаётся в [`re.compile()`](https://python-all.ru/3.2/library/re.html#re.compile) в виде строки. РВ обрабатываются как строки, потому что регулярные выражения не являются частью основного языка Python, и для их записи не было создано специального синтаксиса. (Есть приложения, которым РВ вообще не нужны, так что нет необходимости раздувать спецификацию языка, включая их.) Вместо этого модуль [`re`](https://python-all.ru/3.2/library/re.html#module-re) – это просто модуль-расширение на C, входящий в состав Python, как и модули [`socket`](https://python-all.ru/3.2/library/socket.html#module-socket) или [`zlib`](https://python-all.ru/3.2/library/zlib.html#module-zlib).160161Размещение RE в строках сохраняет язык Python более простым, но имеет один недостаток, который является темой следующего раздела.162163### Проблема обратной косой черты164165Как было сказано ранее, регулярные выражения используют символ обратной косой черты (`'\'`) для обозначения специальных форм или для использования специальных символов без их специального значения. Это конфликтует с использованием того же символа в строковых литералах Python для той же цели.166167Допустим, нужно написать РВ, которое совпадает со строкой `\section`, которая может встречаться в файле LaTeX. Чтобы понять, что писать в коде программы, начните с нужной строки для сопоставления. Затем нужно экранировать все обратные слэши и другие метасимволы, предваряя их обратным слэшем, в результате получится строка `\\section`. Результирующая строка, которую нужно передать в [`re.compile()`](https://python-all.ru/3.2/library/re.html#re.compile), должна быть `\\section`. Однако, чтобы выразить это в виде строкового литерала Python, оба обратных слэша необходимо экранировать *снова*.168169| Символы | Этап |170| --- | --- |171| `\section` | Строка для совпадения |172| `\\section` | Экранированный обратный слэш для [`re.compile()`](https://python-all.ru/3.2/library/re.html#re.compile) |173| `"\\\\section"` | Экранированные обратные слеши для строкового литерала |174175Короче говоря, для сопоставления с литералом обратной косой черты нужно записать `'\\\\'` в качестве строки RE, потому что регулярное выражение должно быть `\\`, а каждый обратный слеш должен быть представлен как `\\` внутри обычного строкового литерала Python. В RE, где обратные слеши встречаются многократно, это приводит к множеству повторных слешей и делает итоговые строки трудными для понимания.176177Решение – использовать в Python сырые строки для регулярных выражений; обратные слэши не обрабатываются особым образом в строковом литерале с префиксом `'r'`, поэтому `r"\n"` – это двухсимвольная строка, содержащая `'\'` и `'n'`, в то время как `"\n"` – односимвольная строка, содержащая символ новой строки. Регулярные выражения часто записываются в коде Python с использованием этой нотации сырых строк.178179| Обычная строка | Сырая строка |180| --- | --- |181| `"ab*"` | `r"ab*"` |182| `"\\\\section"` | `r"\\section"` |183| `"\\w+\\s+\\1"` | `r"\w+\s+\1"` |184185### Выполнение поиска совпадений186187Когда у вас есть объект, представляющий скомпилированное регулярное выражение, что с ним делать? Объекты-шаблоны имеют несколько методов и атрибутов. Здесь будут рассмотрены только самые важные; обратитесь к документации [`re`](https://python-all.ru/3.2/library/re.html#module-re) за полным списком.188189| Метод/Атрибут | Назначение |190| --- | --- |191| `match()` | Определяет, совпадает ли RE в начале строки. |192| `search()` | Сканирует строку в поисках любого места, где данное регулярное выражение совпадает. |193| `findall()` | Находит все подстроки, совпадающие с регулярным выражением, и возвращает их в виде списка. |194| `finditer()` | Find all substrings where the RE matches, and returns them as an [*iterator*](https://python-all.ru/3.2/glossary.html#term-iterator). |195196`match()` и `search()` возвращают `None`, если совпадение не найдено. Если совпадение найдено, возвращается экземпляр [*match object*](https://python-all.ru/3.2/library/re.html#match-objects), содержащий информацию о совпадении: где оно начинается и заканчивается, подстроку, с которой произошло совпадение, и многое другое.197198Вы можете узнать об этом, интерактивно экспериментируя с модулем [`re`](https://python-all.ru/3.2/library/re.html#module-re). Если у вас есть [`tkinter`](https://python-all.ru/3.2/library/tkinter.html#module-tkinter), возможно, вы также захотите посмотреть на [Tools/demo/redemo.py](https://python-all.ru/src/3.2/Tools/demo/redemo.py) – демонстрационную программу, входящую в состав дистрибутива Python. Она позволяет вводить РВ и строки и отображает, совпадает РВ или нет. `redemo.py` может быть весьма полезна при отладке сложного РВ. Phil Schwartz's [Kodos](https://python-all.ru/3.2/howto/regex.html) – также интерактивный инструмент для разработки и тестирования шаблонов РВ.199200В этом HOWTO используется стандартный интерпретатор Python для примеров. Сначала запустите интерпретатор Python, импортируйте модуль [`re`](https://python-all.ru/3.2/library/re.html#module-re) и скомпилируйте РВ:201202```python203>>> import re204>>> p = re.compile('[a-z]+')205>>> p 206<_sre.SRE_Pattern object at 0x...>207```208209Теперь можно попробовать сопоставить различные строки с РВ `[a-z]+`. Пустая строка вообще не должна совпадать, так как `+` означает «одно или более повторений». `match()` в этом случае должна вернуть `None`, что приведёт к тому, что интерпретатор не выведет ничего. Можно явно напечатать результат `match()`, чтобы это стало очевидно.210211```python212>>> p.match("")213>>> print(p.match(""))214None215```216217Теперь попробуем на строке, которая должна совпасть, например `tempo`. В этом случае `match()` вернёт [*объект совпадения*](https://python-all.ru/3.2/library/re.html#match-objects), поэтому следует сохранить результат в переменной для дальнейшего использования.218219```python220>>> m = p.match('tempo')221>>> m 222<_sre.SRE_Match object at 0x...>223```224225Теперь можно запросить у [*объекта match*](https://python-all.ru/3.2/library/re.html#match-objects) информацию о соответствующей строке. [*Объекты match*](https://python-all.ru/3.2/library/re.html#match-objects) также имеют несколько методов и атрибутов; наиболее важные из них:226227| Метод/Атрибут | Назначение |228| --- | --- |229| `group()` | Возвращает строку, совпавшую с РВ |230| `start()` | Возвращает начальную позицию совпадения |231| `end()` | Возвращает конечную позицию совпадения |232| `span()` | Возвращает кортеж с (начало, конец) позициями совпадения |233234Применение этих методов быстро прояснит их смысл:235236```python237>>> m.group()238'tempo'239>>> m.start(), m.end()240(0, 5)241>>> m.span()242(0, 5)243```244245`group()` returns the substring that was matched by the RE. `start()` and `end()` return the starting and ending index of the match. `span()` returns both start and end indexes in a single tuple. Since the `match()` method only checks if the RE matches at the start of a string, `start()` will always be zero. However, the `search()` method of patterns scans through the string, so the match may not start at zero in that case.246247```python248>>> print(p.match('::: message'))249None250>>> m = p.search('::: message'); print(m) 251<_sre.SRE_Match object at 0x...>252>>> m.group()253'message'254>>> m.span()255(4, 11)256```257258В реальных программах чаще всего [*объект совпадения*](https://python-all.ru/3.2/library/re.html#match-objects) сохраняют в переменную, а затем проверяют, не равен ли он `None`. Обычно это выглядит так:259260```python261p = re.compile( ... )262m = p.match( 'string goes here' )263if m:264 print('Match found: ', m.group())265else:266 print('No match')267```268269Два метода шаблонов возвращают все совпадения для шаблона. `findall()` возвращает список совпадающих строк:270271```python272>>> p = re.compile('\d+')273>>> p.findall('12 drummers drumming, 11 pipers piping, 10 lords a-leaping')274['12', '11', '10']275```276277`findall()` должен создать весь список, прежде чем вернуть его в качестве результата. Метод `finditer()` возвращает последовательность объектов [*match object*](https://python-all.ru/3.2/library/re.html#match-objects) в виде [*итератора*](https://python-all.ru/3.2/glossary.html#term-iterator):278279```python280>>> iterator = p.finditer('12 drummers drumming, 11 ... 10 ...')281>>> iterator 282<callable_iterator object at 0x...>283>>> for match in iterator:284... print(match.span())285...286(0, 2)287(22, 24)288(29, 31)289```290291### Функции уровня модуля292293Необязательно создавать объект шаблона и вызывать его методы; модуль [`re`](https://python-all.ru/3.2/library/re.html#module-re) также предоставляет функции верхнего уровня: `match()`, `search()`, `findall()`, `sub()` и так далее. Эти функции принимают те же аргументы, что и соответствующий метод шаблона, с добавлением строки регулярного выражения в качестве первого аргумента, и по-прежнему возвращают либо `None`, либо экземпляр [*объекта совпадения*](https://python-all.ru/3.2/library/re.html#match-objects).294295```python296>>> print(re.match(r'From\s+', 'Fromage amk'))297None298>>> re.match(r'From\s+', 'From amk Thu May 14 19:12:10 1998') 299<_sre.SRE_Match object at 0x...>300```301302Внутри эти функции просто создают объект шаблона и вызывают на нём соответствующий метод. Они также сохраняют скомпилированный объект в кеше, поэтому последующие вызовы с тем же регулярным выражением выполняются быстрее.303304Стоит ли использовать эти функции уровня модуля или лучше получить шаблон и вызывать его методы самостоятельно? Этот выбор зависит от того, как часто используется регулярное выражение, и от вашего личного стиля кодирования. Если регулярное выражение используется только в одном месте кода, то функции модуля, вероятно, удобнее. Если программа содержит много регулярных выражений или использует одни и те же в нескольких местах, то, возможно, стоит собрать все определения в одном месте – в разделе кода, который компилирует все регулярные выражения заранее. В качестве примера из стандартной библиотеки приведём отрывок из ныне несуществующего модуля Python 2 `xmllib`:305306```python307ref = re.compile( ... )308entityref = re.compile( ... )309charref = re.compile( ... )310starttagopen = re.compile( ... )311```312313Я обычно предпочитаю работать со скомпилированным объектом, даже для одноразового использования, но мало кто будет таким же пуристом в этом вопросе, как я.314315### Флаги компиляции316317Флаги компиляции позволяют изменять некоторые аспекты работы регулярных выражений. Флаги доступны в модуле [`re`](https://python-all.ru/3.2/library/re.html#module-re) под двумя именами: длинным, например `IGNORECASE`, и коротким, однобуквенным, например `I`. (Если вы знакомы с модификаторами шаблонов в Perl, то однобуквенные формы используют те же буквы; например, короткая форма [`re.VERBOSE`](https://python-all.ru/3.2/library/re.html#re.VERBOSE) – это [`re.X`](https://python-all.ru/3.2/library/re.html#re.X).) Несколько флагов можно задать с помощью побитового ИЛИ: например, `re.I | re.M` устанавливает оба флага `I` и `M`.318319Ниже приведена таблица доступных флагов, а затем более подробное описание каждого из них.320321| Флаг | Значение |322| --- | --- |323| `DOTALL`, `S` | Заставляет `.` соответствовать любому символу, включая символы новой строки |324| `IGNORECASE`, `I` | Выполняет регистронезависимые сопоставления |325| `LOCALE`, `L` | Выполняет сопоставление с учётом локали |326| `MULTILINE`, `M` | Многострочное сопоставление, затрагивающее `^` и `$` |327| `VERBOSE`, `X` | Включает подробные (verbose) регулярные выражения, которые можно организовать более чисто и понятно. |328| `ASCII`, `A` | Заставляет несколько управляющих последовательностей, таких как `\\w`, `\\b`, `\\s` и `\\d`, сопоставляться только с ASCII-символами, обладающими соответствующим свойством. |329330#### `I`331332#### `IGNORECASE`333334Выполняет сопоставление без учёта регистра; классы символов и строковые литералы будут сопоставляться с буквами, игнорируя регистр. Например, `[A-Z]` будет также находить строчные буквы, а `Spam` будет соответствовать `Spam`, `spam` или `spAM`. Это приведение к нижнему регистру не учитывает текущую локаль; оно будет учитывать, если также установить флаг `LOCALE`.335336#### `L`337338#### `LOCALE`339340Делает `\w`, `\W`, `\b` и `\B` зависимыми от текущей локали.341342Локали – это возможность библиотеки C, предназначенная для написания программ, учитывающих языковые различия. Например, при обработке французского текста может понадобиться написать `\\w+` для поиска слов, но `\\w` соответствует только классу символов `[A-Za-z]`; он не будет находить `'é'` или `'ç'`. Если система настроена правильно и выбрана французская локаль, некоторые функции C сообщат программе, что `'é'` также следует считать буквой. Установка флага `LOCALE` при компиляции регулярного выражения заставит результирующий скомпилированный объект использовать эти функции C для `\\w`; это медленнее, но позволяет `\\w+` находить французские слова так, как и ожидается.343344#### `M`345346#### `MULTILINE`347348(`^` и `$` пока не объяснены; они будут представлены в разделе [*Дополнительные метасимволы*](https://python-all.ru/3.2/howto/regex.html#more-metacharacters).)349350Обычно `^` сопоставляется только в начале строки, а `$` – только в конце строки и непосредственно перед символом новой строки (если он есть) в конце строки. Когда указан этот флаг, `^` сопоставляется в начале строки и в начале каждой строки внутри строки, сразу после каждого символа новой строки. Аналогично, метасимвол `$` сопоставляется в конце строки и в конце каждой строки (непосредственно перед каждым символом новой строки).351352#### `S`353354#### `DOTALL`355356Заставляет специальный символ `'.'` сопоставляться с любым символом, включая символ новой строки; без этого флага `'.'` будет сопоставляться с любым символом, *кроме* символа новой строки.357358#### `A`359360#### `ASCII`361362Заставляет `\\w`, `\\W`, `\\b`, `\\B`, `\\s` и `\\S` выполнять сопоставление только по ASCII вместо полного сопоставления Unicode. Это имеет смысл только для шаблонов Unicode и игнорируется для байтовых шаблонов.363364#### `X`365366#### `VERBOSE`367368Этот флаг позволяет писать более читаемые регулярные выражения, предоставляя больше гибкости в их форматировании. Когда этот флаг указан, пробельные символы внутри строки регулярного выражения игнорируются, за исключением случаев, когда пробел находится внутри символьного класса или перед ним стоит неэкранированная обратная косая черта; это позволяет более четко организовывать и расставлять отступы в регулярном выражении. Этот флаг также позволяет помещать комментарии внутри регулярного выражения, которые будут игнорироваться движком; комментарии обозначаются символом `'#'`, который не находится внутри символьного класса и не предваряется неэкранированной обратной косой чертой.369370Например, вот RE, использующий [`re.VERBOSE`](https://python-all.ru/3.2/library/re.html#re.VERBOSE); видите, насколько его легче читать?371372```python373charref = re.compile(r"""374 &[#] # Start of a numeric entity reference375 (376 0[0-7]+ # Octal form377 | [0-9]+ # Decimal form378 | x[0-9a-fA-F]+ # Hexadecimal form379 )380 ; # Trailing semicolon381""", re.VERBOSE)382```383384Без режима verbose регулярное выражение выглядело бы так:385386```python387charref = re.compile("&#(0[0-7]+"388 "|[0-9]+"389 "|x[0-9a-fA-F]+);")390```391392В приведённом выше примере использовалась автоматическая конкатенация строковых литералов Python для разбиения RE на более мелкие части, но её по-прежнему сложнее понять, чем версию с [`re.VERBOSE`](https://python-all.ru/3.2/library/re.html#re.VERBOSE).393394## Дополнительные возможности шаблонов395396До сих пор мы рассмотрели только часть возможностей регулярных выражений. В этом разделе мы рассмотрим некоторые новые метасимволы и способы использования групп для извлечения частей сопоставленного текста.397398### Дополнительные метасимволы399400Остались некоторые метасимволы, которые мы ещё не рассмотрели. Большинство из них будет рассмотрено в этом разделе.401402Некоторые из оставшихся метасимволов, которые предстоит обсудить, – это *проверки с нулевой шириной* (zero-width assertions). Они не заставляют движок продвигаться по строке; вместо этого они вообще не потребляют символы и просто успешно проходят или нет. Например, `\\b` – это проверка того, что текущая позиция находится на границе слова; позиция `\\b` вообще не меняется. Это означает, что проверки с нулевой шириной никогда не следует повторять, потому что если они один раз совпадают в данной позиции, то, очевидно, могут совпадать бесконечное количество раз.403404**`|`**405406Альтернатива, или оператор «или». Если A и B – регулярные выражения, то `A|B` будет соответствовать любой строке, которая соответствует либо `A`, либо `B`. `|` имеет очень низкий приоритет, чтобы разумно работать при альтернативе многозначных строк. `Crow|Servo` будет соответствовать либо `Crow`, либо `Servo`, а не `Cro`, `'w'` или `'S'`, и `ervo`.407408Чтобы сопоставить литерал `'|'`, используйте `\|` или заключите его в символьный класс, как в `[|]`.409410**`^`**411412Соответствует началу строк. Если не установлен флаг `MULTILINE`, будет соответствовать только началу всей строки. В режиме `MULTILINE` также соответствует сразу после каждого символа новой строки внутри строки.413414Например, если нужно найти слово `From` только в начале строки, следует использовать RE `^From`.415416```python417>>> print(re.search('^From', 'From Here to Eternity')) 418<_sre.SRE_Match object at 0x...>419>>> print(re.search('^From', 'Reciting From Memory'))420None421```422423**`$`**424425Совпадение в конце строки, которая определяется как конец строки или любая позиция, за которой следует символ перевода строки.426427```python428>>> print(re.search('}$', '{block}')) 429<_sre.SRE_Match object at 0x...>430>>> print(re.search('}$', '{block} '))431None432>>> print(re.search('}$', '{block}\n')) 433<_sre.SRE_Match object at 0x...>434```435436Чтобы сопоставить литерал `'$'`, используйте `\$` или заключите его в символьный класс, как в `[$]`.437438**`\A`**439440Совпадает только в начале строки. Если не используется режим441442`MULTILINE`443444,445446`\A`447448и449450`^`451452по сути одно и то же. В режиме453454`MULTILINE`455456они различаются:457458`\A`459460по-прежнему совпадает только в начале строки, а461462`^`463464может совпадать в любом месте строки после символа новой строки.465466**`\Z`**467468Совпадение только в конце строки.469470**`\b`**471472Граница слова. Это утверждение нулевой длины, которое совпадает только в начале или конце слова. Слово определяется как последовательность буквенно-цифровых символов, поэтому конец слова обозначается пробелом или не-буквенно-цифровым символом.473474Следующий пример находит `class` только когда это целое слово; не будет находить, когда оно является частью другого слова.475476```python477>>> p = re.compile(r'\bclass\b')478>>> print(p.search('no class at all')) 479<_sre.SRE_Match object at 0x...>480>>> print(p.search('the declassified algorithm'))481None482>>> print(p.search('one subclass is'))483None484```485486При использовании этой специальной последовательности нужно помнить о двух тонкостях. Первая: это самое серьёзное столкновение между строковыми литералами Python и последовательностями регулярных выражений. В строковых литералах Python `\b` – это символ backspace (ASCII 8). Если не используются сырые строки, Python преобразует `\b` в backspace, и регулярное выражение будет работать не так, как ожидается. Следующий пример выглядит так же, как предыдущее регулярное выражение, но перед строкой RE нет `'r'`.487488```python489>>> p = re.compile('\bclass\b')490>>> print(p.search('no class at all'))491None492>>> print(p.search('\b' + 'class' + '\b')) 493<_sre.SRE_Match object at 0x...>494```495496Внутри символьного класса, где эта проверка не нужна, `\b` представляет символ backspace (для совместимости со строковыми литералами Python).497498**`\B`**499500Ещё одна проверка с нулевой длиной; противоположность501502`\b`503504. Совпадает, когда текущая позиция не находится на границе слова.505506### Группировка507508Часто требуется получить больше информации, чем просто – совпало RE или нет. Регулярные выражения часто используются для разбора строк с помощью RE, разделённого на несколько подгрупп, каждая из которых соответствует интересующему компоненту. Например, строка заголовка RFC-822 разделена на имя заголовка и значение, разделённые `':'`, так:509510```python511From: author@example.com512User-Agent: Thunderbird 1.5.0.9 (X11/20061227)513MIME-Version: 1.0514To: editor@example.com515```516517Это можно обработать, написав регулярное выражение, которое сопоставляет всю строку заголовка, и имеет одну группу для имени заголовка и другую – для значения заголовка.518519Группы выделяются метасимволами `'('` и `')'`. `'('` и `')'` имеют примерно тот же смысл, что и в математических выражениях: они группируют содержащиеся в них выражения, и содержимое группы можно повторять с помощью квантификаторов, таких как `*`, `+`, `?` или `{m,n}`. Например, `(ab)*` найдёт ноль или более повторений `ab`.520521```python522>>> p = re.compile('(ab)*')523>>> print(p.match('ababababab').span())524(0, 10)525```526527Группы, обозначенные `'('`, `')'`, также захватывают начальный и конечный индексы совпавшего текста; их можно получить, передав аргумент в `group()`, `start()`, `end()` и `span()`. Нумерация групп начинается с 0. Группа 0 присутствует всегда; это всё RE, поэтому методы [*объекта совпадения*](https://python-all.ru/3.2/library/re.html#match-objects) по умолчанию принимают группу 0. Позже мы увидим, как указать группы, не захватывающие диапазон совпавшего текста.528529```python530>>> p = re.compile('(a)b')531>>> m = p.match('ab')532>>> m.group()533'ab'534>>> m.group(0)535'ab'536```537538Подгруппы нумеруются слева направо, начиная с 1. Группы могут быть вложенными; чтобы определить номер, нужно просто посчитать открывающие скобки, идя слева направо.539540```python541>>> p = re.compile('(a(b)c)d')542>>> m = p.match('abcd')543>>> m.group(0)544'abcd'545>>> m.group(1)546'abc'547>>> m.group(2)548'b'549```550551`group()` можно передать несколько номеров групп одновременно; в этом случае он вернёт кортеж с соответствующими значениями для этих групп.552553```python554>>> m.group(2,1,2)555('b', 'abc', 'b')556```557558Метод `groups()` возвращает кортеж, содержащий строки для всех подгрупп – от 1 до их общего количества.559560```python561>>> m.groups()562('abc', 'b')563```564565Обратные ссылки в шаблоне позволяют указать, что содержимое ранее захваченной группы должно также присутствовать в текущей позиции строки. Например, `\1` будет успешным, если точное содержимое группы 1 можно найти в текущей позиции, иначе – нет. Помните, что строковые литералы Python также используют обратную косую черту с числами для включения произвольных символов в строку, поэтому обязательно используйте сырую строку при включении обратных ссылок в RE.566567Например, следующее RE обнаруживает удвоенные слова в строке.568569```python570>>> p = re.compile(r'(\b\w+)\s+\1')571>>> p.search('Paris in the the spring').group()572'the the'573```574575Обратные ссылки такого рода редко бывают полезны при простом поиске в строке – существует мало текстовых форматов, которые повторяют данные таким образом, – но вскоре обнаруживается, что они *очень* полезны при выполнении строковых подстановок.576577### Незахватывающие и именованные группы578579Сложные регулярные выражения могут использовать много групп как для захвата интересующих подстрок, так и для группировки и структурирования самого выражения. В сложных RE становится трудно отслеживать номера групп. Существуют две возможности, которые помогают решить эту проблему. Обе используют общий синтаксис расширений регулярных выражений, поэтому сначала рассмотрим его.580581Perl 5 добавил несколько дополнительных возможностей к стандартным регулярным выражениям, и модуль Python [`re`](https://python-all.ru/3.2/library/re.html#module-re) поддерживает большинство из них. Было бы сложно выбрать новые одноклавишные метасимволы или новые специальные последовательности, начинающиеся с `\`, для представления новых возможностей, не делая регулярные выражения Perl запутанно отличными от стандартных. Если бы вы выбрали `&` в качестве нового метасимвола, старые выражения предполагали бы, что `&` – это обычный символ, и не экранировали бы его, записывая `\&` или `[&]`.582583Разработчики Perl выбрали `(?...)` в качестве синтаксиса расширений. `?` сразу после открывающей скобки было синтаксической ошибкой, потому что `?` не к чему было бы применять повтор, так что никаких проблем совместимости это не вызвало. Символы сразу после `?` указывают, какое расширение используется: например, `(?=foo)` – одно (положительная опережающая проверка), а `(?:foo)` – другое (незахватывающая группа с подвыражением `foo`).584585Python добавляет синтаксис расширения к синтаксису расширения Perl. Если первый символ после вопросительного знака – `P`, значит, это расширение, специфичное для Python. В настоящее время существует два таких расширения: `(?P<name>...)` определяет именованную группу, а `(?P=name)` – обратная ссылка на именованную группу. Если будущие версии Perl 5 добавят подобные возможности с другим синтаксисом, модуль [`re`](https://python-all.ru/3.2/library/re.html#module-re) будет изменён для поддержки нового синтаксиса, сохраняя при этом синтаксис, специфичный для Python, ради совместимости.586587Теперь, когда мы рассмотрели общий синтаксис расширений, можно вернуться к возможностям, упрощающим работу с группами в сложных регулярных выражениях. Поскольку группы нумеруются слева направо, а сложное выражение может содержать много групп, отслеживать правильную нумерацию становится трудно. Модифицировать такое сложное регулярное выражение тоже утомительно: добавьте новую группу в начале, и номера всех последующих групп изменятся.588589Иногда нужно использовать группу, чтобы собрать часть регулярного выражения, но нет необходимости извлекать содержимое группы. Можно явно указать это, используя незахватывающую группу: `(?:...)`, где `...` можно заменить любым другим регулярным выражением.590591```python592>>> m = re.match("([abc])+", "abc")593>>> m.groups()594('c',)595>>> m = re.match("(?:[abc])+", "abc")596>>> m.groups()597()598```599600За исключением того, что нельзя извлечь содержимое того, что сопоставила группа, незахватывающая группа ведёт себя точно так же, как захватывающая: в неё можно поместить что угодно, повторить её с помощью метасимвола повторения, такого как `*`, и вложить в другие группы (захватывающие или незахватывающие). `(?:...)` особенно полезен при изменении существующего шаблона, поскольку можно добавлять новые группы, не меняя нумерацию всех остальных групп. Стоит упомянуть, что нет разницы в производительности поиска между захватывающими и незахватывающими группами; ни одна из форм не быстрее другой.601602Более значимая возможность – именованные группы: вместо обращения по номерам группы можно ссылаться по имени.603604Синтаксис для именованной группы – одно из расширений, специфичных для Python: `(?P<name>...)`. *name* – это, очевидно, имя группы. Именованные группы ведут себя точно так же, как захватывающие, и дополнительно связывают имя с группой. Методы [*объекта совпадения*](https://python-all.ru/3.2/library/re.html#match-objects), работающие с захватывающими группами, принимают как целые числа, ссылающиеся на группу по номеру, так и строки, содержащие имя нужной группы. Именованные группы по-прежнему получают номера, поэтому информацию о группе можно получить двумя способами:605606```python607>>> p = re.compile(r'(?P<word>\b\w+\b)')608>>> m = p.search( '(((( Lots of punctuation )))' )609>>> m.group('word')610'Lots'611>>> m.group(1)612'Lots'613```614615Именованные группы удобны, потому что позволяют использовать легко запоминающиеся имена вместо запоминания номеров. Вот пример регулярного выражения из модуля [`imaplib`](https://python-all.ru/3.2/library/imaplib.html#module-imaplib):616617```python618InternalDate = re.compile(r'INTERNALDATE "'619 r'(?P<day>[ 123][0-9])-(?P<mon>[A-Z][a-z][a-z])-'620 r'(?P<year>[0-9][0-9][0-9][0-9])'621 r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'622 r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'623 r'"')624```625626Очевидно, что получить `m.group('zonem')` гораздо проще, чем запоминать, что нужно получить группу 9.627628Синтаксис обратных ссылок в выражении, например `(...)\1`, относится к номеру группы. Существует вариант, использующий имя группы вместо номера. Это ещё одно расширение Python: `(?P=name)` означает, что содержимое группы с именем *name* должно снова совпадать в текущей позиции. Регулярное выражение для поиска повторяющихся слов, `(\b\w+)\s+\1`, можно также записать как `(?P<word>\b\w+)\s+(?P=word)`:629630```python631>>> p = re.compile(r'(?P<word>\b\w+)\s+(?P=word)')632>>> p.search('Paris in the the spring').group()633'the the'634```635636### Опережающие проверки637638Ещё одна проверка нулевой ширины – опережающая проверка. Опережающие проверки бывают положительными и отрицательными и выглядят так:639640**`(?=...)`**641642Положительная опережающая проверка. Она успешна, если вложенное регулярное выражение, представленное здесь как643644`...`645646, успешно совпадает в текущей позиции, и неуспешна в противном случае. Но как только вложенное выражение проверено, движок сопоставления не продвигается; остальная часть шаблона проверяется прямо с того места, где началась проверка.647648**`(?!...)`**649650Отрицательная опережающая проверка. Это противоположность положительной проверке; она успешна, если вложенное выражение651652*не*653654совпадает в текущей позиции строки.655656Чтобы стало понятнее, рассмотрим случай, где полезна опережающая проверка. Возьмём простой шаблон для сопоставления имени файла и разделения его на имя базы и расширение, разделённые точкой `.`. Например, в `news.rc` `news` – имя базы, а `rc` – расширение.657658Шаблон для сопоставления этого довольно прост:659660`.*[.].*$`661662Обратите внимание, что `.` нужно обрабатывать особым образом, потому что это метасимвол; я поместил его внутрь символьного класса. Также обратите внимание на завершающий `$`; он добавлен, чтобы гарантировать, что вся остальная часть строки должна быть включена в расширение. Это регулярное выражение совпадает с `foo.bar`, `autoexec.bat`, `sendmail.cf` и `printers.conf`.663664Теперь усложним задачу: нужно найти имена файлов, у которых расширение не равно `bat`. Несколько неправильных попыток:665666`.*[.][^b].*$` Первая попытка пытается исключить `bat`, требуя, чтобы первый символ расширения не был `b`. Это неправильно, потому что шаблон также не находит `foo.bar`.667668`.*[.]([^b]..|.[^a].|..[^t])$`669670Выражение становится всё запутаннее, когда мы пытаемся исправить первое решение, требуя, чтобы выполнялся один из следующих случаев: первый символ расширения не равен `b`; второй символ не равен `a`; или третий символ не равен `t`. Такой подход принимает `foo.bar` и отвергает `autoexec.bat`, но требует трёхбуквенного расширения и не будет принимать имена с двухбуквенным расширением, например `sendmail.cf`. Мы снова усложним шаблон, пытаясь это исправить.671672`.*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$`673674В третьей попытке вторая и третья буквы сделаны необязательными, чтобы допустить расширения короче трёх символов, например `sendmail.cf`.675676Шаблон становится очень сложным, что затрудняет его чтение и понимание. Хуже того, если задача изменится и нужно будет исключить как `bat`, так и `exe` в качестве расширений, шаблон станет ещё более запутанным.677678Отрицательная опережающая проверка разрешает всю эту путаницу:679680`.*[.](?!bat$).*$` Отрицательная опережающая проверка означает: если выражение `bat` не совпадает в этой точке, продолжать проверку по остальной части шаблона; если `bat$` совпадает, весь шаблон не сработает. Завершающая `$` необходима, чтобы гарантировать, что такие имена, как `sample.batch`, где расширение только начинается с `bat`, будут допущены.681682Исключить другое расширение теперь просто: достаточно добавить его как альтернативу внутри проверки. Следующий шаблон исключает имена файлов, оканчивающиеся на `bat` или `exe`:683684`.*[.](?!bat$|exe$).*$`685686## Изменение строк687688До этого момента мы просто выполняли поиск в статической строке. Регулярные выражения также часто используются для изменения строк различными способами с помощью следующих методов шаблона:689690| Метод/Атрибут | Назначение |691| --- | --- |692| `split()` | Разделяет строку на список, разбивая её везде, где совпадает RE |693| `sub()` | Находит все подстроки, совпадающие с RE, и заменяет их на другую строку |694| `subn()` | Делает то же самое, что и `sub()`, но возвращает новую строку и количество замен |695696### Разделение строк697698Метод `split()` шаблона разбивает строку на части везде, где совпадение с регулярным выражением, и возвращает список фрагментов. Он похож на `split()` для строк, но предоставляет гораздо больше возможностей для задания разделителей; `split()` поддерживает только разбиение по пробельным символам или по фиксированной строке. Как и следовало ожидать, существует также функция уровня модуля [`re.split()`](https://python-all.ru/3.2/library/re.html#re.split).699700#### `.split(string[, maxsplit=0])`701702Разделяет *строку* по совпадениям регулярного выражения. Если в RE используются захватывающие круглые скобки, то их содержимое также будет возвращено как часть результирующего списка. Если *maxsplit* не равно нулю, выполняется не более *maxsplit* разбиений.703704Количество выполняемых разбиений можно ограничить, передав значение для *maxsplit*. Если *maxsplit* не равно нулю, будет выполнено не более *maxsplit* разбиений, а остаток строки возвращается как последний элемент списка. В следующем примере разделителем является любая последовательность неалфавитно-цифровых символов.705706```python707>>> p = re.compile(r'\W+')708>>> p.split('This is a test, short and sweet, of split().')709['This', 'is', 'a', 'test', 'short', 'and', 'sweet', 'of', 'split', '']710>>> p.split('This is a test, short and sweet, of split().', 3)711['This', 'is', 'a', 'test, short and sweet, of split().']712```713714Иногда нужно знать не только текст между разделителями, но и сам разделитель. Если в RE используются захватывающие круглые скобки, то их значения также возвращаются как часть списка. Сравните следующие вызовы:715716```python717>>> p = re.compile(r'\W+')718>>> p2 = re.compile(r'(\W+)')719>>> p.split('This... is a test.')720['This', 'is', 'a', 'test', '']721>>> p2.split('This... is a test.')722['This', '... ', 'is', ' ', 'a', ' ', 'test', '.', '']723```724725Функция уровня модуля [`re.split()`](https://python-all.ru/3.2/library/re.html#re.split) добавляет регулярное выражение в качестве первого аргумента, но в остальном делает то же самое.726727```python728>>> re.split('[\W]+', 'Words, words, words.')729['Words', 'words', 'words', '']730>>> re.split('([\W]+)', 'Words, words, words.')731['Words', ', ', 'words', ', ', 'words', '.', '']732>>> re.split('[\W]+', 'Words, words, words.', 1)733['Words', 'words, words.']734```735736### Поиск и замена737738Еще одна распространенная задача – найти все совпадения с шаблоном и заменить их другой строкой. Метод `sub()` принимает значение замены, которое может быть строкой или функцией, и обрабатываемую строку.739740#### `.sub(replacement, string[, count=0])`741742Возвращает строку, полученную заменой самых левых непересекающихся совпадений RE в *строке* на замену *replacement*. Если шаблон не найден, *строка* возвращается без изменений.743744Необязательный аргумент *count* – максимальное количество заменяемых вхождений шаблона; *count* должно быть неотрицательным целым числом. Значение по умолчанию 0 означает замену всех вхождений.745746Вот простой пример использования метода `sub()`. Он заменяет названия цветов на слово `colour`:747748```python749>>> p = re.compile( '(blue|white|red)')750>>> p.sub( 'colour', 'blue socks and red shoes')751'colour socks and colour shoes'752>>> p.sub( 'colour', 'blue socks and red shoes', count=1)753'colour socks and red shoes'754```755756Метод `subn()` делает то же самое, но возвращает кортеж из двух элементов, содержащий новую строку и количество выполненных замен:757758```python759>>> p = re.compile( '(blue|white|red)')760>>> p.subn( 'colour', 'blue socks and red shoes')761('colour socks and colour shoes', 2)762>>> p.subn( 'colour', 'no colours at all')763('no colours at all', 0)764```765766Пустые совпадения заменяются только тогда, когда они не примыкают к предыдущему совпадению.767768```python769>>> p = re.compile('x*')770>>> p.sub('-', 'abxd')771'-a-b-d-'772```773774Если *replacement* – строка, все управляющие последовательности с обратной косой чертой в ней обрабатываются. То есть `\n` преобразуется в символ новой строки, `\r` – в символ возврата каретки и так далее. Неизвестные управляющие последовательности, такие как `\j`, остаются без изменений. Обратные ссылки, например `\6`, заменяются подстрокой, соответствующей группе в регулярном выражении. Это позволяет включать фрагменты исходного текста в результирующую строку замены.775776Этот пример находит слово `section`, за которым следует строка, заключенная в `{`, `}`, и заменяет `section` на `subsection`:777778```python779>>> p = re.compile('section{ ( [^}]* ) }', re.VERBOSE)780>>> p.sub(r'subsection{\1}','section{First} section{second}')781'subsection{First} subsection{second}'782```783784Существует также синтаксис для ссылки на именованные группы, определяемый синтаксисом `(?P<name>...)`. `\g<name>` использует подстроку, совпавшую с группой с именем `name`, а `\g<number>` – соответствующий номер группы. Таким образом, `\g<2>` эквивалентно `\2`, но не вызывает неоднозначности в строке замены, такой как `\g<2>0`. (`\20` было бы интерпретировано как ссылка на группу 20, а не как ссылка на группу 2, за которой следует литеральный символ `'0'`.) Следующие подстановки эквивалентны, но используют все три варианта строки замены.785786```python787>>> p = re.compile('section{ (?P<name> [^}]* ) }', re.VERBOSE)788>>> p.sub(r'subsection{\1}','section{First}')789'subsection{First}'790>>> p.sub(r'subsection{\g<1>}','section{First}')791'subsection{First}'792>>> p.sub(r'subsection{\g<name>}','section{First}')793'subsection{First}'794```795796*replacement* также может быть функцией, что даёт ещё больше контроля. Если *replacement* является функцией, она вызывается для каждого непересекающегося вхождения *pattern*. При каждом вызове функции передаётся аргумент [*объект совпадения*](https://python-all.ru/3.2/library/re.html#match-objects) и она может использовать эту информацию для вычисления нужной строки замены и возврата её.797798В следующем примере функция замены преобразует десятичные числа в шестнадцатеричные:799800```python801>>> def hexrepl(match):802... "Return the hex string for a decimal number"803... value = int(match.group())804... return hex(value)805...806>>> p = re.compile(r'\d+')807>>> p.sub(hexrepl, 'Call 65490 for printing, 49152 for user code.')808'Call 0xffd2 for printing, 0xc000 for user code.'809```810811При использовании функции уровня модуля [`re.sub()`](https://python-all.ru/3.2/library/re.html#re.sub) шаблон передается первым аргументом. Шаблон может быть предоставлен как объект или как строка; если нужно указать флаги регулярного выражения, необходимо либо использовать объект шаблона в качестве первого параметра, либо использовать встроенные модификаторы в строке шаблона, например, `sub("(?i)b+", "x", "bbbb BBBB")` возвращает `'x x'`.812813## Распространенные проблемы814815Регулярные выражения – мощный инструмент для некоторых приложений, но в некоторых аспектах их поведение не интуитивно и иногда они ведут себя не так, как можно было бы ожидать. В этом разделе будут указаны некоторые наиболее распространённые ошибки.816817### Применение строковых методов818819Иногда использование модуля [`re`](https://python-all.ru/3.2/library/re.html#module-re) является ошибкой. Если вы сопоставляете фиксированную строку или одиночный класс символов и не используете никаких возможностей [`re`](https://python-all.ru/3.2/library/re.html#module-re), таких как флаг `IGNORECASE`, то полная мощь регулярных выражений может не понадобиться. У строк есть несколько методов для выполнения операций с фиксированными строками, и они обычно гораздо быстрее, поскольку реализация представляет собой один небольшой цикл на C, оптимизированный для этой цели, вместо большого, более универсального движка регулярных выражений.820821Один из примеров – замена одной фиксированной строки на другую; например, можно заменить `word` на `deed`. `re.sub()` кажется подходящей функцией для этого, но рассмотрите метод `replace()`. Обратите внимание, что `replace()` также заменит `word` внутри слов, превратив `swordfish` в `sdeedfish`, но наивное RE `word` сделало бы то же самое. (Чтобы избежать замены в частях слов, шаблон должен быть `\bword\b`, чтобы требовалось наличие границы слова с обеих сторон от `word`. Это выходит за рамки возможностей `replace()`.)822823Еще одна распространенная задача – удаление всех вхождений одного символа из строки или замена его другим символом. Это можно сделать с помощью чего-то вроде `re.sub('\n', ' ', S)`, но `translate()` способен выполнить обе задачи и будет быстрее любой операции с регулярными выражениями.824825Короче говоря, прежде чем обращаться к модулю [`re`](https://python-all.ru/3.2/library/re.html#module-re), подумайте, можно ли решить вашу задачу с помощью более быстрого и простого строкового метода.826827### match() и search()828829Функция `match()` проверяет только, совпадает ли регулярное выражение в начале строки, тогда как `search()` будет сканировать строку вперед в поисках совпадения. Важно помнить об этом различии. Помните, `match()` сообщит об успешном совпадении только в том случае, если оно начинается с позиции 0; если совпадение не начинается с нуля, `match()` *не* сообщит о нем.830831```python832>>> print(re.match('super', 'superstition').span())833(0, 5)834>>> print(re.match('super', 'insuperable'))835None836```837838С другой стороны, `search()` будет сканировать строку вперед, сообщая о первом найденном совпадении.839840```python841>>> print(re.search('super', 'superstition').span())842(0, 5)843>>> print(re.search('super', 'insuperable').span())844(2, 7)845```846847Иногда возникает соблазн продолжать использовать [`re.match()`](https://python-all.ru/3.2/library/re.html#re.match) и просто добавить `.*` в начало регулярного выражения. Сопротивляйтесь этому искушению и используйте [`re.search()`](https://python-all.ru/3.2/library/re.html#re.search). Компилятор регулярных выражений выполняет некоторый анализ, чтобы ускорить поиск совпадения. Один из таких анализов определяет, каким должен быть первый символ совпадения; например, шаблон, начинающийся с `Crow`, должен начинаться с `'C'`. Этот анализ позволяет движку быстро просматривать строку в поисках начального символа, пробуя полное совпадение только в том случае, если найден `'C'`.848849Добавление `.*` сводит на нет эту оптимизацию, требуя сканирования до конца строки и последующего возврата для поиска совпадения с остальной частью регулярного выражения. Вместо этого используйте [`re.search()`](https://python-all.ru/3.2/library/re.html#re.search).850851### Жадный и нежадный режимы852853При повторении регулярного выражения, как в `a*`, результирующее действие – поглотить как можно больше шаблона. Этот факт часто подводит при попытке сопоставить пару сбалансированных разделителей, например угловые скобки, окружающие HTML-тег. Наивный шаблон для сопоставления одного HTML-тега не работает из-за жадной природы `.*`.854855```python856>>> s = '<html><head><title>Title</title>'857>>> len(s)85832859>>> print(re.match('<.*>', s).span())860(0, 32)861>>> print(re.match('<.*>', s).group())862<html><head><title>Title</title>863```864865Регулярное выражение находит `'<'` в `<html>`, и `.*` поглощает остальную часть строки. Однако в регулярном выражении еще осталась часть, и `>` не может совпасть в конце строки, поэтому движку регулярных выражений приходится возвращаться посимвольно, пока он не найдет совпадение для `>`. Итоговое совпадение простирается от `'<'` в `<html>` до `'>'` в `</title>`, что не является желаемым результатом.866867В этом случае решение – использовать нежадные квантификаторы `*?`, `+?`, `??` или `{m,n}?`, которые сопоставляют как можно *меньше* текста. В приведенном выше примере `'>'` пробуется сразу после того, как первое `'<'` совпадает, и когда это не удается, движок продвигается на один символ за раз, повторяя попытку `'>'` на каждом шаге. Это дает правильный результат:868869```python870>>> print(re.match('<.*?>', s).group())871<html>872```873874(Заметьте, что разбор HTML или XML с помощью регулярных выражений – дело мучительное. Быстрые и грязные шаблоны справятся с обычными случаями, но у HTML и XML есть особые случаи, которые сломают очевидное регулярное выражение; к тому времени, как вы напишете регулярное выражение, обрабатывающее все возможные случаи, шаблоны станут *очень* сложными. Используйте для таких задач модуль разбора HTML или XML.)875876### Использование re.VERBOSE877878К этому моменту вы, вероятно, заметили, что регулярные выражения – это очень компактная запись, но они не слишком читаемы. RE умеренной сложности могут превратиться в длинные наборы обратных слешей, скобок и метасимволов, что затрудняет их чтение и понимание.879880Для таких регулярных выражений может быть полезно указать флаг `re.VERBOSE` при компиляции, так как он позволяет форматировать регулярное выражение более наглядно.881882Флаг `re.VERBOSE` имеет несколько эффектов. Пробельные символы в регулярном выражении, которые *не находятся* внутри класса символов, игнорируются. Это означает, что выражение, такое как `dog | cat`, эквивалентно менее читаемому `dog|cat`, но `[a b]` все равно будет соответствовать символам `'a'`, `'b'` или пробелу. Кроме того, можно размещать комментарии внутри регулярного выражения; комментарии начинаются с символа `#` и продолжаются до следующего перевода строки. При использовании с тройными кавычками это позволяет форматировать регулярные выражения более аккуратно:883884```python885pat = re.compile(r"""886 \s* # Skip leading whitespace887 (?P<header>[^:]+) # Header name888 \s* : # Whitespace, and a colon889 (?P<value>.*?) # The header's value -- *? used to890 # lose the following trailing whitespace891 \s*$ # Trailing whitespace to end-of-line892""", re.VERBOSE)893```894895Это гораздо читаемее, чем:896897```python898pat = re.compile(r"\s*(?P<header>[^:]+)\s*:(?P<value>.*?)\s*$")899```900901## Обратная связь902903Регулярные выражения – сложная тема. Помог ли вам этот документ понять их? Были ли части, которые остались неясными, или проблемы, с которыми вы столкнулись и которые не были здесь рассмотрены? Если да, отправьте предложения по улучшению автору.904905Самая полная книга по регулярным выражениям – это, почти наверняка, «Mastering Regular Expressions» Джеффри Фридла, издательство O'Reilly. К сожалению, она полностью сосредоточена на реализациях регулярных выражений в Perl и Java и не содержит никакого материала по Python, поэтому она не пригодится в качестве справочника для программирования на Python. (Первое издание охватывало ныне удаленный модуль Python `regex`, что вам не очень поможет.) Подумайте о том, чтобы взять ее в библиотеке.906