Регулярные выражения Python (часть 1)

Оглавление

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

Ранее в этой серии, в уроке Строки и символьные данные в Python, вы узнали, как определять строковые объекты и манипулировать ими. С тех пор вы познакомились с некоторыми способами определения соответствия двух строк друг другу:

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

В этом уроке вы узнаете:

  • Как получить доступ к модулю re, который реализует регекс-сопоставление в Python
  • Как использовать re.search() для сопоставления шаблона со строкой
  • Как создать сложный шаблон соответствия с помощью regex metacharacters

Пристегните ремни! Синтаксис регексов требует некоторого привыкания. Но как только вы освоитесь с ним, вы найдете регексы практически незаменимыми в программировании на Python.

Бесплатная загрузка: Получите пример главы из книги Python Tricks: The Book, которая покажет вам лучшие практики Python на простых примерах, которые вы сможете мгновенно применить, чтобы писать более красивый + Pythonic код.

Регексы в Python и их использование

Представьте, что у вас есть строковый объект s. Теперь предположим, что вам нужно написать код на Python, чтобы выяснить, содержит ли s подстроку '123'. Есть по крайней мере несколько способов сделать это. Вы можете использовать оператор in:

>>> s = 'foo123bar'
>>> '123' in s
True

Если вы хотите узнать не только существует ли '123' в s, но и где он существует, то вы можете использовать .find() или .index(). Каждый из них возвращает позицию символа в s, в которой находится подстрока:

>>> s = 'foo123bar'
>>> s.find('123')
3
>>> s.index('123')
3

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

Например, вместо поиска фиксированной подстроки, такой как '123', предположим, что вы хотите определить, содержит ли строка любые три последовательных десятичных знака, как в строках 'foo123bar', 'foo456bar', '234baz' и 'qux678'.

Строгие сравнения символов здесь не помогут. Здесь на помощь приходят регексы в Python.

А (очень краткая) история регулярных выражений

В 1951 году математик Стивен Коул Клин описал концепцию регулярного языка - языка, распознаваемого конечным автоматом и формально выражаемого с помощью регулярных выражений. В середине 1960-х годов пионер компьютерных наук Кен Томпсон, один из разработчиков Unix, реализовал согласование шаблонов в текстовом редакторе QED, используя нотацию Клина.

С тех пор регексы появились во многих языках программирования, редакторах и других инструментах как средство определения соответствия строки заданному шаблону. Python, Java и Perl поддерживают функции regex, как и большинство инструментов Unix и многие текстовые редакторы.

Модуль re

Функциональность Regex в Python находится в модуле с именем re. Модуль re содержит множество полезных функций и методов, с большинством из которых вы познакомитесь в следующем уроке этой серии.

Сейчас вы сосредоточитесь преимущественно на одной функции, re.search().

re.search(<regex>, <string>)

Проверяет строку на совпадение с регексом.

re.search(<regex>, <string>) сканирует <string> в поисках первого места, где совпадает шаблон <regex>. Если совпадение найдено, то re.search() возвращает объект match. В противном случае возвращается None.

re.search() принимает необязательный третий аргумент <flags>, о котором вы узнаете в конце этого урока.

Как импортировать re.search()

Поскольку search() находится в модуле re, вам необходимо импортировать его, прежде чем вы сможете его использовать. Один из способов сделать это - импортировать весь модуль, а затем использовать имя модуля в качестве префикса при вызове функции:

import re
re.search(...)

Альтернативный вариант - импортировать функцию из модуля по имени, а затем ссылаться на нее без префикса имени модуля:

from re import search
search(...)

Вам всегда придется импортировать re.search() тем или иным способом, прежде чем вы сможете его использовать.

В примерах, приведенных в оставшейся части этого руководства, будет использован первый показанный подход - импортирование модуля re и обращение к функции с префиксом имени модуля: re.search(). Для краткости утверждение import re обычно опускается, но помните, что оно всегда необходимо.

Для получения дополнительной информации об импорте из модулей и пакетов ознакомьтесь с Python Modules and Packages-An Introduction.

Первый пример сопоставления с образцом

Теперь, когда вы знаете, как получить доступ к re.search(), вы можете попробовать:

 1>>> s = 'foo123bar'
 2
 3>>> # One last reminder to import!
 4>>> import re
 5
 6>>> re.search('123', s)
 7<_sre.SRE_Match object; span=(3, 6), match='123'>

Здесь шаблон поиска <regex> - это 123, а <string> - это s. Возвращаемый объект соответствия появляется на строке 7. Объекты соответствия содержат множество полезной информации, которую вы вскоре изучите.

На данный момент важно то, что re.search() действительно вернул объект match, а не None. Это говорит о том, что совпадение найдено. Другими словами, заданный <regex> шаблон 123 присутствует в s.

Объект match является истинным, поэтому вы можете использовать его в логическом контексте, как условный оператор:

>>> if re.search('123', s):
...     print('Found a match.')
... else:
...     print('No match.')
...
Found a match.

Интерпретатор отображает объект соответствия как <_sre.SRE_Match object; span=(3, 6), match='123'>. Это содержит некоторую полезную информацию.

span=(3, 6) указывает на часть <string>, в которой было найдено совпадение. Это означает то же самое, что и в нотации слайсов:

>>> s[3:6]
'123'

В этом примере совпадение начинается с позиции символа 3 и продолжается до позиции 6, но не включая ее.

match='123' указывает, какие символы из <string> совпали.

Это хорошее начало. Но в данном случае шаблон <regex> представляет собой обычную строку '123'. Сопоставление шаблонов здесь все еще представляет собой сравнение по символам, практически такое же, как и в примерах оператора in и .find(), показанных ранее. Объект match услужливо сообщает, что совпадающими символами были '123', но это не слишком большое откровение, поскольку именно эти символы вы искали.

Ты только разогреваешься.

Python Regex Metacharacters

Настоящая мощь regex-сопоставления в Python проявляется, когда <regex> содержит специальные символы, называемые метасимволами. Они имеют уникальное значение для механизма regex-сопоставления и значительно расширяют возможности поиска.

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

В regex набор символов, указанных в квадратных скобках ([]), составляет класс символов . Эта последовательность метасимволов соответствует любому отдельному символу, входящему в класс, как показано в следующем примере:

>>> s = 'foo123bar'
>>> re.search('[0-9][0-9][0-9]', s)
<_sre.SRE_Match object; span=(3, 6), match='123'>

[0-9] соответствует любой десятичной цифре - любому символу между '0' и '9', включительно. Полное выражение [0-9][0-9][0-9] соответствует любой последовательности из трех десятичных цифр. В данном случае совпадает s, поскольку оно содержит три последовательных десятичных знака, '123' и

Эти строки также совпадают:

>>> re.search('[0-9][0-9][0-9]', 'foo456bar')
<_sre.SRE_Match object; span=(3, 6), match='456'>

>>> re.search('[0-9][0-9][0-9]', '234baz')
<_sre.SRE_Match object; span=(0, 3), match='234'>

>>> re.search('[0-9][0-9][0-9]', 'qux678')
<_sre.SRE_Match object; span=(3, 6), match='678'>

С другой стороны, строка, не содержащая трех последовательных цифр, не будет соответствовать:

>>> print(re.search('[0-9][0-9][0-9]', '12foo34'))
None

С помощью регексов в Python можно выявить в строке закономерности, которые невозможно найти с помощью оператора in или строковых методов.

Взгляните на другой метахарактер regex. Метасимвол точки (.) соответствует любому символу, кроме новой строки, поэтому он работает как подстановочный знак:

>>> s = 'foo123bar'
>>> re.search('1.3', s)
<_sre.SRE_Match object; span=(3, 6), match='123'>

>>> s = 'foo13bar'
>>> print(re.search('1.3', s))
None

В первом примере регекс 1.3 совпадает с '123', поскольку '1' и '3' совпадают буквально, а . совпадает с '2'. Здесь вы, по сути, спрашиваете: "Содержит ли s символ '1', затем любой символ (кроме новой строки), затем '3'?". Ответ - да для 'foo123bar', но нет для 'foo13bar'.

Эти примеры позволяют быстро проиллюстрировать возможности метасимволов regex. Класс символов и точка - это лишь два из метасимволов, поддерживаемых модулем re. Их гораздо больше. Далее вы подробно изучите их.

Метасимволы, поддерживаемые модулем re

В следующей таблице кратко перечислены все метасимволы, поддерживаемые модулем re. Некоторые символы служат более чем одной цели:

Character(s) Meaning
. Сопоставляет любой одиночный символ, кроме новой строки
^ ∙ Закрепляет совпадение в начале строки
∙ Дополняет класс символа
$ Закрепляет совпадение в конце строки
* Совпадает с нулем или более повторов
+ Совпадает с одним или несколькими повторениями
? ∙ Сопоставляет нулевой или один повтор
∙ Определяет нежадные версии *, + и ?
∙ Вводит утверждение lookahead или lookbehind
∙ Создает именованную группу
{} Сопоставляет явно заданное количество повторений
\ ∙ Избавляет метасимвол от его специального значения
∙ Вводит специальный класс символов
∙ Вводит обратную ссылку на группировку
[] Указание класса символов
| Обозначает чередование
() Создает группу
:
#
=
!
Назначает специализированную группу
<> Создает именованную группу

Это может показаться непомерным объемом информации, но не паникуйте! В следующих разделах мы подробно рассмотрим каждый из них.

Парсер regex рассматривает любой символ, не указанный выше, как обычный символ, который совпадает только с самим собой. Например, в первом примере подбора по образцу, показанном выше, вы видели следующее:

>>> s = 'foo123bar'
>>> re.search('123', s)
<_sre.SRE_Match object; span=(3, 6), match='123'>

В данном случае 123 технически является регексом, но он не очень интересен, поскольку не содержит метасимволов. Он просто соответствует строке '123'.

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

Метасимволы, совпадающие с одним символом

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

[]

Определяет конкретный набор символов для сопоставления.

Символы, заключенные в квадратные скобки ([]), представляют собой класс символов - перечисляемый набор символов, с которыми можно сравнивать. Последовательность метасимволов класса символов будет соответствовать любому символу, входящему в этот класс.

Вы можете перечислить символы по отдельности следующим образом:

>>> re.search('ba[artz]', 'foobarqux')
<_sre.SRE_Match object; span=(3, 6), match='bar'>
>>> re.search('ba[artz]', 'foobazqux')
<_sre.SRE_Match object; span=(3, 6), match='baz'>

Последовательность метасимволов [artz] соответствует любому одиночному символу 'a', 'r', 't' или 'z'. В примере регекс ba[artz] совпадает с символами 'bar' и 'baz' (а также с символами 'baa' и 'bat').

Класс символов может также содержать диапазон символов, разделенных дефисом (-), в этом случае он соответствует любому отдельному символу в этом диапазоне. Например, [a-z] соответствует любому строчному алфавитному символу между 'a' и 'z', включительно:

>>> re.search('[a-z]', 'FOObar')
<_sre.SRE_Match object; span=(3, 4), match='b'>

[0-9] соответствует любому цифровому символу:

>>> re.search('[0-9][0-9]', 'foo123bar')
<_sre.SRE_Match object; span=(3, 5), match='12'>

В данном случае [0-9][0-9] соответствует последовательности из двух цифр. Первая часть строки 'foo123bar', которая совпадает, это '12'.

[0-9a-fA-F] соответствует любому шестнадцатеричному цифровому символу:

>>> re.search('[0-9a-fA-f]', '--- a0 ---')
<_sre.SRE_Match object; span=(4, 5), match='a'>

Здесь [0-9a-fA-F] соответствует первому шестнадцатизначному символу в строке поиска, 'a'.

Примечание: В приведенных выше примерах возвращаемым значением всегда является крайнее левое возможное совпадение. re.search() сканирует поисковую строку слева направо, и как только обнаруживает совпадение для <regex>, прекращает сканирование и возвращает это совпадение.

Вы можете дополнить класс символов, указав в качестве первого символа ^, и тогда он будет соответствовать любому символу, который не в этом наборе. В следующем примере [^0-9] соответствует любому символу, не являющемуся цифрой:

>>> re.search('[^0-9]', '12345foo')
<_sre.SRE_Match object; span=(5, 6), match='f'>

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

Если символ ^ встречается в классе символов, но не является первым символом, то он не имеет специального значения и соответствует буквальному символу '^':

>>> re.search('[#:^]', 'foo^bar:baz#qux')
<_sre.SRE_Match object; span=(3, 4), match='^'>

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

>>> re.search('[-abc]', '123-456')
<_sre.SRE_Match object; span=(3, 4), match='-'>
>>> re.search('[abc-]', '123-456')
<_sre.SRE_Match object; span=(3, 4), match='-'>
>>> re.search('[ab\-c]', '123-456')
<_sre.SRE_Match object; span=(3, 4), match='-'>

Если вы хотите включить литерал ']' в символьный класс, то вы можете поместить его в качестве первого символа или экранировать его с помощью обратного слеша:

>>> re.search('[]]', 'foo[1]')
<_sre.SRE_Match object; span=(5, 6), match=']'>
>>> re.search('[ab\]cd]', 'foo[1]')
<_sre.SRE_Match object; span=(5, 6), match=']'>

Другие метасимволы regex теряют свое особое значение внутри класса символов:

>>> re.search('[)*+|]', '123*456')
<_sre.SRE_Match object; span=(3, 4), match='*'>
>>> re.search('[)*+|]', '123+456')
<_sre.SRE_Match object; span=(3, 4), match='+'>

Как вы видели в таблице выше, * и + имеют специальные значения в regex в Python. Они обозначают повторение, о котором вы скоро узнаете подробнее. Но в данном примере они находятся внутри класса символов, поэтому совпадают буквально.

точка (.)

Указывает подстановочный знак.

Метасимвол . соответствует любому символу, кроме новой строки:

>>> re.search('foo.bar', 'fooxbar')
<_sre.SRE_Match object; span=(0, 7), match='fooxbar'>

>>> print(re.search('foo.bar', 'foobar'))
None
>>> print(re.search('foo.bar', 'foo\nbar'))
None

Как регекс, foo.bar по сути означает символы 'foo', затем любой символ, кроме новой строки, затем символы 'bar'. Первая строка, показанная выше, 'fooxbar', подходит для этого, поскольку метасимвол . совпадает с 'x'.

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

\w
\W

Совпадение на основе того, является ли символ словом.

\w соответствует любому буквенно-цифровому символу слова. Символы слова - это прописные и строчные буквы, цифры и символ подчеркивания (_), поэтому \w по сути является сокращением для [a-zA-Z0-9_]:

>>> re.search('\w', '#(.a$@&')
<_sre.SRE_Match object; span=(3, 4), match='a'>
>>> re.search('[a-zA-Z0-9_]', '#(.a$@&')
<_sre.SRE_Match object; span=(3, 4), match='a'>

В данном случае первым символом слова в строке '#(.a$@&' является 'a'.

\W является противоположностью. Он соответствует любому символу, не являющемуся словом, и эквивалентен [^a-zA-Z0-9_]:

>>> re.search('\W', 'a_1*3Qb')
<_sre.SRE_Match object; span=(3, 4), match='*'>
>>> re.search('[^a-zA-Z0-9_]', 'a_1*3Qb')
<_sre.SRE_Match object; span=(3, 4), match='*'>

Здесь первым несловарным символом в 'a_1*3!b' является '*'.

\d
\D

Совпадение на основе того, является ли символ десятичной цифрой.

\d соответствует любому символу десятичной цифры. \D - наоборот. Он соответствует любому символу, который не является десятичной цифрой:

>>> re.search('\d', 'abc4def')
<_sre.SRE_Match object; span=(3, 4), match='4'>

>>> re.search('\D', '234Q678')
<_sre.SRE_Match object; span=(3, 4), match='Q'>

\d по сути эквивалентен [0-9], а \D эквивалентен [^0-9].

\s
\S

Совпадение на основе того, представляет ли символ пробел.

\s соответствует любому пробельному символу:

>>> re.search('\s', 'foo\nbar baz')
<_sre.SRE_Match object; span=(3, 4), match='\n'>

Обратите внимание, что, в отличие от метасимвола подстановки точки, \s соответствует символу новой строки.

\S является противоположностью \s. Он соответствует любому символу, который не является пробельным:

>>> re.search('\S', '  \n foo  \n  ')
<_sre.SRE_Match object; span=(4, 5), match='f'>

Опять же, \s и \S считают новую строку пробельным символом. В приведенном выше примере первым символом, не являющимся пробельным, является 'f'.

Последовательности классов символов \w, \W, \d, \D, \s и \S могут появляться и внутри класса символов в квадратных скобках:

>>> re.search('[\d\w\s]', '---3---')
<_sre.SRE_Match object; span=(3, 4), match='3'>
>>> re.search('[\d\w\s]', '---a---')
<_sre.SRE_Match object; span=(3, 4), match='a'>
>>> re.search('[\d\w\s]', '--- ---')
<_sre.SRE_Match object; span=(3, 4), match=' '>

В этом случае [\d\w\s] соответствует любой цифре, слову или пробельному символу. А поскольку \w включает в себя \d, тот же класс символов можно выразить чуть короче - [\w\s].

Уход от метаперсонажей

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

обратный слеш (\)

Удаляет специальное значение метасимвола.

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

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

 1>>> re.search('.', 'foo.bar')
 2<_sre.SRE_Match object; span=(0, 1), match='f'>
 3
 4>>> re.search('\.', 'foo.bar')
 5<_sre.SRE_Match object; span=(3, 4), match='.'>

В строке <regex> на строке 1 точка (.) работает как метасимвол подстановки, который соответствует первому символу в строке ('f'). Символ . в <regex> на строке 4 экранирован обратным слешем, поэтому он не является символом подстановки. Он интерпретируется буквально и соответствует символу '.' в индексе 3 строки поиска.

Использование обратных слэшей для экранирования может привести к путанице. Предположим, у вас есть строка, содержащая один обратный слеш:

>>> s = r'foo\bar'
>>> print(s)
foo\bar

Теперь предположим, что вы хотите создать <regex>, который будет соответствовать обратной косой черте между 'foo' и 'bar'. Обратная косая черта сама по себе является специальным символом в регексе, поэтому, чтобы указать буквальную обратную косую черту, вам нужно экранировать ее другой обратной косой чертой. Если это так, то должно сработать следующее:

>>> re.search('\\', s)

Не совсем. Вот что вы получите, если попробуете это сделать:

>>> re.search('\\', s)
Traceback (most recent call last):
  File "<pyshell#3>", line 1, in <module>
    re.search('\\', s)
  File "C:\Python36\lib\re.py", line 182, in search
    return _compile(pattern, flags).search(string)
  File "C:\Python36\lib\re.py", line 301, in _compile
    p = sre_compile.compile(pattern, flags)
  File "C:\Python36\lib\sre_compile.py", line 562, in compile
    p = sre_parse.parse(p, flags)
  File "C:\Python36\lib\sre_parse.py", line 848, in parse
    source = Tokenizer(str)
  File "C:\Python36\lib\sre_parse.py", line 231, in __init__
    self.__next()
  File "C:\Python36\lib\sre_parse.py", line 245, in __next
    self.string, len(self.string) - 1) from None
sre_constants.error: bad escape (end of pattern) at position 0

Упс. Что случилось?

Проблема в том, что экранирование обратного слеша происходит дважды: сначала интерпретатором Python в строковом литерале, а затем парсером regex в полученном им regex.

Вот последовательность событий:

  1. Интерпретатор Python первым обрабатывает строковый литерал '\\'. Он интерпретирует его как экранированный обратный слеш и передает только один обратный слеш в re.search().
  2. Парсер regex получает только один обратный слеш, который не является осмысленным regex, поэтому возникает ошибка.

Есть два способа обойти это. Во-первых, вы можете экранировать оба обратных слэша в исходном строковом литерале:

>>> re.search('\\\\', s)
<_sre.SRE_Match object; span=(3, 4), match='\\'>

При этом происходит следующее:

  1. Интерпретатор воспринимает '\\\\' как пару экранированных обратных слешей. Он сокращает каждую пару до одного обратного слеша и передает '\\' в regex-парсер.
  2. <<<Парсер regex воспринимает
  3. как одну \\ обратную косую черту. Как <regex>, это соответствует одному символу обратной косой черты. Из объекта match видно, что он сопоставил обратную косую черту в индексе 3 в s, как и предполагалось. Это громоздко, но работает.

Второй, и, вероятно, более чистый, способ справиться с этим - указать <regex> с помощью сырой строки:

>>> re.search(r'\\', s)
<_sre.SRE_Match object; span=(3, 4), match='\\'>

Это подавляет экранирование на уровне интерпретатора. Строка '\\' передается без изменений в регекс-парсер, который снова видит одну экранированную обратную косую черту, как и требуется.

В Python принято использовать необработанную строку для задания регекса, если она содержит обратные косые черты.

Якоря

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

^
\A

Зафиксируйте совпадение с началом <string>.

Когда парсер regex встречает ^ или \A, текущая позиция парсера должна быть в начале строки поиска, чтобы он нашел совпадение.

Другими словами, regex ^foo предусматривает, что 'foo' должен присутствовать не в любом старом месте поисковой строки, а в самом начале:

>>> re.search('^foo', 'foobar')
<_sre.SRE_Match object; span=(0, 3), match='foo'>
>>> print(re.search('^foo', 'barfoo'))
None

\A функционирует аналогично:

>>> re.search('\Afoo', 'foobar')
<_sre.SRE_Match object; span=(0, 3), match='foo'>
>>> print(re.search('\Afoo', 'barfoo'))
None

^ и \A ведут себя несколько иначе, чем в режиме MULTILINE. Подробнее о режиме MULTILINE вы узнаете ниже, в разделе о флагах.

$
\Z

Зафиксируйте совпадение в конце <string>.

Когда парсер regex встречает $ или \Z, текущая позиция парсера должна быть в конце строки поиска, чтобы он нашел совпадение. Все, что предшествует $ или \Z, должно представлять собой конец строки поиска:

>>> re.search('bar$', 'foobar')
<_sre.SRE_Match object; span=(3, 6), match='bar'>
>>> print(re.search('bar$', 'barfoo'))
None

>>> re.search('bar\Z', 'foobar')
<_sre.SRE_Match object; span=(3, 6), match='bar'>
>>> print(re.search('bar\Z', 'barfoo'))
None

В качестве особого случая, $ (но не \Z) также соответствует непосредственно перед одиночной новой строкой в конце строки поиска:

>>> re.search('bar$', 'foobar\n')
<_sre.SRE_Match object; span=(3, 6), match='bar'>

В этом примере 'bar' технически не является концом строки поиска, поскольку за ним следует еще один символ новой строки. Но парсер regex пропускает его и все равно называет совпадением. Это исключение не относится к \Z.

$ и \Z ведут себя несколько иначе, чем в режиме MULTILINE. Дополнительную информацию о режиме MULTILINE см. в разделе flags.

\b

Закрепляет совпадение с границей слова.

\b утверждает, что текущая позиция регекс-парсера должна находиться в начале или конце слова. Слово состоит из последовательности буквенно-цифровых символов или знаков подчеркивания ([a-zA-Z0-9_]), таких же, как и для класса символов \w:

 1>>> re.search(r'\bbar', 'foo bar')
 2<_sre.SRE_Match object; span=(4, 7), match='bar'>
 3>>> re.search(r'\bbar', 'foo.bar')
 4<_sre.SRE_Match object; span=(4, 7), match='bar'>
 5
 6>>> print(re.search(r'\bbar', 'foobar'))
 7None
 8
 9>>> re.search(r'foo\b', 'foo bar')
10<_sre.SRE_Match object; span=(0, 3), match='foo'>
11>>> re.search(r'foo\b', 'foo.bar')
12<_sre.SRE_Match object; span=(0, 3), match='foo'>
13
14>>> print(re.search(r'foo\b', 'foobar'))
15None

В приведенных выше примерах совпадение происходит на строках 1 и 3, поскольку в начале 'bar' есть граница слов. Этого не происходит в строке 6, поэтому совпадение там не происходит.

Аналогично, есть совпадения в строках 9 и 11, потому что граница слова существует в конце 'foo', но нет в строке 14.

Использование якоря \b на обоих концах <regex> приведет к тому, что он будет совпадать, если присутствует в строке поиска в виде целого слова:

>>> re.search(r'\bbar\b', 'foo bar baz')
<_sre.SRE_Match object; span=(4, 7), match='bar'>
>>> re.search(r'\bbar\b', 'foo(bar)baz')
<_sre.SRE_Match object; span=(4, 7), match='bar'>

>>> print(re.search(r'\bbar\b', 'foobarbaz'))
None

Это еще один случай, когда стоит указать <regex> в виде необработанной строки, как это было сделано в примерах выше.

<<<Поскольку

Поскольку '\b' является экранирующей последовательностью как для строковых литералов, так и для регексов в Python, каждое использование выше должно быть дважды экранировано как '\\b', если вы не используете необработанные строки. Это не было бы концом света, но необработанные строки выглядят аккуратнее.

\B

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

\B делает противоположное \b. Он утверждает, что текущая позиция регекс-парсера должна не находиться в начале или конце слова:

 1>>> print(re.search(r'\Bfoo\B', 'foo'))
 2None
 3>>> print(re.search(r'\Bfoo\B', '.foo.'))
 4None
 5
 6>>> re.search(r'\Bfoo\B', 'barfoobaz')
 7<_sre.SRE_Match object; span=(3, 6), match='foo'>

В данном случае совпадение происходит на строке 7, поскольку в поисковой строке 'barfoobaz' не существует границы слов в начале или конце 'foo'.

Квантификаторы

Метасимвол квантификатор следует сразу за частью <regex> и указывает, сколько раз эта часть должна встречаться для успешного совпадения.

*

Совпадает с нулем или более повторений предыдущего regex.

Например, a* соответствует нулю или более символов 'a'. Это означает, что она будет соответствовать пустой строке, 'a', 'aa', 'aaa' и так далее.

Рассмотрим эти примеры:

 1>>> re.search('foo-*bar', 'foobar')                     # Zero dashes
 2<_sre.SRE_Match object; span=(0, 6), match='foobar'>
 3>>> re.search('foo-*bar', 'foo-bar')                    # One dash
 4<_sre.SRE_Match object; span=(0, 7), match='foo-bar'>
 5>>> re.search('foo-*bar', 'foo--bar')                   # Two dashes
 6<_sre.SRE_Match object; span=(0, 8), match='foo--bar'>

На строке 1 между 'foo' и 'bar' находится ноль символов '-'. На строке 3 - один, а на строке 5 - два. Последовательность метасимволов -* совпадает во всех трех случаях.

Вы наверняка когда-нибудь сталкивались с регексом .* в программах на Python. Он соответствует нулю или более вхождений любого символа. Другими словами, по сути, он соответствует любой последовательности символов вплоть до перевода строки. (Помните, что метасимвол подстановки . не совпадает с новой строкой.)

В этом примере .* соответствует всему между 'foo' и 'bar':

>>> re.search('foo.*bar', '# foo $qux@grault % bar #')
<_sre.SRE_Match object; span=(2, 23), match='foo $qux@grault % bar'>

Заметили ли вы информацию span= и match=, содержащуюся в объекте match?

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

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

+

Совпадает с одним или несколькими повторениями предыдущего regex.

Это похоже на *, но квантифицированный регекс должен встречаться хотя бы один раз:

 1 >>> print(re.search('foo-+bar', 'foobar'))              # Zero dashes
 2 None
 3 >>> re.search('foo-+bar', 'foo-bar')                    # One dash
 4 <_sre.SRE_Match object; span=(0, 7), match='foo-bar'>
 5 >>> re.search('foo-+bar', 'foo--bar')                   # Two dashes
 6 <_sre.SRE_Match object; span=(0, 8), match='foo--bar'>

Помните, что foo-*bar соответствовал строке 'foobar', поскольку метасимвол * допускает ноль вхождений '-'. Метасимвол +, с другой стороны, требует хотя бы одного вхождения '-'. Это означает, что в данном случае нет совпадения в строке 1.

?

Совпадает с нулем или одним повторением предыдущего regex.

Опять же, это похоже на * и +, но в этом случае совпадение будет только в том случае, если предыдущий regex встречается один раз или не встречается вообще:

 1 >>> re.search('foo-?bar', 'foobar')                     # Zero dashes
 2 <_sre.SRE_Match object; span=(0, 6), match='foobar'>
 3 >>> re.search('foo-?bar', 'foo-bar')                    # One dash
 4 <_sre.SRE_Match object; span=(0, 7), match='foo-bar'>
 5 >>> print(re.search('foo-?bar', 'foo--bar'))            # Two dashes
 6None

В этом примере есть совпадения на строках 1 и 3. Но на строке 5, где есть два символа '-', совпадение не происходит.

Вот еще несколько примеров, демонстрирующих использование всех трех метасимволов квантификатора:

>>> re.match('foo[1-9]*bar', 'foobar')
<_sre.SRE_Match object; span=(0, 6), match='foobar'>
>>> re.match('foo[1-9]*bar', 'foo42bar')
<_sre.SRE_Match object; span=(0, 8), match='foo42bar'>

>>> print(re.match('foo[1-9]+bar', 'foobar'))
None
>>> re.match('foo[1-9]+bar', 'foo42bar')
<_sre.SRE_Match object; span=(0, 8), match='foo42bar'>

>>> re.match('foo[1-9]?bar', 'foobar')
<_sre.SRE_Match object; span=(0, 6), match='foobar'>
>>> print(re.match('foo[1-9]?bar', 'foo42bar'))
None

На этот раз квантифицированным регексом является класс символов [1-9], а не простой символ '-'.

*?
+?
??

Не жадные (или ленивые) версии квантификаторов *, + и ?.

При одиночном использовании метасимволы квантификатора *, + и ? являются greedy, то есть дают самое длинное возможное соответствие. Рассмотрим этот пример:

>>> re.search('<.*>', '%<foo> <bar> <baz>%')
<_sre.SRE_Match object; span=(1, 18), match='<foo> <bar> <baz>'>

Регекс <.*> фактически означает:

  • А '<' символ
  • Затем любая последовательность символов
  • Затем символ '>'

Но какой '>' персонаж? Есть три возможности:

  1. Тот, что сразу после 'foo'
  2. Тот, что сразу после 'bar'
  3. Тот, что сразу после 'baz'

Поскольку метасимвол является жадным, он диктует самое длинное возможное совпадение, которое включает в себя все до символа '>', следующего за 'baz', включительно. Из объекта match видно, что именно такое совпадение было получено.

Если вы хотите получить максимально короткое совпадение, используйте нежадную последовательность метасимволов *?:

>>> re.search('<.*?>', '%<foo> <bar> <baz>%')
<_sre.SRE_Match object; span=(1, 6), match='<foo>'>

В этом случае совпадение заканчивается символом '>', следующим за 'foo'.

Примечание: То же самое можно сделать с помощью регекса <[^>]*>, который означает:

  • А '<' символ
  • Затем любая последовательность символов, отличная от '>'
  • Затем символ '>'

Это единственная возможность, доступная для некоторых старых парсеров, которые не поддерживают ленивые квантификаторы. К счастью, это не относится к парсеру regex в модуле Python re.

Существуют также ленивые версии квантификаторов + и ?:

 1 >>> re.search('<.+>', '%<foo> <bar> <baz>%')
 2 <_sre.SRE_Match object; span=(1, 18), match='<foo> <bar> <baz>'>
 3 >>> re.search('<.+?>', '%<foo> <bar> <baz>%')
 4 <_sre.SRE_Match object; span=(1, 6), match='<foo>'>
 5
 6 >>> re.search('ba?', 'baaaa')
 7 <_sre.SRE_Match object; span=(0, 2), match='ba'>
 8 >>> re.search('ba??', 'baaaa')
 9 <_sre.SRE_Match object; span=(0, 1), match='b'>

Первые два примера на строках 1 и 3 похожи на примеры, показанные выше, только вместо * и *? используются + и +?.

Последние примеры на строках 6 и 8 немного отличаются. В общем случае метасимвол ? соответствует нулю или одному вхождению предыдущего регекса. Жадная версия, ?, соответствует одному вхождению, поэтому ba? соответствует 'b', за которым следует один 'a'. Нежадная версия, ??, соответствует нулевому вхождению, поэтому ba?? соответствует только 'b'.

{m}

Находит точные m повторения предыдущего regex.

Это похоже на * или +, но указывает, сколько раз должен повториться предыдущий регекс, чтобы совпадение было успешным:

>>> print(re.search('x-{3}x', 'x--x'))                # Two dashes
None

>>> re.search('x-{3}x', 'x---x')                      # Three dashes
<_sre.SRE_Match object; span=(0, 5), match='x---x'>

>>> print(re.search('x-{3}x', 'x----x'))              # Four dashes
None

Здесь x-{3}x соответствует 'x', за которым следуют ровно три экземпляра символа '-', а затем еще один 'x'. Если между символами 'x' меньше или больше трех тире, совпадение не выполняется.

{m,n}

Соответствует любому количеству повторений предыдущего регекса от m до n, включительно.

В следующем примере квантифицированным <regex> является -{2,4}. Совпадение будет успешным, если между символами 'x' есть два, три или четыре тире, но не будет успешным в противном случае:

>>> for i in range(1, 6):
...     s = f"x{'-' * i}x"
...     print(f'{i}  {s:10}', re.search('x-{2,4}x', s))
...
1  x-x        None
2  x--x       <_sre.SRE_Match object; span=(0, 4), match='x--x'>
3  x---x      <_sre.SRE_Match object; span=(0, 5), match='x---x'>
4  x----x     <_sre.SRE_Match object; span=(0, 6), match='x----x'>
5  x-----x    None

Опустив m, можно получить нижнюю границу 0, а опустив n, можно получить неограниченную верхнюю границу:

Regular Expression Matches Identical to
<regex>{,n} Any number of repetitions of <regex> less than or equal to n <regex>{0,n}
<regex>{m,} Any number of repetitions of <regex> greater than or equal to m ----
<regex>{,} Any number of repetitions of <regex> <regex>{0,}
<regex>*

Если вы опустите все m, n и запятую, то фигурные скобки перестанут выполнять функцию метасимволов. {} соответствует только литеральной строке '{}':

>>> re.search('x{}y', 'x{}y')
<_sre.SRE_Match object; span=(0, 4), match='x{}y'>

На самом деле, чтобы последовательность с фигурными скобками имела какой-то особый смысл, она должна соответствовать одной из следующих схем, в которых m и n являются неотрицательными целыми числами:

  • {m,n}
  • {m,}
  • {,n}
  • {,}

В противном случае он совпадает дословно:

>>> re.search('x{foo}y', 'x{foo}y')
<_sre.SRE_Match object; span=(0, 7), match='x{foo}y'>
>>> re.search('x{a:b}y', 'x{a:b}y')
<_sre.SRE_Match object; span=(0, 7), match='x{a:b}y'>
>>> re.search('x{1,3,5}y', 'x{1,3,5}y')
<_sre.SRE_Match object; span=(0, 9), match='x{1,3,5}y'>
>>> re.search('x{foo,bar}y', 'x{foo,bar}y')
<_sre.SRE_Match object; span=(0, 11), match='x{foo,bar}y'>

Позже в этом учебнике, когда вы узнаете о флаге DEBUG, вы увидите, как это можно подтвердить.

{m,n}?

Не жадная (ленивая) версия {m,n}.

{m,n} будет соответствовать как можно большему количеству символов, а {m,n}? - как можно меньшему:

>>> re.search('a{3,5}', 'aaaaaaaa')
<_sre.SRE_Match object; span=(0, 5), match='aaaaa'>

>>> re.search('a{3,5}?', 'aaaaaaaa')
<_sre.SRE_Match object; span=(0, 3), match='aaa'>

В данном случае a{3,5} дает самое длинное из возможных совпадений, поэтому совпадает с пятью символами 'a'. a{3,5}? дает самое короткое совпадение, поэтому совпадает три символа.

Группировка конструкций и обратные ссылки

Конструкции группировки разбивают регекс в Python на подвыражения или группы. Это служит двум целям:

  1. Группировка: Группа представляет собой единую синтаксическую единицу. Дополнительные метасимволы применяются ко всей группе как единому целому.
  2. Захват: Некоторые конструкции группировки также захватывают часть строки поиска, которая соответствует подвыражению в группе. Вы можете извлечь захваченные совпадения позже с помощью нескольких различных механизмов.

Вот взгляд на то, как работают группировка и захват.

(<regex>)

Определяет подвыражение или группу.

Это самая базовая конструкция группировки. Регекс в круглых скобках просто соответствует содержимому скобок:

>>> re.search('(bar)', 'foo bar baz')
<_sre.SRE_Match object; span=(4, 7), match='bar'>

>>> re.search('bar', 'foo bar baz')
<_sre.SRE_Match object; span=(4, 7), match='bar'>

Как регекс, (bar) соответствует строке 'bar', так же как и регекс bar без скобок.

Обращение с группой как с единицей

Метасимвол квантификатора, следующий за группой, действует на все подвыражение, указанное в группе, как на единое целое.

Например, следующий пример соответствует одному или нескольким вхождениям строки 'bar':

>>> re.search('(bar)+', 'foo bar baz')
<_sre.SRE_Match object; span=(4, 7), match='bar'>
>>> re.search('(bar)+', 'foo barbar baz')
<_sre.SRE_Match object; span=(4, 10), match='barbar'>
>>> re.search('(bar)+', 'foo barbarbarbar baz')
<_sre.SRE_Match object; span=(4, 16), match='barbarbarbar'>

Вот описание разницы между двумя регексами с группирующими скобками и без них:

Regex Interpretation Matches Examples
bar+ The + metacharacter applies only to the character 'r'. 'ba' followed by one or more occurrences of 'r' 'bar'
'barr'
'barrr'
(bar)+ The + metacharacter applies to the entire string 'bar'. One or more occurrences of 'bar' 'bar'
'barbar'
'barbarbar'

Теперь рассмотрим более сложный пример. Регекс (ba[rz]){2,4}(qux)? соответствует 2 до 4 вхождениям либо 'bar', либо 'baz', за которыми по желанию следует 'qux':

>>> re.search('(ba[rz]){2,4}(qux)?', 'bazbarbazqux')
<_sre.SRE_Match object; span=(0, 12), match='bazbarbazqux'>
>>> re.search('(ba[rz]){2,4}(qux)?', 'barbar')
<_sre.SRE_Match object; span=(0, 6), match='barbar'>

В следующем примере показано, что можно вложить группирующие скобки:

>>> re.search('(foo(bar)?)+(\d\d\d)?', 'foofoobar')
<_sre.SRE_Match object; span=(0, 9), match='foofoobar'>
>>> re.search('(foo(bar)?)+(\d\d\d)?', 'foofoobar123')
<_sre.SRE_Match object; span=(0, 12), match='foofoobar123'>
>>> re.search('(foo(bar)?)+(\d\d\d)?', 'foofoo123')
<_sre.SRE_Match object; span=(0, 9), match='foofoo123'>

Регекс (foo(bar)?)+(\d\d\d)? довольно сложный, поэтому давайте разобьем его на более мелкие части:

Regex Matches
foo(bar)? 'foo' optionally followed by 'bar'
(foo(bar)?)+ One or more occurrences of the above
\d\d\d Three decimal digit characters
(\d\d\d)? Zero or one occurrences of the above

Сложите все вместе, и вы получите: по крайней мере одно вхождение 'foo', за которым по выбору следует 'bar', и все они по выбору сопровождаются тремя десятичными цифрами.

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

Захват групп

Группировка - не единственная полезная цель, которой служат конструкции группировки. Большинство (но не все) конструкций группировки также фиксируют часть строки поиска, которая соответствует группе. Захваченную часть можно извлечь или сослаться на нее позже несколькими различными способами.

Помните объект соответствия, который возвращает re.search()? Для объекта match определены два метода, которые предоставляют доступ к захваченным группам: .groups() и .group().

m.groups()

Возвращает кортеж, содержащий все захваченные группы из регекса.

Рассмотрим этот пример:

>>> m = re.search('(\w+),(\w+),(\w+)', 'foo,quux,baz')
>>> m
<_sre.SRE_Match object; span=(0, 12), match='foo:quux:baz'>

Каждое из трех выражений (\w+) соответствует последовательности символов слова. Полный регекс (\w+),(\w+),(\w+) разбивает строку поиска на три лексемы, разделенные запятыми.

Поскольку в выражениях (\w+) используются группировочные скобки, соответствующие совпадающие лексемы захватываются. Для доступа к захваченным совпадениям можно использовать .groups(), который возвращает кортеж, содержащий все захваченные совпадения в порядке:

>>> m.groups()
('foo', 'quux', 'baz')

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

m.group(<n>)

Возвращает строку, содержащую <n>th захваченное совпадение.

При одном аргументе .group() возвращает одно захваченное совпадение. Обратите внимание, что аргументы основаны на единицах, а не на нулях. Так, m.group(1) относится к первому захваченному совпадению, m.group(2) - ко второму, и так далее:

>>> m = re.search('(\w+),(\w+),(\w+)', 'foo,quux,baz')
>>> m.groups()
('foo', 'quux', 'baz')

>>> m.group(1)
'foo'
>>> m.group(2)
'quux'
>>> m.group(3)
'baz'

Поскольку нумерация захваченных спичек основана на единицах, и нет ни одной группы с нулевым номером, m.group(0) имеет особое значение:

>>> m.group(0)
'foo,quux,baz'
>>> m.group()
'foo,quux,baz'

m.group(0) возвращает все совпадение, а m.group() делает то же самое.

m.group(<n1>, <n2>, ...)

Возвращает кортеж, содержащий указанные захваченные совпадения.

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

>>> m.groups()
('foo', 'quux', 'baz')

>>> m.group(2, 3)
('quux', 'baz')
>>> m.group(3, 2, 1)
('baz', 'quux', 'foo')

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

>>> m.group(3, 2, 1)
('baz', 'qux', 'foo')
>>> (m.group(3), m.group(2), m.group(1))
('baz', 'qux', 'foo')

Два показанных утверждения функционально эквивалентны.

Отзывы

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

\<n>

Совпадает с содержимым ранее захваченной группы.

В regex в Python последовательность \<n>, где <n> - целое число от 1 до 99, соответствует содержимому захваченной группы <n>th.

Вот regex, который соответствует слову, за которым следует запятая, а затем снова то же самое слово:

 1>>> regex = r'(\w+),\1'
 2
 3>>> m = re.search(regex, 'foo,foo')
 4>>> m
 5<_sre.SRE_Match object; span=(0, 7), match='foo,foo'>
 6>>> m.group(1)
 7'foo'
 8
 9>>> m = re.search(regex, 'qux,qux')
10>>> m
11<_sre.SRE_Match object; span=(0, 7), match='qux,qux'>
12>>> m.group(1)
13'qux'
14
15>>> m = re.search(regex, 'foo,qux')
16>>> print(m)
17None

В первом примере, в строке 3, (\w+) совпадает с первым экземпляром строки 'foo' и сохраняет его в качестве первой захваченной группы. Запятая совпадает буквально. Затем \1 является обратной ссылкой на первую захваченную группу и снова совпадает с 'foo'. Второй пример, в строке 9, идентичен, за исключением того, что (\w+) вместо 'qux' соответствует

В последнем примере, в строке 15, нет совпадения, потому что то, что идет перед запятой, не совпадает с тем, что идет после нее, поэтому обратная ссылка \1 не совпадает.

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

Рассмотрим этот пример:

>>> print(re.search('([a-z])#\1', 'd#d'))
None

Регекс ([a-z])#\1 соответствует строчной букве, за которой следует '#', за которой следует такая же строчная буква. В данном случае строка 'd#d', которая должна совпасть. Но совпадение не происходит, потому что Python неправильно интерпретирует обратную ссылку \1 как символ, восьмеричное значение которого равно единице:

>>> oct(ord('\1'))
'0o1'

Вы добьетесь правильного совпадения, если укажете regex в виде необработанной строки:

>>> re.search(r'([a-z])#\1', 'd#d')
<_sre.SRE_Match object; span=(0, 3), match='d#d'>

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

Нумерованные обратные ссылки основаны на единице, как и аргументы к .group(). Только первые девяносто девять захваченных групп доступны по обратной ссылке. Интерпретатор будет воспринимать \100 как символ '@', восьмеричное значение которого равно 100.

Другие конструкции группировки

<<<Последовательность метасимволов

, показанная (<regex>) выше, является наиболее простым способом выполнения группировки в regex в Python. В следующем разделе вы познакомитесь с некоторыми усовершенствованными конструкциями группировки, позволяющими настраивать, когда и как происходит группировка.

(?P<name><regex>)

Создает именованную захваченную группу.

Эта последовательность метасимволов похожа на группировку круглых скобок, поскольку создает группу соответствия <regex>, доступную через объект match или последующую обратную ссылку. Разница в данном случае заключается в том, что вы ссылаетесь на группу соответствия по заданному символу <name>, а не по номеру.

Ранее вы видели этот пример с тремя захваченными группами, пронумерованными 1, 2 и 3:

>>> m = re.search('(\w+),(\w+),(\w+)', 'foo,quux,baz')
>>> m.groups()
('foo', 'quux', 'baz')

>>> m.group(1, 2, 3)
('foo', 'quux', 'baz')

Следующее эффективно делает то же самое, за исключением того, что группы имеют символические имена w1, w2 и w3:

>>> m = re.search('(?P<w1>\w+),(?P<w2>\w+),(?P<w3>\w+)', 'foo,quux,baz')
>>> m.groups()
('foo', 'quux', 'baz')

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

>>> m.group('w1')
'foo'
>>> m.group('w3')
'baz'
>>> m.group('w1', 'w2', 'w3')
('foo', 'quux', 'baz')

При желании вы все еще можете получить доступ к группам с символическими именами по номеру:

>>> m = re.search('(?P<w1>\w+),(?P<w2>\w+),(?P<w3>\w+)', 'foo,quux,baz')

>>> m.group('w1')
'foo'
>>> m.group(1)
'foo'

>>> m.group('w1', 'w2', 'w3')
('foo', 'quux', 'baz')
>>> m.group(1, 2, 3)
('foo', 'quux', 'baz')

Любой <name>, указанный с помощью этой конструкции, должен соответствовать правилам для Python-идентификатора, и каждый <name> может встречаться только один раз в одном regex.

(?P=<name>)

Совпадает с содержимым ранее захваченной именованной группы.

Последовательность метасимволов (?P=<name>) представляет собой обратную ссылку, аналогичную \<n>, за исключением того, что она ссылается на именованную, а не пронумерованную группу.

Вот пример, приведенный выше, в котором используется нумерованная обратная ссылка для соответствия слову, за которым следует запятая, а затем снова то же самое слово:

>>> m = re.search(r'(\w+),\1', 'foo,foo')
>>> m
<_sre.SRE_Match object; span=(0, 7), match='foo,foo'>
>>> m.group(1)
'foo'

Следующий код делает то же самое, используя именованную группу и обратную ссылку:

>>> m = re.search(r'(?P<word>\w+),(?P=word)', 'foo,foo')
>>> m
<_sre.SRE_Match object; span=(0, 7), match='foo,foo'>
>>> m.group('word')
'foo'

(?P=<word>\w+) совпадает с 'foo' и сохраняется как захваченная группа с именем word. Опять же, запятая совпадает буквально. Затем (?P=word) является обратной ссылкой на именованный захват и снова совпадает с 'foo'.

Примечание: Угловые скобки (< и >) необходимы вокруг name при создании именованной группы, но не при последующем обращении к ней, либо с помощью обратной ссылки, либо с помощью .group():

>>> m = re.match(r'(?P<num>\d+)\.(?P=num)', '135.135')
>>> m
<_sre.SRE_Match object; span=(0, 7), match='135.135'>

>>> m.group('num')
'135'

Здесь (?P<num>\d+) создает захваченную группу. Но соответствующая обратная ссылка (?P=num) без угловых скобок.

(?:<regex>)

Создает группу без захвата.

(?:<regex>) так же, как и (<regex>), совпадает с указанным <regex>. Но (?:<regex>) не фиксирует совпадение для последующего извлечения:

>>> m = re.search('(\w+),(?:\w+),(\w+)', 'foo,quux,baz')
>>> m.groups()
('foo', 'baz')

>>> m.group(1)
'foo'
>>> m.group(2)
'baz'

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

Почему вы хотите определить группу, но не захватить ее?

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

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

(?(<n>)<yes-regex>|<no-regex>)
(?(<name>)<yes-regex>|<no-regex>)

Определяет условное соответствие.

Условное совпадение соответствует одному из двух указанных регексов в зависимости от того, существует ли данная группа:

  • (?(<n>)<yes-regex>|<no-regex>) сопоставляется с <yes-regex>, если существует группа с номером <n>. В противном случае она совпадает с <no-regex>.

  • (?(<name>)<yes-regex>|<no-regex>) совпадает с <yes-regex>, если существует группа с номером <name>. В противном случае она совпадает с <no-regex>.

Условные соответствия лучше проиллюстрировать на примере. Рассмотрим следующий regex:

regex = r'^(###)?foo(?(1)bar|baz)'

Ниже приведены части этого регекса с некоторыми пояснениями:

  1. ^(###)? указывает, что строка поиска может начинаться с '###'. Если это так, то группировочные скобки вокруг ### создадут группу, пронумерованную 1. В противном случае такой группы не будет.
  2. Следующая часть, foo, буквально соответствует строке 'foo'.
  3. И, наконец, (?(1)bar|baz) совпадает с 'bar', если группа 1 существует, и 'baz', если не существует.

Следующие блоки кода демонстрируют использование приведенного выше регекса в нескольких различных фрагментах кода Python:

Пример 1:

>>> re.search(regex, '###foobar')
<_sre.SRE_Match object; span=(0, 9), match='###foobar'>

Поисковая строка '###foobar' начинается с '###', поэтому синтаксический анализатор создает группу с номером 1. Условное совпадение происходит с 'bar', которое совпадает с

Пример 2:

>>> print(re.search(regex, '###foobaz'))
None

Поисковая строка '###foobaz' начинается с '###', поэтому синтаксический анализатор создает группу с номером 1. Затем условное совпадение производится с 'bar', которое не совпадает.

Пример 3:

>>> print(re.search(regex, 'foobar'))
None

Строка поиска 'foobar' не начинается с '###', поэтому не существует группы с номером 1. Тогда условное совпадение происходит с 'baz', которое не совпадает.

Пример 4:

>>> re.search(regex, 'foobaz')
<_sre.SRE_Match object; span=(0, 6), match='foobaz'>

Строка поиска 'foobaz' не начинается с '###', поэтому не существует группы с номером 1. Тогда условное совпадение происходит с 'baz', что соответствует.

Вот еще одно условное соответствие с использованием именованной группы вместо нумерованной:

>>> regex = r'^(?P<ch>\W)?foo(?(ch)(?P=ch)|)$'

Этот регекс соответствует строке 'foo', которой предшествует один символ, не являющийся словом, и за которой следует такой же символ, не являющийся словом, или строке 'foo' самой по себе.

Опять же, давайте разделим это на части:

Regex Matches
^ The start of the string
(?P<ch>\W) A single non-word character, captured in a group named ch
(?P<ch>\W)? Zero or one occurrences of the above
foo The literal string 'foo'
(?(ch)(?P=ch)|) The contents of the group named ch if it exists, or the empty string if it doesn’t
$ The end of the string

Если перед 'foo' стоит символ, не являющийся словом, то синтаксический анализатор создает группу с именем ch, содержащую этот символ. Затем условное соответствие сопоставляется с <yes-regex>, который является (?P=ch), снова тем же самым символом. Это означает, что тот же символ должен также следовать за 'foo', чтобы все совпадение было успешным.

Если перед 'foo' не стоит символ, не являющийся словом, то синтаксический анализатор не создает группу ch. <no-regex> - это пустая строка, а значит, после 'foo' не должно быть ничего, чтобы совпадение было успешным. Поскольку ^ и $ связывают весь регекс, строка должна быть точно равна 'foo'.

Вот несколько примеров поиска с использованием этого регекса в коде Python:

 1>>> re.search(regex, 'foo')
 2<_sre.SRE_Match object; span=(0, 3), match='foo'>
 3>>> re.search(regex, '#foo#')
 4<_sre.SRE_Match object; span=(0, 5), match='#foo#'>
 5>>> re.search(regex, '@foo@')
 6<_sre.SRE_Match object; span=(0, 5), match='@foo@'>
 7
 8>>> print(re.search(regex, '#foo'))
 9None
10>>> print(re.search(regex, 'foo@'))
11None
12>>> print(re.search(regex, '#foo@'))
13None
14>>> print(re.search(regex, '@foo#'))
15None

На строке 1, 'foo' находится сам по себе. На строках 3 и 5 один и тот же несловарный символ предшествует и следует за 'foo'. Как и было объявлено, эти совпадения успешны.

В остальных случаях совпадения заканчиваются неудачей.

Условные регексы в Python довольно эзотеричны и сложны для работы. Если вы когда-нибудь найдете причину для их использования, то, вероятно, вы сможете достичь той же цели с помощью нескольких отдельных вызовов re.search(), и ваш код будет менее сложным для чтения и понимания.

Утверждения Lookahead и Lookbehind

Утверждения Lookahead и lookbehind определяют успех или неудачу regex-сопоставления в Python на основе того, что находится позади (слева) или впереди (справа) текущей позиции парсера в строке поиска.

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

(?=<lookahead_regex>)

Создает утверждение с положительным заблаговременным ожиданием.

(?=<lookahead_regex>) утверждает, что то, что следует за текущей позицией regex-парсера, должно соответствовать <lookahead_regex>:

>>> re.search('foo(?=[a-z])', 'foobar')
<_sre.SRE_Match object; span=(0, 3), match='foo'>

Утверждение lookahead (?=[a-z]) указывает, что то, что следует за 'foo', должно быть символом алфавита в нижнем регистре. В данном случае это символ 'b', поэтому совпадение найдено.

В следующем примере, с другой стороны, заблаговременное ожидание не работает. Следующим символом после 'foo' является '1', поэтому совпадения нет:

>>> print(re.search('foo(?=[a-z])', 'foo123'))
None

Уникальность lookahead заключается в том, что часть поисковой строки, которая соответствует <lookahead_regex>, не потребляется и не является частью возвращаемого объекта match.

Взгляните еще раз на первый пример:

>>> re.search('foo(?=[a-z])', 'foobar')
<_sre.SRE_Match object; span=(0, 3), match='foo'>

Парсер regex смотрит вперед только на 'b', который следует за 'foo', но пока не переходит на него. Вы можете сказать, что 'b' не считается частью соответствия, потому что объект соответствия отображает match='foo'.

Сравните это с аналогичным примером, в котором используются группирующие скобки без заставки:

>>> re.search('foo([a-z])', 'foobar')
<_sre.SRE_Match object; span=(0, 4), match='foob'>

На этот раз регекс поглощает 'b', и он становится частью конечного соответствия.

Вот еще один пример, иллюстрирующий, чем lookahead отличается от обычного regex в Python:

 1>>> m = re.search('foo(?=[a-z])(?P<ch>.)', 'foobar')
 2>>> m.group('ch')
 3'b'
 4
 5>>> m = re.search('foo([a-z])(?P<ch>.)', 'foobar')
 6>>> m.group('ch')
 7'a'

В первом поиске, на строке 1, синтаксический анализатор работает следующим образом:

  1. Первая часть регекса, foo, соответствует и потребляет 'foo' из строки поиска 'foobar'.
  2. Следующая часть, (?=[a-z]), представляет собой заголовок, который соответствует 'b', но синтаксический анализатор не продвигается дальше 'b'.
  3. И наконец, (?P<ch>.) соответствует следующему доступному одиночному символу, который является 'b', и записывает его в группу с именем ch.

Вызов m.group('ch') подтверждает, что группа с именем ch содержит 'b'.

Сравните это с поиском в строке 5, который не содержит заголовок поиска:

  1. Как и в первом примере, первая часть regex, foo, соответствует и потребляет 'foo' из строки поиска 'foobar'.
  2. Следующая часть, ([a-z]), соответствует и потребляет 'b', и парсер продвигается дальше 'b'.
  3. Наконец, (?P<ch>.) соответствует следующему доступному одиночному символу, которым теперь является 'a'.

m.group('ch') подтверждает, что в данном случае группа с именем ch содержит 'a'.

(?!<lookahead_regex>)

Создает утверждение с отрицательным опережением.

(?!<lookahead_regex>) утверждает, что то, что следует за текущей позицией regex парсера, должно не соответствовать <lookahead_regex>.

Вот примеры положительного lookahead, которые вы видели ранее, а также их аналоги с отрицательным lookahead:

 1>>> re.search('foo(?=[a-z])', 'foobar')
 2<_sre.SRE_Match object; span=(0, 3), match='foo'>
 3>>> print(re.search('foo(?![a-z])', 'foobar'))
 4None
 5
 6>>> print(re.search('foo(?=[a-z])', 'foo123'))
 7None
 8>>> re.search('foo(?![a-z])', 'foo123')
 9<_sre.SRE_Match object; span=(0, 3), match='foo'>

Утверждения с отрицательным зазором в строках 3 и 8 предусматривают, что то, что следует за 'foo', не должно быть строчным алфавитным символом. Это не удается в строке 3, но удается в строке 8. Это противоположно тому, что произошло с соответствующими утверждениями с положительным зазором.

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

(?<=<lookbehind_regex>)

Создает положительное утверждение lookbehind.

(?<=<lookbehind_regex>) утверждает, что то, что предшествует текущей позиции regex-парсера, должно соответствовать <lookbehind_regex>.

В следующем примере утверждение lookbehind указывает, что 'foo' должен предшествовать 'bar':

>>> re.search('(?<=foo)bar', 'foobar')
<_sre.SRE_Match object; span=(3, 6), match='bar'>

В данном случае это так, поэтому совпадение проходит успешно. Как и в случае с утверждениями lookahead, часть строки поиска, соответствующая lookbehind, не становится частью конечного соответствия.

Следующий пример не соответствует, потому что lookbehind требует, чтобы 'qux' предшествовал 'bar':

>>> print(re.search('(?<=qux)bar', 'foobar'))
None

Существует ограничение на утверждения lookbehind, которое не распространяется на утверждения lookahead. Утверждение <lookbehind_regex> в утверждении lookbehind должно указывать соответствие фиксированной длины.

Например, следующее не разрешено, потому что длина строки, сопоставленной с a+, является неопределенной:

>>> re.search('(?<=a+)def', 'aaadef')
Traceback (most recent call last):
  File "<pyshell#72>", line 1, in <module>
    re.search('(?<=a+)def', 'aaadef')
  File "C:\Python36\lib\re.py", line 182, in search
    return _compile(pattern, flags).search(string)
  File "C:\Python36\lib\re.py", line 301, in _compile
    p = sre_compile.compile(pattern, flags)
  File "C:\Python36\lib\sre_compile.py", line 566, in compile
    code = _code(p, flags)
  File "C:\Python36\lib\sre_compile.py", line 551, in _code
    _compile(code, p.data, flags)
  File "C:\Python36\lib\sre_compile.py", line 160, in _compile
    raise error("look-behind requires fixed-width pattern")
sre_constants.error: look-behind requires fixed-width pattern

Это, однако, нормально:

>>> re.search('(?<=a{3})def', 'aaadef')
<_sre.SRE_Match object; span=(3, 6), match='def'>

Все, что соответствует a{3}, будет иметь фиксированную длину три, поэтому a{3} допустимо в утверждении lookbehind.

(?<!<lookbehind_regex>)

Создает отрицательное утверждение lookbehind.

(?<!<lookbehind_regex>) утверждает, что то, что предшествует текущей позиции regex парсера, должно не соответствовать <lookbehind_regex>:

>>> print(re.search('(?<!foo)bar', 'foobar'))
None

>>> re.search('(?<!qux)bar', 'foobar')
<_sre.SRE_Match object; span=(3, 6), match='bar'>

Как и в случае с положительным утверждением lookbehind, <lookbehind_regex> должен указывать соответствие фиксированной длины.

Разные метахарактеры

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

(?#...)

Указывает комментарий.

Парсер regex игнорирует все, что содержится в последовательности (?#...):

>>> re.search('bar(?#This is a comment) *baz', 'foo bar baz qux')
<_sre.SRE_Match object; span=(4, 11), match='bar baz'>

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

Вертикальный стержень, или труба (|)

Определяет набор альтернатив, по которым будет производиться поиск.

An expression of the form <regex1>|<regex2>|...|<regexn> matches at most one of the specified <regexi> expressions:

>>> re.search('foo|bar|baz', 'bar')
<_sre.SRE_Match object; span=(0, 3), match='bar'>

>>> re.search('foo|bar|baz', 'baz')
<_sre.SRE_Match object; span=(0, 3), match='baz'>

>>> print(re.search('foo|bar|baz', 'quux'))
None

Здесь foo|bar|baz будет соответствовать любому из 'foo', 'bar' или 'baz'. Вы можете разделить любое количество регексов с помощью |.

Альтернатива - не жадность. Парсер regex просматривает выражения, разделенные символом |, в порядке слева направо и возвращает первое найденное совпадение. Остальные выражения не проверяются, даже если одно из них даст более длинное совпадение:

 1>>> re.search('foo', 'foograult')
 2<_sre.SRE_Match object; span=(0, 3), match='foo'>
 3>>> re.search('grault', 'foograult')
 4<_sre.SRE_Match object; span=(3, 9), match='grault'>
 5
 6>>> re.search('foo|grault', 'foograult')
 7<_sre.SRE_Match object; span=(0, 3), match='foo'>

В этом случае шаблон, указанный в строке 6, 'foo|grault', будет соответствовать либо 'foo', либо 'grault'. Возвращенное совпадение - 'foo', потому что оно появляется первым при сканировании слева направо, хотя 'grault' было бы более длинным совпадением.

Вы можете комбинировать чередование, группировку и любые другие метасимволы, чтобы достичь любого уровня сложности, который вам нужен. В следующем примере (foo|bar|baz)+ означает последовательность из одной или нескольких строк 'foo', 'bar' или 'baz':

>>> re.search('(foo|bar|baz)+', 'foofoofoo')
<_sre.SRE_Match object; span=(0, 9), match='foofoofoo'>
>>> re.search('(foo|bar|baz)+', 'bazbazbazbaz')
<_sre.SRE_Match object; span=(0, 12), match='bazbazbazbaz'>
>>> re.search('(foo|bar|baz)+', 'barbazfoo')
<_sre.SRE_Match object; span=(0, 9), match='barbazfoo'>

В следующем примере ([0-9]+|[a-f]+) означает последовательность из одной или нескольких десятичных цифр или последовательность из одного или нескольких символов 'a-f':

>>> re.search('([0-9]+|[a-f]+)', '456')
<_sre.SRE_Match object; span=(0, 3), match='456'>
>>> re.search('([0-9]+|[a-f]+)', 'ffda')
<_sre.SRE_Match object; span=(0, 4), match='ffda'>

Со всеми метасимволами, которые поддерживает модуль re, небо - практически предел.

Вот и все, друзья!

На этом мы закончим знакомство с метасимволами regex, поддерживаемыми модулем Python re. (На самом деле, это еще не все - есть еще пара отступников, о которых вы узнаете ниже, при обсуждении флагов.)

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

Если вы новичок в регексах и хотите попрактиковаться в работе с ними, или если вы разрабатываете приложение, использующее регекс, и хотите протестировать его в интерактивном режиме, загляните на сайт Regular Expressions 101. Это очень круто!

Модифицированное сопоставление регулярных выражений с флагами

Большинство функций в модуле re принимают необязательный аргумент <flags>. В их число входит функция, с которой вы уже хорошо знакомы, re.search().

re.search(<regex>, <string>, <flags>)

Проверяет строку на совпадение с regex, применяя указанный модификатор <flags>.

Флаги изменяют поведение синтаксического анализа regex, позволяя вам еще больше усовершенствовать поиск шаблонов.

Поддерживаемые флаги регулярных выражений

В таблице ниже приведены краткие сведения о доступных флагах. Все флаги, кроме re.DEBUG, имеют короткое однобуквенное имя, а также более длинное полнословное имя:

Short Name Long Name Effect
re.I re.IGNORECASE Makes matching of alphabetic characters case-insensitive
re.M re.MULTILINE Causes start-of-string and end-of-string anchors to match embedded newlines
re.S re.DOTALL Causes the dot metacharacter to match a newline
re.X re.VERBOSE Allows inclusion of whitespace and comments within a regular expression
---- re.DEBUG Causes the regex parser to display debugging information to the console
re.A re.ASCII Specifies ASCII encoding for character classification
re.U re.UNICODE Specifies Unicode encoding for character classification
re.L                             re.LOCALE Specifies encoding for character classification based on the current locale

В следующих разделах более подробно описывается, как эти флаги влияют на поведение при подборе.

re.I
re.IGNORECASE

Делает совпадение нечувствительным к регистру.

Когда действует IGNORECASE, совпадение символов не зависит от регистра:

 1>>> re.search('a+', 'aaaAAA')
 2<_sre.SRE_Match object; span=(0, 3), match='aaa'>
 3>>> re.search('A+', 'aaaAAA')
 4<_sre.SRE_Match object; span=(3, 6), match='AAA'>
 5
 6>>> re.search('a+', 'aaaAAA', re.I)
 7<_sre.SRE_Match object; span=(0, 6), match='aaaAAA'>
 8>>> re.search('A+', 'aaaAAA', re.IGNORECASE)
 9<_sre.SRE_Match object; span=(0, 6), match='aaaAAA'>

При поиске в строке 1, a+ соответствует только первым трем символам 'aaaAAA'. Аналогично, на строке 3, A+ соответствует только трем последним символам. Но в последующих поисках синтаксический анализатор игнорирует регистр, поэтому и a+, и A+ соответствуют всей строке.

IGNORECASE также влияет на алфавитное соответствие с использованием классов символов:

>>> re.search('[a-z]+', 'aBcDeF')
<_sre.SRE_Match object; span=(0, 1), match='a'>
>>> re.search('[a-z]+', 'aBcDeF', re.I)
<_sre.SRE_Match object; span=(0, 6), match='aBcDeF'>

Когда регистр значителен, самая длинная часть 'aBcDeF', с которой совпадает [a-z]+, - это только начальная 'a'. Указание re.I делает поиск нечувствительным к регистру, поэтому [a-z]+ соответствует всей строке.

re.M
re.MULTILINE

Приводит к тому, что якоря начала и конца строки совпадают со встроенными новыми строками.

По умолчанию якоря ^ (начало строки) и $ (конец строки) совпадают только в начале и конце строки поиска:

>>> s = 'foo\nbar\nbaz'

>>> re.search('^foo', s)
<_sre.SRE_Match object; span=(0, 3), match='foo'>
>>> print(re.search('^bar', s))
None
>>> print(re.search('^baz', s))
None

>>> print(re.search('foo$', s))
None
>>> print(re.search('bar$', s))
None
>>> re.search('baz$', s)
<_sre.SRE_Match object; span=(8, 11), match='baz'>

В этом случае, несмотря на то, что поисковая строка 'foo\nbar\nbaz' содержит встроенные символы новой строки, только 'foo' соответствует при привязке к началу строки, и только 'baz' соответствует при привязке к концу.

Однако если в строке есть встроенные новые строки, можно считать, что она состоит из нескольких внутренних строк. В этом случае, если установлен флаг MULTILINE, метасимволы привязки ^ и $ также соответствуют внутренним строкам:

  • ^ совпадает с началом строки или с началом любой строки внутри строки (то есть сразу после новой строки).
  • $ совпадает с концом строки или с концом любой строки в строке (непосредственно перед новой строкой).

Ниже приведены те же поисковые запросы, что и выше:

>>> s = 'foo\nbar\nbaz'
>>> print(s)
foo
bar
baz

>>> re.search('^foo', s, re.MULTILINE)
<_sre.SRE_Match object; span=(0, 3), match='foo'>
>>> re.search('^bar', s, re.MULTILINE)
<_sre.SRE_Match object; span=(4, 7), match='bar'>
>>> re.search('^baz', s, re.MULTILINE)
<_sre.SRE_Match object; span=(8, 11), match='baz'>

>>> re.search('foo$', s, re.M)
<_sre.SRE_Match object; span=(0, 3), match='foo'>
>>> re.search('bar$', s, re.M)
<_sre.SRE_Match object; span=(4, 7), match='bar'>
>>> re.search('baz$', s, re.M)
<_sre.SRE_Match object; span=(8, 11), match='baz'>

В строке 'foo\nbar\nbaz' все три символа 'foo', 'bar' и 'baz' встречаются либо в начале, либо в конце строки, либо в начале или в конце строки внутри строки. При установленном флаге MULTILINE все три совпадают при привязке либо к ^, либо к $.

Примечание: Флаг MULTILINE изменяет таким образом только якоря ^ и $. Он не оказывает никакого влияния на якоря \A и \Z:

 1>>> s = 'foo\nbar\nbaz'
 2
 3>>> re.search('^bar', s, re.MULTILINE)
 4<_sre.SRE_Match object; span=(4, 7), match='bar'>
 5>>> re.search('bar$', s, re.MULTILINE)
 6<_sre.SRE_Match object; span=(4, 7), match='bar'>
 7
 8>>> print(re.search('\Abar', s, re.MULTILINE))
 9None
10>>> print(re.search('bar\Z', s, re.MULTILINE))
11None

На строках 3 и 5 якоря ^ и $ диктуют, что 'bar' должен находиться в начале и конце строки. Указание флага MULTILINE делает эти совпадения успешными.

В примерах на строках 8 и 10 вместо этого используются флаги \A и \Z. Видно, что эти совпадения не работают даже при наличии флага MULTILINE.

re.S
re.DOTALL

Приводит метасимвол точки (.) в соответствие с новой строкой.

Помните, что по умолчанию метасимвол точки соответствует любому символу, кроме символа новой строки. Флаг DOTALL снимает это ограничение:

 1>>> print(re.search('foo.bar', 'foo\nbar'))
 2None
 3>>> re.search('foo.bar', 'foo\nbar', re.DOTALL)
 4<_sre.SRE_Match object; span=(0, 7), match='foo\nbar'>
 5>>> re.search('foo.bar', 'foo\nbar', re.S)
 6<_sre.SRE_Match object; span=(0, 7), match='foo\nbar'>

В этом примере в строке 1 метасимвол точки не совпадает с новой строкой в 'foo\nbar'. На строках 3 и 5 действует DOTALL, поэтому точка совпадает с новой строкой. Обратите внимание, что краткое название флага DOTALL - re.S, а не re.D, как можно было бы ожидать.

re.X
re.VERBOSE

Позволяет включать в regex пробельные символы и комментарии.

Флаг VERBOSE задает несколько особых вариантов поведения:

  • Парсер regex игнорирует все пробельные символы, если только они не находятся внутри класса символов или не экранированы обратным слешем.

  • Если регекс содержит символ #, который не содержится в классе символов или не сопровождается обратной косой чертой, то парсер игнорирует его и все символы справа от него.

Что в этом такого? Это позволяет отформатировать регекс в Python так, чтобы он был более читабельным и самодокументирующимся.

Вот пример, показывающий, как это можно использовать. Предположим, вам нужно разобрать телефонные номера, которые имеют следующий формат:

  • Опциональный трехзначный код города, в круглых скобках
  • Опциональный пробел
  • Трехзначный префикс
  • Разделитель (либо '-', либо '.')
  • Четырехзначный номер линии

Следующий regex делает трюк:

>>> regex = r'^(\(\d{3}\))?\s*\d{3}[-.]\d{4}$'

>>> re.search(regex, '414.9229')
<_sre.SRE_Match object; span=(0, 8), match='414.9229'>
>>> re.search(regex, '414-9229')
<_sre.SRE_Match object; span=(0, 8), match='414-9229'>
>>> re.search(regex, '(712)414-9229')
<_sre.SRE_Match object; span=(0, 13), match='(712)414-9229'>
>>> re.search(regex, '(712) 414-9229')
<_sre.SRE_Match object; span=(0, 14), match='(712) 414-9229'>

Но r'^(\(\d{3}\))?\s*\d{3}[-.]\d{4}$' - это же глаз, не так ли? Используя флаг VERBOSE, вы можете написать тот же регекс на Python следующим образом:

>>> regex = r'''^               # Start of string
...             (\(\d{3}\))?    # Optional area code
...             \s*             # Optional whitespace
...             \d{3}           # Three-digit prefix
...             [-.]            # Separator character
...             \d{4}           # Four-digit line number
...             $               # Anchor at end of string
...             '''

>>> re.search(regex, '414.9229', re.VERBOSE)
<_sre.SRE_Match object; span=(0, 8), match='414.9229'>
>>> re.search(regex, '414-9229', re.VERBOSE)
<_sre.SRE_Match object; span=(0, 8), match='414-9229'>
>>> re.search(regex, '(712)414-9229', re.X)
<_sre.SRE_Match object; span=(0, 13), match='(712)414-9229'>
>>> re.search(regex, '(712) 414-9229', re.X)
<_sre.SRE_Match object; span=(0, 14), match='(712) 414-9229'>

<<<Вызовы

и re.search() такие же, как показано выше, так что вы можете видеть, что этот регекс работает так же, как и тот, что был указан ранее. Но понять ее на первый взгляд не так сложно.

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

При использовании флага VERBOSE помните о пробелах, которые должны быть значимыми. Рассмотрим эти примеры:

 1>>> re.search('foo bar', 'foo bar')
 2<_sre.SRE_Match object; span=(0, 7), match='foo bar'>
 3
 4>>> print(re.search('foo bar', 'foo bar', re.VERBOSE))
 5None
 6
 7>>> re.search('foo\ bar', 'foo bar', re.VERBOSE)
 8<_sre.SRE_Match object; span=(0, 7), match='foo bar'>
 9>>> re.search('foo[ ]bar', 'foo bar', re.VERBOSE)
10<_sre.SRE_Match object; span=(0, 7), match='foo bar'>

После всего, что вы видели до этого момента, вы можете задаться вопросом, почему в строке 4 regex foo bar не соответствует строке 'foo bar'. Не совпадает потому, что флаг VERBOSE заставляет синтаксический анализатор игнорировать символ пробела.

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

Как и в случае с флагом DOTALL, обратите внимание, что флаг VERBOSE имеет неинтуитивное короткое название: re.X, а не re.V.

re.DEBUG

Отображает отладочную информацию.

Флаг DEBUG заставляет regex-парсер в Python выводить отладочную информацию о процессе парсинга на консоль:

>>> re.search('foo.bar', 'fooxbar', re.DEBUG)
LITERAL 102
LITERAL 111
LITERAL 111
ANY None
LITERAL 98
LITERAL 97
LITERAL 114
<_sre.SRE_Match object; span=(0, 7), match='fooxbar'>

Когда парсер отображает LITERAL nnn в отладочном выводе, он показывает ASCII-код литерального символа в regex. В данном случае литеральными символами являются 'f', 'o', 'o' и 'b', 'a', 'r',

Вот более сложный пример. Это регекс номера телефона, показанный в обсуждении флага VERBOSE ранее:

>>> regex = r'^(\(\d{3}\))?\s*\d{3}[-.]\d{4}$'

>>> re.search(regex, '414.9229', re.DEBUG)
AT AT_BEGINNING
MAX_REPEAT 0 1
  SUBPATTERN 1 0 0
    LITERAL 40
    MAX_REPEAT 3 3
      IN
        CATEGORY CATEGORY_DIGIT
    LITERAL 41
MAX_REPEAT 0 MAXREPEAT
  IN
    CATEGORY CATEGORY_SPACE
MAX_REPEAT 3 3
  IN
    CATEGORY CATEGORY_DIGIT
IN
  LITERAL 45
  LITERAL 46
MAX_REPEAT 4 4
  IN
    CATEGORY CATEGORY_DIGIT
AT AT_END
<_sre.SRE_Match object; span=(0, 8), match='414.9229'>

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

Глубокое погружение: Отладка разбора регулярных выражений

Как вы уже знаете, последовательность метасимволов {m,n} указывает на определенное количество повторений. Она соответствует любому количеству от m до n повторений того, что ей предшествует:

>>> re.search('x[123]{2,4}y', 'x222y')
<_sre.SRE_Match object; span=(0, 5), match='x222y'>

Вы можете проверить это с помощью флага DEBUG:

>>> re.search('x[123]{2,4}y', 'x222y', re.DEBUG)
LITERAL 120
MAX_REPEAT 2 4
  IN
    LITERAL 49
    LITERAL 50
    LITERAL 51
LITERAL 121
<_sre.SRE_Match object; span=(0, 5), match='x222y'>

MAX_REPEAT 2 4 подтверждает, что regex парсер распознает последовательность метасимволов {2,4} и интерпретирует ее как квантификатор диапазона.

Но, как отмечалось ранее, если пара фигурных скобок в regex в Python содержит что-либо, отличное от действительного числа или числового диапазона, то она теряет свое особое значение.

Вы можете проверить это также:

>>> re.search('x[123]{foo}y', 'x222y', re.DEBUG)
LITERAL 120
IN
  LITERAL 49
  LITERAL 50
  LITERAL 51
LITERAL 123
LITERAL 102
LITERAL 111
LITERAL 111
LITERAL 125
LITERAL 121

Вы видите, что в отладочном выводе нет маркера MAX_REPEAT. Токены LITERAL указывают на то, что синтаксический анализатор рассматривает {foo} буквально, а не как последовательность метасимволов квантификатора. 123, 102, 111, 111 и 125 - коды ASCII для символов в буквенной строке '{foo}'.

Информация, отображаемая флагом DEBUG, может помочь вам в поиске неисправностей, показывая, как парсер интерпретирует ваш regex.

Любопытно, что модуль re не определяет однобуквенную версию флага DEBUG. Вы можете определить свой собственный, если захотите:

>>> import re
>>> re.D
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 're' has no attribute 'D'

>>> re.D = re.DEBUG
>>> re.search('foo', 'foo', re.D)
LITERAL 102
LITERAL 111
LITERAL 111
<_sre.SRE_Match object; span=(0, 3), match='foo'>

Но это может скорее запутать, чем помочь, так как читатели вашего кода могут ошибочно понять, что это сокращение для флага DOTALL. Если вы все же сделали такое назначение, было бы неплохо тщательно его задокументировать.

re.A
re.ASCII
re.U
re.UNICODE
re.L
re.LOCALE

Укажите кодировку символов, используемую для разбора специальных классов символов regex.

Некоторые последовательности метасимволов regex (\w, \W, \b, \B, \d, \D, \s и \S) требуют от вас отнесения символов к определенным классам, таким как слово, цифра или пробел. Флаги этой группы определяют схему кодирования, используемую для отнесения символов к этим классам. Возможные кодировки: ASCII, Unicode или в соответствии с текущей локалью.

Вы кратко познакомились с кодировкой символов и Юникодом в уроке "Строки и символьные данные в Python", где обсуждалась встроенная функция ord(). Для получения более подробной информации ознакомьтесь с этими ресурсами:

Почему кодировка символов так важна в контексте регексов в Python? Вот небольшой пример.

Ранее вы узнали, что \d задает символ одной цифры. В описании последовательности метасимволов \d говорится, что она эквивалентна классу символов [0-9]. Это верно для английского и западноевропейских языков, но для большинства языков мира символы '0' - '9' представляют не все или даже любые цифры.

Например, вот строка, состоящая из трех цифровых символов Деванагари:

>>> s = '\u0967\u096a\u096c'
>>> s
'१४६'

Для того чтобы регекс-парсер правильно учитывал шрифт деванагари, последовательность метасимволов цифр \d должна соответствовать каждому из этих символов.

Консорциум Unicode Consortium создал Unicode для решения этой проблемы. Unicode - это стандарт кодирования символов, разработанный для представления всех мировых систем письма. Все строки в Python 3, включая регексы, по умолчанию имеют кодировку Unicode.

Итак, вернемся к перечисленным выше флагам. Эти флаги помогают определить принадлежность символа к определенному классу, указывая, какая кодировка используется: ASCII, Unicode или текущая локаль:

  • re.U и re.UNICODE указывают кодировку Unicode. По умолчанию используется Unicode, поэтому эти флаги излишни. Они поддерживаются в основном для обратной совместимости.
  • re.A и re.ASCII принудительно определяют кодировку на основе ASCII. Если вы работаете на английском языке, то это происходит в любом случае, поэтому флаг не влияет на то, будет ли найдено совпадение.
  • re.L и re.LOCALE определяют, основываясь на текущей локали. Локаль - устаревшая концепция и не считается надежной. За исключением редких случаев, она вам вряд ли понадобится.

Используя кодировку Unicode по умолчанию, парсер regex должен уметь работать с любым языком, который вы ему подкинете. В следующем примере он правильно распознает каждый из символов в строке '१४६' как цифру:

>>> s = '\u0967\u096a\u096c'
>>> s
'१४६'
>>> re.search('\d+', s)
<_sre.SRE_Match object; span=(0, 3), match='१४६'>

Вот еще один пример, иллюстрирующий, как кодировка символов может повлиять на совпадение regex в Python. Рассмотрим эту строку:

>>> s = 'sch\u00f6n'
>>> s
'schön'

'schön' (немецкое слово, означающее pretty или nice) содержит символ 'ö', который имеет 16-битное шестнадцатеричное значение Юникода 00f6. Этот символ не может быть представлен в традиционном 7-битном ASCII.

Если вы работаете на немецком языке, то вполне обоснованно ожидаете, что парсер regex будет считать все символы в 'schön' символами слова. Но посмотрите, что произойдет, если искать в s символы слов с помощью класса символов \w и принудительно использовать кодировку ASCII:

>>> re.search('\w+', s, re.ASCII)
<_sre.SRE_Match object; span=(0, 3), match='sch'>

Когда вы ограничиваете кодировку ASCII, парсер regex распознает только первые три символа как символы слов. Совпадение останавливается на 'ö'.

С другой стороны, если вы укажете re.UNICODE или позволите кодировке по умолчанию быть Unicode, то все символы в 'schön' будут считаться символами слова:

>>> re.search('\w+', s, re.UNICODE)
<_sre.SRE_Match object; span=(0, 5), match='schön'>
>>> re.search('\w+', s)
<_sre.SRE_Match object; span=(0, 5), match='schön'>

Флаги ASCII и LOCALE доступны на случай, если они понадобятся вам в особых обстоятельствах. Но в целом лучшей стратегией является использование кодировки Unicode по умолчанию. Это позволит корректно обрабатывать любой мировой язык.

Комбинирование <flags> аргументов в вызове функции

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

>>> re.search('^bar', 'FOO\nBAR\nBAZ', re.I|re.M)
<_sre.SRE_Match object; span=(4, 7), match='BAR'>

В этом вызове re.search() используется побитовое ИЛИ для указания флагов IGNORECASE и MULTILINE одновременно.

Установка и очистка флагов в регулярном выражении

Помимо возможности передавать аргумент <flags> в большинстве вызовов функций модуля re, в Python вы также можете изменять значения флагов в regex. Существует две последовательности метасимволов regex, которые предоставляют такую возможность.

(?<flags>)

Устанавливает значение флага (флагов) на время выполнения regex.

Внутри regex последовательность метасимволов (?<flags>) устанавливает указанные флаги для всего выражения.

Значением <flags> является одна или несколько букв из набора a, i, L, m, s, u и x. Вот как они соотносятся с флагами модуля re:

Letter Flags
a re.A     re.ASCII
i re.I     re.IGNORECASE
L re.L     re.LOCALE
m re.M     re.MULTILINE
s re.S     re.DOTALL
u re.U     re.UNICODE
x re.X     re.VERBOSE

Последовательность метасимволов (?<flags>) в целом совпадает с пустой строкой. Она всегда совпадает успешно и не потребляет ничего из строки поиска.

В следующих примерах приведены эквивалентные способы установки флагов IGNORECASE и MULTILINE:

>>> re.search('^bar', 'FOO\nBAR\nBAZ\n', re.I|re.M)
<_sre.SRE_Match object; span=(4, 7), match='BAR'>

>>> re.search('(?im)^bar', 'FOO\nBAR\nBAZ\n')
<_sre.SRE_Match object; span=(4, 7), match='BAR'>

Обратите внимание, что последовательность метасимволов (?<flags>) устанавливает заданный флаг (флаги) для всего регекса, независимо от того, где вы поместили его в выражение:

>>> re.search('foo.bar(?s).baz', 'foo\nbar\nbaz')
<_sre.SRE_Match object; span=(0, 11), match='foo\nbar\nbaz'>

>>> re.search('foo.bar.baz(?s)', 'foo\nbar\nbaz')
<_sre.SRE_Match object; span=(0, 11), match='foo\nbar\nbaz'>

В приведенных выше примерах оба метасимвола точки совпадают с новой строкой, поскольку действует флаг DOTALL. Это верно, даже если (?s) находится в середине или в конце выражения.

Начиная с версии Python 3.7, запрещено указывать (?<flags>) в любом месте regex, кроме начала:

>>> import sys
>>> sys.version
'3.8.0 (default, Oct 14 2019, 21:29:03) \n[GCC 7.4.0]'

>>> re.search('foo.bar.baz(?s)', 'foo\nbar\nbaz')
<stdin>:1: DeprecationWarning: Flags not at the start
    of the expression 'foo.bar.baz(?s)'
<re.Match object; span=(0, 11), match='foo\nbar\nbaz'>

Все равно получится соответствующее совпадение, но вы получите предупреждение.

(?<set_flags>-<remove_flags>:<regex>)

Устанавливает или удаляет значение(я) флага(ов) на время работы группы.

(?<set_flags>-<remove_flags>:<regex>) определяет не захватывающую группу, которая сопоставляется с <regex>. Для <regex>, содержащихся в группе, регекс-парсер устанавливает любые флаги, указанные в <set_flags>, и очищает любые флаги, указанные в <remove_flags>.

Значения для <set_flags> и <remove_flags> чаще всего бывают i, m, s или x.

В следующем примере флаг IGNORECASE установлен для указанной группы:

>>> re.search('(?i:foo)bar', 'FOObar')
<re.Match object; span=(0, 6), match='FOObar'>

Это дает совпадение, поскольку (?i:foo) диктует, что совпадение с 'FOO' не чувствительно к регистру.

Теперь сравните это с таким примером:

>>> print(re.search('(?i:foo)bar', 'FOOBAR'))
None

Как и в предыдущем примере, совпадение с 'FOO' будет успешным, поскольку оно не зависит от регистра. Но, оказавшись за пределами группы, IGNORECASE больше не действует, поэтому совпадение с 'BAR' чувствительно к регистру и не проходит.

Вот пример, демонстрирующий отключение флага для группы:

>>> print(re.search('(?-i:foo)bar', 'FOOBAR', re.IGNORECASE))
None

И снова нет совпадений. Хотя re.IGNORECASE включает сопоставление без учета регистра для всего вызова, последовательность метасимволов (?-i:foo) отключает IGNORECASE на время работы этой группы, поэтому сопоставление с 'FOO' не удается.

Начиная с Python 3.7, вы можете указать u, a или L в качестве <set_flags>, чтобы переопределить кодировку по умолчанию для указанной группы:

>>> s = 'sch\u00f6n'
>>> s
'schön'

>>> # Requires Python 3.7 or later
>>> re.search('(?a:\w+)', s)
<re.Match object; span=(0, 3), match='sch'>
>>> re.search('(?u:\w+)', s)
<re.Match object; span=(0, 5), match='schön'>

Однако таким образом можно задать только кодировку. Удалить ее нельзя:

>>> re.search('(?-a:\w+)', s)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.8/re.py", line 199, in search
    return _compile(pattern, flags).search(string)
  File "/usr/lib/python3.8/re.py", line 302, in _compile
    p = sre_compile.compile(pattern, flags)
  File "/usr/lib/python3.8/sre_compile.py", line 764, in compile
    p = sre_parse.parse(p, flags)
  File "/usr/lib/python3.8/sre_parse.py", line 948, in parse
    p = _parse_sub(source, state, flags & SRE_FLAG_VERBOSE, 0)
  File "/usr/lib/python3.8/sre_parse.py", line 443, in _parse_sub
    itemsappend(_parse(source, state, verbose, nested + 1,
  File "/usr/lib/python3.8/sre_parse.py", line 805, in _parse
    flags = _parse_flags(source, state, char)
  File "/usr/lib/python3.8/sre_parse.py", line 904, in _parse_flags
    raise source.error(msg)
re.error: bad inline flags: cannot turn off flags 'a', 'u' and 'L' at
position 4

u, a и L являются взаимоисключающими. В каждой группе может присутствовать только один из них.

Заключение

На этом вы закончите знакомство с подбором регулярных выражений и модулем re Python. Поздравляем! Вы освоили огромное количество материала.

Теперь вы знаете, как:

  • Используйте re.search() для выполнения regex-сопоставления в Python
  • Создавайте сложные поисковые запросы с использованием regex metacharacters
  • Настройте поведение разбора regex с помощью флагов

Но вы все еще видели только одну функцию в модуле: re.search()! В модуле re есть еще много полезных функций и объектов, которые можно добавить к вашему набору инструментов для сопоставления шаблонов. В следующем уроке серии вы узнаете, что еще может предложить модуль regex в Python.

Вернуться на верх