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

Оглавление

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

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

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

  • Изучите больше функций, помимо re.search(), которые предоставляет модуль re
  • Узнайте, когда и как предварительно скомпилировать regex в Python в объект регулярных выражений
  • Откройте для себя полезные вещи, которые можно делать с объектом match, возвращаемым функциями в модуле re

Готовы? Давайте покопаемся!

Функции модуля re

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

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

Вы можете указать <flags> как позиционный аргумент или как аргумент ключевого слова:

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

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

Доступные функции regex в модуле Python re делятся на следующие три категории:

  1. Функции поиска
  2. Функции подстановки
  3. Утилитарные функции

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

Функции поиска

Функции поиска сканируют строку поиска на предмет одного или нескольких совпадений с указанным regex:

Function Description
re.search() Scans a string for a regex match
re.match() Looks for a regex match at the beginning of a string
re.fullmatch() Looks for a regex match on an entire string
re.findall() Returns a list of all regex matches in a string
re.finditer() Returns an iterator that yields regex matches from a string

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

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

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

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

>>> re.search(r'(\d+)', 'foo123bar')
<_sre.SRE_Match object; span=(3, 6), match='123'>
>>> re.search(r'[a-z]+', '123FOO456', flags=re.IGNORECASE)
<_sre.SRE_Match object; span=(3, 6), match='FOO'>

>>> print(re.search(r'\d+', 'foo.bar'))
None

Функция возвращает объект match, если найдено совпадение, и None в противном случае.

re.match(<regex>, <string>, flags=0)

Ищет regex-соответствие в начале строки.

Это идентично re.search(), за исключением того, что re.search() возвращает совпадение, если <regex> совпадает везде в <string>, тогда как re.match() возвращает совпадение, только если <regex> совпадает в начале из <string>:

>>> re.search(r'\d+', '123foobar')
<_sre.SRE_Match object; span=(0, 3), match='123'>
>>> re.search(r'\d+', 'foo123bar')
<_sre.SRE_Match object; span=(3, 6), match='123'>

>>> re.match(r'\d+', '123foobar')
<_sre.SRE_Match object; span=(0, 3), match='123'>
>>> print(re.match(r'\d+', 'foo123bar'))
None

В приведенном выше примере re.search() совпадает, когда цифры находятся и в начале строки, и в середине, а re.match() совпадает только тогда, когда цифры находятся в начале.

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

 1>>> s = 'foo\nbar\nbaz'
 2
 3>>> re.search('^foo', s)
 4<_sre.SRE_Match object; span=(0, 3), match='foo'>
 5>>> re.search('^bar', s, re.MULTILINE)
 6<_sre.SRE_Match object; span=(4, 7), match='bar'>

Флаг MULTILINE не влияет на re.match() таким образом:

 1>>> s = 'foo\nbar\nbaz'
 2
 3>>> re.match('^foo', s)
 4<_sre.SRE_Match object; span=(0, 3), match='foo'>
 5>>> print(re.match('^bar', s, re.MULTILINE))
 6None

Даже с установленным флагом MULTILINE, re.match() будет соответствовать каретке (^) только в начале <string>, но не в начале строк, содержащихся внутри <string>.

Обратите внимание, что, хотя это и иллюстрирует суть, якорь каретки (^) на строке 3 в приведенном выше примере является лишним. С помощью re.match() совпадения, по сути, всегда привязываются к началу строки.

re.fullmatch(<regex>, <string>, flags=0)

Ищет regex-соответствие во всей строке.

Это аналогично re.search() и re.match(), но re.fullmatch() возвращает совпадение, только если <regex> полностью совпадает с <string>:

 1>>> print(re.fullmatch(r'\d+', '123foo'))
 2None
 3>>> print(re.fullmatch(r'\d+', 'foo123'))
 4None
 5>>> print(re.fullmatch(r'\d+', 'foo123bar'))
 6None
 7>>> re.fullmatch(r'\d+', '123')
 8<_sre.SRE_Match object; span=(0, 3), match='123'>
 9
10>>> re.search(r'^\d+$', '123')
11<_sre.SRE_Match object; span=(0, 3), match='123'>

В вызове на линии 7 строка поиска '123' состоит полностью из цифр от начала до конца. Таким образом, это единственный случай, когда re.fullmatch() возвращает совпадение.

Вызов re.search() на строке 10, в котором регекс \d+ явно привязан к началу и концу строки поиска, функционально эквивалентен.

re.findall(<regex>, <string>, flags=0)

Возвращает список всех совпадений regex в строке.

re.findall(<regex>, <string>) возвращает список всех непересекающихся совпадений <regex> в <string>. Он сканирует поисковую строку слева направо и возвращает все совпадения в порядке их нахождения:

>>> re.findall(r'\w+', '...foo,,,,bar:%$baz//|')
['foo', 'bar', 'baz']

Если <regex> содержит группу захвата, то возвращаемый список содержит только содержимое группы, а не все совпадение:

>>> re.findall(r'#(\w+)#', '#foo#.#bar#.#baz#')
['foo', 'bar', 'baz']

В данном случае указанный regex - #(\w+)#. Соответствующими строками являются '#foo#', '#bar#' и '#baz#'. Но символы хэша (#) не появляются в списке возвратов, потому что они находятся вне группирующих скобок.

Если <regex> содержит более одной группы захвата, то re.findall() возвращает список кортежей, содержащих группы захвата. Длина каждого кортежа равна количеству указанных групп:

 1>>> re.findall(r'(\w+),(\w+)', 'foo,bar,baz,qux,quux,corge')
 2[('foo', 'bar'), ('baz', 'qux'), ('quux', 'corge')]
 3
 4>>> re.findall(r'(\w+),(\w+),(\w+)', 'foo,bar,baz,qux,quux,corge')
 5[('foo', 'bar', 'baz'), ('qux', 'quux', 'corge')]

В приведенном выше примере регекс в строке 1 содержит две группы захвата, поэтому re.findall() возвращает список из трех кортежей, каждый из которых содержит два захваченных совпадения. строка 4 содержит три группы, поэтому возвращаемое значение - список из двух трех кортежей.

re.finditer(<regex>, <string>, flags=0)

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

re.finditer(<regex>, <string>) сканирует <string> на предмет непересекающихся совпадений с <regex> и возвращает итератор, который выдает объекты совпадений из всех найденных. Он сканирует строку поиска слева направо и возвращает совпадения в том порядке, в котором их находит:

>>> it = re.finditer(r'\w+', '...foo,,,,bar:%$baz//|')
>>> next(it)
<_sre.SRE_Match object; span=(3, 6), match='foo'>
>>> next(it)
<_sre.SRE_Match object; span=(10, 13), match='bar'>
>>> next(it)
<_sre.SRE_Match object; span=(16, 19), match='baz'>
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

>>> for i in re.finditer(r'\w+', '...foo,,,,bar:%$baz//|'):
...     print(i)
...
<_sre.SRE_Match object; span=(3, 6), match='foo'>
<_sre.SRE_Match object; span=(10, 13), match='bar'>
<_sre.SRE_Match object; span=(16, 19), match='baz'>

re.findall() и re.finditer() очень похожи, но отличаются в двух отношениях:

  1. re.findall() возвращает список, тогда как re.finditer() возвращает итератор.

  2. Элементы списка, который возвращает re.findall(), являются фактическими совпадающими строками, в то время как элементы, выдаваемые итератором, который возвращает re.finditer(), являются объектами совпадения.

Любая задача, которую вы можете решить с помощью одного из них, скорее всего, удастся и с помощью другого. Какой из них вы выберете, зависит от обстоятельств. Как вы увидите далее в этом учебнике, из объекта match можно получить много полезной информации. Если вам нужна эта информация, то re.finditer(), вероятно, будет лучшим выбором.

Функции подстановки

Функции подстановки заменяют части строки поиска, которые соответствуют заданному regex:

Function Description
re.sub() Scans a string for regex matches, replaces the matching portions of the string with the specified replacement string, and returns the result
re.subn() Behaves just like re.sub() but also returns information regarding the number of substitutions made

И re.sub(), и re.subn() создают новую строку с указанными заменами и возвращают ее. Исходная строка остается неизменной. (Помните, что строки в Python неизменяемы, поэтому эти функции не смогут изменить исходную строку.)

re.sub(<regex>, <repl>, <string>, count=0, flags=0)

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

re.sub(<regex>, <repl>, <string>) находит самые левые непересекающиеся вхождения <regex> в <string>, заменяет каждое совпадение, как указано в <repl>, и возвращает результат. <string> остается неизменным.

<repl> может быть либо строкой, либо функцией, как объясняется ниже.

Замена на строку

Если <repl> - строка, то re.sub() вставляет ее в <string> вместо любых последовательностей, соответствующих <regex>:

 1>>> s = 'foo.123.bar.789.baz'
 2
 3>>> re.sub(r'\d+', '#', s)
 4'foo.#.bar.#.baz'
 5>>> re.sub('[a-z]+', '(*)', s)
 6'(*).123.(*).789.(*)'

На строке 3 строка '#' заменяет последовательности цифр в s. На строке 5 строка '(*)' заменяет последовательности строчных букв. В обоих случаях re.sub() возвращает измененную строку, как и всегда.

re.sub() заменяет пронумерованные обратные ссылки (\<n>) в <repl> на текст соответствующей захваченной группы:

>>> re.sub(r'(\w+),bar,baz,(\w+)',
...        r'\2,bar,baz,\1',
...        'foo,bar,baz,qux')
'qux,bar,baz,foo'

Здесь захваченные группы 1 и 2 содержат 'foo' и 'qux'. В строке замены '\2,bar,baz,\1', 'foo' заменяет \1, а 'qux' заменяет \2.

Вы также можете ссылаться на именованные обратные ссылки, созданные с помощью (?P<name><regex>), в строке замены, используя последовательность метасимволов \g<name>:

>>> re.sub(r'foo,(?P<w1>\w+),(?P<w2>\w+),qux',
...        r'foo,\g<w2>,\g<w1>,qux',
...        'foo,bar,baz,qux')
'foo,baz,bar,qux'

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

>>> re.sub(r'foo,(\w+),(\w+),qux',
...        r'foo,\g<2>,\g<1>,qux',
...        'foo,bar,baz,qux')
'foo,baz,bar,qux'

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

>>> re.sub(r'(\d+)', r'\10', 'foo 123 bar')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.6/re.py", line 191, in sub
    return _compile(pattern, flags).sub(repl, string, count)
  File "/usr/lib/python3.6/re.py", line 326, in _subx
    template = _compile_repl(template, pattern)
  File "/usr/lib/python3.6/re.py", line 317, in _compile_repl
    return sre_parse.parse_template(repl, pattern)
  File "/usr/lib/python3.6/sre_parse.py", line 943, in parse_template
    addgroup(int(this[1:]), len(this) - 1)
  File "/usr/lib/python3.6/sre_parse.py", line 887, in addgroup
    raise s.error("invalid group reference %d" % index, pos)
sre_constants.error: invalid group reference 10 at position 1

Увы, синтаксический анализатор regex в Python интерпретирует \10 как обратную ссылку на десятую захваченную группу, которой в данном случае не существует. Вместо этого вы можете использовать \g<1> для ссылки на группу:

>>> re.sub(r'(\d+)', r'\g<1>0', 'foo 123 bar')
'foo 1230 bar'

Обратная ссылка \g<0> ссылается на текст всего матча. Это справедливо, даже если в <regex>:

нет группирующих скобок.

>>> re.sub(r'\d+', '/\g<0>/', 'foo 123 bar')
'foo /123/ bar'

Если <regex> задает совпадение нулевой длины, то re.sub() подставит <repl> в каждую позицию символа в строке:

>>> re.sub('x*', '-', 'foo')
'-f-o-o-'

В приведенном выше примере регекс x* соответствует любой последовательности нулевой длины, поэтому re.sub() вставляет заменяющую строку в каждую позицию символа в строке - перед первым символом, между каждой парой символов и после последнего символа.

Если re.sub() не находит ни одного совпадения, то всегда возвращает <string> без изменений.

Замена по функции

Если вы указываете <repl> в качестве функции, то re.sub() вызывает эту функцию для каждого найденного совпадения. В качестве аргумента функции передается каждый соответствующий объект совпадения, чтобы предоставить информацию о совпадении. Возвращаемое значение функции становится строкой замены:

>>> def f(match_obj):
...     s = match_obj.group(0)  # The matching string
...
...     # s.isdigit() returns True if all characters in s are digits
...     if s.isdigit():
...         return str(int(s) * 10)
...     else:
...         return s.upper()
...
>>> re.sub(r'\w+', f, 'foo.10.bar.20.baz.30')
'FOO.100.BAR.200.BAZ.300'

В этом примере f() вызывается для каждого матча. В результате re.sub() преобразует каждую буквенно-цифровую часть <string> во все заглавные и умножает каждую числовую часть на 10.

Ограничение количества замен

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

>>> re.sub(r'\w+', 'xxx', 'foo.bar.baz.qux')
'xxx.xxx.xxx.xxx'
>>> re.sub(r'\w+', 'xxx', 'foo.bar.baz.qux', count=2)
'xxx.xxx.baz.qux'

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

re.subn(<regex>, <repl>, <string>, count=0, flags=0)

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

re.subn() идентичен re.sub(), за исключением того, что re.subn() возвращает кортеж, состоящий из измененной строки и количества произведенных замен:

>>> re.subn(r'\w+', 'xxx', 'foo.bar.baz.qux')
('xxx.xxx.xxx.xxx', 4)
>>> re.subn(r'\w+', 'xxx', 'foo.bar.baz.qux', count=2)
('xxx.xxx.baz.qux', 2)

>>> def f(match_obj):
...     m = match_obj.group(0)
...     if m.isdigit():
...         return str(int(m) * 10)
...     else:
...         return m.upper()
...
>>> re.subn(r'\w+', f, 'foo.10.bar.20.baz.30')
('FOO.100.BAR.200.BAZ.300', 6)

Во всех остальных отношениях re.subn() ведет себя так же, как re.sub().

Утилитарные функции

В модуле Python re остались две функции regex, которые вам еще предстоит изучить:

Function Description
re.split() Splits a string into substrings using a regex as a delimiter
re.escape() Escapes characters in a regex

Это функции, которые включают в себя regex-сопоставление, но не попадают ни в одну из категорий, описанных выше.

re.split(<regex>, <string>, maxsplit=0, flags=0)

Разбивает строку на подстроки.

re.split(<regex>, <string>) разбивает <string> на подстроки, используя <regex> в качестве разделителя, и возвращает подстроки в виде списка.

Следующий пример разбивает указанную строку на подстроки, разделенные запятой (,), точкой с запятой (;) или косой чертой (/), окруженные любым количеством пробельных символов:

>>> re.split('\s*[,;/]\s*', 'foo,bar  ;  baz / qux')
['foo', 'bar', 'baz', 'qux']

Если <regex> содержит группы захвата, то в возвращаемый список также входят соответствующие строки-разделители:

>>> re.split('(\s*[,;/]\s*)', 'foo,bar  ;  baz / qux')
['foo', ',', 'bar', '  ;  ', 'baz', ' / ', 'qux']

На этот раз возвращаемый список содержит не только подстроки 'foo', 'bar', 'baz' и 'qux', но и несколько строк-разделителей:

  • ','
  • ' ; '
  • ' / '

Это может быть полезно, если вы хотите разделить <string> на разграниченные лексемы, обработать их каким-либо образом, а затем собрать строку обратно, используя те же разделители, которые первоначально разделяли их:

>>> string = 'foo,bar  ;  baz / qux'
>>> regex = r'(\s*[,;/]\s*)'
>>> a = re.split(regex, string)

>>> # List of tokens and delimiters
>>> a
['foo', ',', 'bar', '  ;  ', 'baz', ' / ', 'qux']

>>> # Enclose each token in <>'s
>>> for i, s in enumerate(a):
...
...     # This will be True for the tokens but not the delimiters
...     if not re.fullmatch(regex, s):
...         a[i] = f'<{s}>'
...

>>> # Put the tokens back together using the same delimiters
>>> ''.join(a)
'<foo>,<bar>  ;  <baz> / <qux>'

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

>>> string = 'foo,bar  ;  baz / qux'
>>> regex = r'(?:\s*[,;/]\s*)'
>>> re.split(regex, string)
['foo', 'bar', 'baz', 'qux']

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

>>> s = 'foo, bar, baz, qux, quux, corge'

>>> re.split(r',\s*', s)
['foo', 'bar', 'baz', 'qux', 'quux', 'corge']
>>> re.split(r',\s*', s, maxsplit=3)
['foo', 'bar', 'baz', 'qux, quux, corge']

Явное указание maxsplit=0 эквивалентно его полному отсутствию. Если maxsplit отрицательно, то re.split() возвращает <string> без изменений (на случай, если вы искали довольно изощренный способ вообще ничего не делать).

Если <regex> содержит группы захвата так, что возвращаемый список включает разделители, а <regex> совпадает с началом <string>, то re.split() помещает пустую строку в качестве первого элемента возвращаемого списка. Аналогично, последним элементом в возвращаемом списке будет пустая строка, если <regex> совпадает с концом <string>:

>>> re.split('(/)', '/foo/bar/baz/')
['', '/', 'foo', '/', 'bar', '/', 'baz', '/', '']

В данном случае разделителем <regex> является одиночный символ косой черты (/). В некотором смысле, слева от первого разделителя и справа от последнего находится пустая строка. Поэтому логично, что re.split() помещает пустые строки в качестве первого и последнего элементов возвращаемого списка.

re.escape(<regex>)

Удаляет символы в regex.

re.escape(<regex>) возвращает копию <regex> с каждым символом, не являющимся словом (любым, кроме буквы, цифры или символа подчеркивания), перед которым ставится обратная косая черта.

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

 1>>> print(re.match('foo^bar(baz)|qux', 'foo^bar(baz)|qux'))
 2None
 3>>> re.match('foo\^bar\(baz\)\|qux', 'foo^bar(baz)|qux')
 4<_sre.SRE_Match object; span=(0, 16), match='foo^bar(baz)|qux'>
 5
 6>>> re.escape('foo^bar(baz)|qux') == 'foo\^bar\(baz\)\|qux'
 7True
 8>>> re.match(re.escape('foo^bar(baz)|qux'), 'foo^bar(baz)|qux')
 9<_sre.SRE_Match object; span=(0, 16), match='foo^bar(baz)|qux'>

В этом примере нет совпадения в строке 1, потому что регекс 'foo^bar(baz)|qux' содержит специальные символы, которые ведут себя как метасимволы. В строке 3 они явно экранированы обратными косыми чертами, поэтому совпадение происходит. Строки 6 и 8 демонстрируют, что того же эффекта можно добиться, используя re.escape().

Скомпилированные объекты Regex в Python

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

re.compile(<regex>, flags=0)

Компилирует regex в объект регулярного выражения.

re.compile(<regex>) компилирует <regex> и возвращает соответствующий объект регулярного выражения. Если вы включите значение <flags>, то соответствующие флаги будут применяться ко всем поискам, выполняемым с этим объектом.

Существует два способа использования скомпилированного объекта регулярного выражения. Вы можете указать его в качестве первого аргумента функций модуля re вместо <regex>:

re_obj = re.compile(<regex>, <flags>)
result = re.search(re_obj, <string>)

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

re_obj = re.compile(<regex>, <flags>)
result = re_obj.search(<string>)

Оба приведенных выше примера эквивалентны следующему:

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

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

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

>>> re_obj = re.compile(r'(\d+)')
>>> re.search(re_obj, 'foo123bar')
<_sre.SRE_Match object; span=(3, 6), match='123'>
>>> re_obj.search('foo123bar')
<_sre.SRE_Match object; span=(3, 6), match='123'>

Вот еще один вариант, который также использует флаг IGNORECASE:

 1>>> r1 = re.search('ba[rz]', 'FOOBARBAZ', flags=re.I)
 2
 3>>> re_obj = re.compile('ba[rz]', flags=re.I)
 4>>> r2 = re.search(re_obj, 'FOOBARBAZ')
 5>>> r3 = re_obj.search('FOOBARBAZ')
 6
 7>>> r1
 8<_sre.SRE_Match object; span=(3, 6), match='BAR'>
 9>>> r2
10<_sre.SRE_Match object; span=(3, 6), match='BAR'>
11>>> r3
12<_sre.SRE_Match object; span=(3, 6), match='BAR'>

В этом примере оператор на строке 1 указывает regex ba[rz] непосредственно на re.search() в качестве первого аргумента. В строке 4 первым аргументом re.search() является скомпилированный объект регулярного выражения re_obj. На строке 5, search() вызывается непосредственно на re_obj. Во всех трех случаях получается одно и то же совпадение.

Зачем нужно компилировать регекс?

Что хорошего в прекомпиляции? Есть несколько возможных преимуществ.

Если вы часто используете определенный regex в своем коде Python, то прекомпиляция позволяет отделить определение regex от его использования. Это повышает модульность. Рассмотрим этот пример:

>>> s1, s2, s3, s4 = 'foo.bar', 'foo123bar', 'baz99', 'qux & grault'

>>> import re
>>> re.search('\d+', s1)
>>> re.search('\d+', s2)
<_sre.SRE_Match object; span=(3, 6), match='123'>
>>> re.search('\d+', s3)
<_sre.SRE_Match object; span=(3, 5), match='99'>
>>> re.search('\d+', s4)

Здесь регекс \d+ встречается несколько раз. Если в процессе поддержки этого кода вы решите, что вам нужен другой регекс, то вам придется менять его в каждом месте. В этом небольшом примере это не так уж плохо, потому что места использования расположены близко друг к другу. Но в более крупном приложении они могут быть широко разбросаны и их будет трудно отследить.

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

>>> s1, s2, s3, s4 = 'foo.bar', 'foo123bar', 'baz99', 'qux & grault'
>>> re_obj = re.compile('\d+')

>>> re_obj.search(s1)
>>> re_obj.search(s2)
<_sre.SRE_Match object; span=(3, 6), match='123'>
>>> re_obj.search(s3)
<_sre.SRE_Match object; span=(3, 5), match='99'>
>>> re_obj.search(s4)

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

>>> s1, s2, s3, s4 = 'foo.bar', 'foo123bar', 'baz99', 'qux & grault'
>>> regex = '\d+'

>>> re.search(regex, s1)
>>> re.search(regex, s2)
<_sre.SRE_Match object; span=(3, 6), match='123'>
>>> re.search(regex, s3)
<_sre.SRE_Match object; span=(3, 5), match='99'>
>>> re.search(regex, s4)

Теоретически, можно ожидать, что предварительная компиляция также приведет к увеличению времени выполнения. Предположим, вы вызываете re.search() много тысяч раз для одного и того же regex. Может показаться, что компиляция регекса один раз заранее будет более эффективной, чем перекомпиляция его каждый из тысяч раз, когда он используется.

Однако на практике это не так. Дело в том, что модуль re компилирует и кэширует регекс, когда он используется в вызове функции. Если этот же regex используется в дальнейшем в том же коде Python, то он не перекомпилируется. Вместо этого скомпилированное значение извлекается из кэша. Поэтому преимущество в производительности минимально.

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

Методы объектов регулярных выражений

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

  • re_obj.search(<string>[, <pos>[, <endpos>]])
  • re_obj.match(<string>[, <pos>[, <endpos>]])
  • re_obj.fullmatch(<string>[, <pos>[, <endpos>]])
  • re_obj.findall(<string>[, <pos>[, <endpos>]])
  • re_obj.finditer(<string>[, <pos>[, <endpos>]])

Все они ведут себя так же, как и соответствующие функции re, с которыми вы уже сталкивались, за исключением того, что они также поддерживают необязательные параметры <pos> и <endpos>. Если они присутствуют, то поиск применяется только к части <string>, обозначенной <pos> и <endpos>, которые действуют так же, как индексы в нотации слайсов:

 1>>> re_obj = re.compile(r'\d+')
 2>>> s = 'foo123barbaz'
 3
 4>>> re_obj.search(s)
 5<_sre.SRE_Match object; span=(3, 6), match='123'>
 6
 7>>> s[6:9]
 8'bar'
 9>>> print(re_obj.search(s, 6, 9))
10None

В приведенном выше примере регекс - это \d+, последовательность цифровых символов. Вызов .search() на строке 4 ищет все s, так что совпадение есть. В строке 9 параметры <pos> и <endpos> фактически ограничивают поиск подстрокой, начинающейся с символа 6 и идущей до символа 9, но не включая его (подстрока 'bar'), которая не содержит никаких цифр.

Если вы укажете <pos>, но опустите <endpos>, то поиск будет применяться к подстроке от <pos> до конца строки.

Обратите внимание, что такие якоря, как каретка (^) и знак доллара ($), по-прежнему относятся к началу и концу всей строки, а не к подстроке, определенной <pos> и <endpos>:

>>> re_obj = re.compile('^bar')
>>> s = 'foobarbaz'

>>> s[3:]
'barbaz'

>>> print(re_obj.search(s, 3))
None

Здесь, несмотря на то, что 'bar' находится в начале подстроки, начинающейся с символа 3, он не находится в начале всей строки, поэтому каретка (^) не совпадает.

Для скомпилированного объекта регулярного выражения re_obj также доступны следующие методы:

  • re_obj.split(<string>, maxsplit=0)
  • re_obj.sub(<repl>, <string>, count=0)
  • re_obj.subn(<repl>, <string>, count=0)

Они также ведут себя аналогично соответствующим функциям re, но не поддерживают параметры <pos> и <endpos>.

Атрибуты объектов регулярных выражений

Модуль re определяет несколько полезных атрибутов для скомпилированного объекта регулярного выражения:

Attribute Meaning
re_obj.flags Any <flags> that are in effect for the regex
re_obj.groups The number of capturing groups in the regex
re_obj.groupindex A dictionary mapping each symbolic group name defined by the (?P<name>) construct (if any) to the corresponding group number
re_obj.pattern The <regex> pattern that produced this object

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

 1>>> re_obj = re.compile(r'(?m)(\w+),(\w+)', re.I)
 2>>> re_obj.flags
 342
 4>>> re.I|re.M|re.UNICODE
 5<RegexFlag.UNICODE|MULTILINE|IGNORECASE: 42>
 6>>> re_obj.groups
 72
 8>>> re_obj.pattern
 9'(?m)(\\w+),(\\w+)'
10
11>>> re_obj = re.compile(r'(?P<w1>),(?P<w2>)')
12>>> re_obj.groupindex
13mappingproxy({'w1': 1, 'w2': 2})
14>>> re_obj.groupindex['w1']
151
16>>> re_obj.groupindex['w2']
172

Обратите внимание, что .flags включает любые флаги, указанные в качестве аргументов для re.compile(), любые, указанные в regex с помощью последовательности метасимволов (?flags), и любые, которые действуют по умолчанию. В объекте регулярного выражения, заданном на строке 1, определены три флага:

  1. re.I: Указывается как <flags> значение в re.compile() вызове
  2. re.M: Указывается как (?m) внутри regex
  3. re.UNICODE: Включено по умолчанию

На строке 4 видно, что значение re_obj.flags является логическим ИЛИ из этих трех значений, которое равно 42.

Значение атрибута .groupindex для объекта регулярного выражения, определенного на строке 11, технически является объектом типа mappingproxy. Для практических целей он функционирует как словарь.

Совпадение методов и атрибутов объектов

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

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

>>> if re.search('bar', 'foo.bar.baz'):
...     print('Found a match')
...
Found a match

Но объекты match также содержат довольно много удобной информации о матче. Вы уже видели некоторые из них - данные span= и match=, которые интерпретатор показывает при отображении объекта совпадения. Вы можете получить гораздо больше информации из объекта совпадения, используя его методы и атрибуты.

Методы совпадения объектов

В таблице ниже перечислены методы, доступные для объекта соответствия match:

Method Returns
match.group() The specified captured group or groups from match
match.__getitem__() A captured group from match
match.groups() All the captured groups from match
match.groupdict() A dictionary of named captured groups from match
match.expand() The result of performing backreference substitutions from match
match.start() The starting index of match
match.end() The ending index of match
match.span() Both the starting and ending indices of match as a tuple

В следующих разделах эти методы описаны более подробно.

match.group([<group1>, ...])

Возвращает указанную захваченную группу (группы) из совпадения.

Для нумерованных групп, match.group(n) возвращает группу nth:

>>> m = re.search(r'(\w+),(\w+),(\w+)', 'foo,bar,baz')
>>> m.group(1)
'foo'
>>> m.group(3)
'baz'

Помните: пронумерованные захваченные группы основаны на единице, а не на нуле.

Если вы захватили группы с помощью (?P<name><regex>), то match.group(<name>) возвращает соответствующую именованную группу:

>>> m = re.match(r'(?P<w1>\w+),(?P<w2>\w+),(?P<w3>\w+)', 'quux,corge,grault')
>>> m.group('w1')
'quux'
>>> m.group('w3')
'grault'

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

>>> m = re.search(r'(\w+),(\w+),(\w+)', 'foo,bar,baz')
>>> m.group(1, 3)
('foo', 'baz')
>>> m.group(3, 3, 1, 1, 2, 2)
('baz', 'baz', 'foo', 'foo', 'bar', 'bar')

>>> m = re.match(r'(?P<w1>\w+),(?P<w2>\w+),(?P<w3>\w+)', 'quux,corge,grault')
>>> m.group('w3', 'w1', 'w1', 'w2')
('grault', 'quux', 'quux', 'corge')

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

>>> m = re.search(r'(\w+),(\w+),(\w+)', 'foo,bar,baz')
>>> m.group(4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: no such group

>>> m = re.match(r'(?P<w1>\w+),(?P<w2>\w+),(?P<w3>\w+)', 'quux,corge,grault')
>>> m.group('foo')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: no such group

В Python возможно, чтобы регекс совпадал в целом, но содержал группу, которая не участвует в совпадении. В этом случае .group() возвращает None для неучаствующей группы. Рассмотрим этот пример:

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

Этот regex совпадает, как видно из объекта match. Первые две захваченные группы содержат 'foo' и 'bar' соответственно.

<<<За третьей группой следует метасимвол квантификатора

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

В данном случае нет. То есть в целом совпадение есть, но третья группа в нем не участвует. В результате m.group(3) по-прежнему определена и является корректной ссылкой, но она возвращает None:

>>> print(m.group(3))
None

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

>>> m = re.match(r'(\w{3},)+', 'foo,bar,baz,qux')
>>> m
<_sre.SRE_Match object; span=(0, 12), match='foo,bar,baz,'>
>>> m.group(1)
'baz,'

В данном примере полное совпадение - 'foo,bar,baz,', о чем свидетельствует отображаемый объект совпадения. Каждое из 'foo,', 'bar,' и 'baz,' соответствует тому, что находится внутри группы, но m.group(1) возвращает только последнее совпадение, 'baz,'.

Если вы вызываете .group() с аргументом 0 или вообще без аргумента, то он возвращает все совпадение:

 1>>> m = re.search(r'(\w+),(\w+),(\w+)', 'foo,bar,baz')
 2>>> m
 3<_sre.SRE_Match object; span=(0, 11), match='foo,bar,baz'>
 4
 5>>> m.group(0)
 6'foo,bar,baz'
 7>>> m.group()
 8'foo,bar,baz'

Это те же данные, которые интерпретатор показывает после match=, когда отображает объект соответствия, как вы можете видеть на строке 3 выше.

match.__getitem__(<grp>)

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

match.__getitem__(<grp>) идентичен match.group(<grp>) и возвращает единственную группу, указанную <grp>:

>>> m = re.search(r'(\w+),(\w+),(\w+)', 'foo,bar,baz')
>>> m.group(2)
'bar'
>>> m.__getitem__(2)
'bar'

Если .__getitem__() просто повторяет функциональность .group(), то зачем его использовать? Напрямую, скорее всего, нет, но косвенно - возможно. Читайте дальше, чтобы понять, почему.

Краткое введение в магические методы

.__getitem__() - один из коллекции методов в Python, называемых магическими методами. Это специальные методы, которые интерпретатор вызывает, когда оператор Python содержит определенные соответствующие синтаксические элементы.

Примечание: Магические методы также называются dunder methods из-за d double underscore в начале и конце названия метода.

Позже в этой серии будет несколько уроков по объектно-ориентированному программированию. Там вы узнаете гораздо больше о магических методах.

Особый синтаксис, которому соответствует .__getitem__(), - это индексация с помощью квадратных скобок. Для любого объекта obj всякий раз, когда вы используете выражение obj[n], Python за кулисами спокойно переводит его в вызов .__getitem__(). Следующие выражения фактически эквивалентны:

obj[n]
obj.__getitem__(n)

Синтаксис obj[n] имеет смысл только в том случае, если для класса или типа, которому принадлежит obj, существует метод .__getitem()__. То, как именно Python интерпретирует obj[n], будет зависеть от реализации .__getitem__() для этого класса.

Возврат к разделу "Соответствие объектов

Начиная с версии Python 3.6, модуль re реализует .__getitem__() для объектов соответствия. Реализация такова, что match.__getitem__(n) является тем же самым, что и match.group(n).

В результате всего этого вместо прямого вызова .group() вы можете получить доступ к захваченным группам из объекта match, используя синтаксис индексации квадратных скобок:

>>> m = re.search(r'(\w+),(\w+),(\w+)', 'foo,bar,baz')
>>> m.group(2)
'bar'
>>> m.__getitem__(2)
'bar'
>>> m[2]
'bar'

Это работает и с именованными захваченными группами:

>>> m = re.match(
...     r'foo,(?P<w1>\w+),(?P<w2>\w+),qux',
...     'foo,bar,baz,qux')
>>> m.group('w2')
'baz'
>>> m['w2']
'baz'

Этого можно добиться, просто вызвав .group() явно, но, тем не менее, это довольно короткая нотация.

Когда язык программирования предоставляет альтернативный синтаксис, который не является строго необходимым, но позволяет выразить что-то более чистым и легким для чтения способом, это называется синтаксическим сахаром. Для объекта соответствия match[n] является синтаксическим сахаром для match.group(n).

Примечание: У многих объектов в Python определен метод .__getitem()__, позволяющий использовать синтаксис индексирования квадратными скобками. Однако эта возможность доступна только для объектов regex match в Python версии 3.6 или более поздней.

match.groups(default=None)

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

match.groups() возвращает кортеж всех захваченных групп:

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

Как вы уже видели, когда группа в регексе в Python не участвует в общем совпадении, .group() возвращает None для этой группы. По умолчанию .groups() делает то же самое.

Если вы хотите, чтобы .groups() в этой ситуации возвращалось что-то другое, то можете использовать аргумент default с ключевым словом:

 1>>> m = re.search(r'(\w+),(\w+),(\w+)?', 'foo,bar,')
 2>>> m
 3<_sre.SRE_Match object; span=(0, 8), match='foo,bar,'>
 4>>> print(m.group(3))
 5None
 6
 7>>> m.groups()
 8('foo', 'bar', None)
 9>>> m.groups(default='---')
10('foo', 'bar', '---')

Здесь третья группа (\w+) не участвует в совпадении, поскольку метасимвол вопросительного знака (?) делает его необязательным, а строка 'foo,bar,' не содержит третьей последовательности слов-символов. По умолчанию m.groups() возвращает None для третьей группы, как показано на строке 8. На строке 10 видно, что указание default='---' приводит к возврату строки '---' вместо нее.

Нет ни одного соответствующего ключевого слова default для .group(). Он всегда возвращает None для неучаствующих групп.

match.groupdict(default=None)

Возвращает словарь именованных захваченных групп.

match.groupdict() возвращает словарь всех именованных групп, захваченных с помощью последовательности метасимволов (?P<name><regex>). Ключами словаря являются имена групп, а значениями словаря - соответствующие значения групп:

>>> m = re.match(
...     r'foo,(?P<w1>\w+),(?P<w2>\w+),qux',
...     'foo,bar,baz,qux')
>>> m.groupdict()
{'w1': 'bar', 'w2': 'baz'}
>>> m.groupdict()['w2']
'baz'

Как и в случае с .groups(), для .groupdict() аргумент default определяет возвращаемое значение для неучаствующих групп:

>>> m = re.match(
...     r'foo,(?P<w1>\w+),(?P<w2>\w+)?,qux',
...     'foo,bar,,qux')
>>> m.groupdict()
{'w1': 'bar', 'w2': None}
>>> m.groupdict(default='---')
{'w1': 'bar', 'w2': '---'}

Опять же, последняя группа (?P<w2>\w+) не участвует в общем совпадении из-за метасимвола вопросительного знака (?). По умолчанию m.groupdict() возвращает None для этой группы, но вы можете изменить это с помощью аргумента default.

match.expand(<template>)

Выполняет подстановку обратных ссылок из совпадения.

match.expand(<template>) возвращает строку, которая получается в результате выполнения подстановки обратной ссылки на <template> точно так же, как это делает re.sub():

 1>>> m = re.search(r'(\w+),(\w+),(\w+)', 'foo,bar,baz')
 2>>> m
 3<_sre.SRE_Match object; span=(0, 11), match='foo,bar,baz'>
 4>>> m.groups()
 5('foo', 'bar', 'baz')
 6
 7>>> m.expand(r'\2')
 8'bar'
 9>>> m.expand(r'[\3] -> [\1]')
10'[baz] -> [foo]'
11
12>>> m = re.search(r'(?P<num>\d+)', 'foo123qux')
13>>> m
14<_sre.SRE_Match object; span=(3, 6), match='123'>
15>>> m.group(1)
16'123'
17
18>>> m.expand(r'--- \g<num> ---')
19'--- 123 ---'

Это работает для числовых обратных ссылок, как на строках 7 и 9 выше, а также для именованных обратных ссылок, как на строке 18.

match.start([<grp>])

match.end([<grp>])

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

match.start() возвращает индекс в строке поиска, с которого начинается совпадение, а match.end() возвращает индекс, следующий сразу за тем, где совпадение заканчивается:

 1>>> s = 'foo123bar456baz'
 2>>> m = re.search('\d+', s)
 3>>> m
 4<_sre.SRE_Match object; span=(3, 6), match='123'>
 5>>> m.start()
 63
 7>>> m.end()
 86

Когда Python отображает объект соответствия, это значения, перечисленные с ключевым словом span=, как показано на строке 4 выше. Они ведут себя как значения для нарезки строк, поэтому если вы используете их для нарезки исходной строки поиска, то должны получить соответствующую подстроку:

>>> m
<_sre.SRE_Match object; span=(3, 6), match='123'>
>>> s[m.start():m.end()]
'123'

match.start(<grp>) и match.end(<grp>) возвращают начальный и конечный индексы подстроки, совпадающей с <grp>, которая может быть нумерованной или именованной группой:

>>> s = 'foo123bar456baz'
>>> m = re.search(r'(\d+)\D*(?P<num>\d+)', s)

>>> m.group(1)
'123'
>>> m.start(1), m.end(1)
(3, 6)
>>> s[m.start(1):m.end(1)]
'123'

>>> m.group('num')
'456'
>>> m.start('num'), m.end('num')
(9, 12)
>>> s[m.start('num'):m.end('num')]
'456'

Если указанная группа совпадает с нулевой строкой, то .start() и .end() равны:

>>> m = re.search('foo(\d*)bar', 'foobar')
>>> m[1]
''
>>> m.start(1), m.end(1)
(3, 3)

Это имеет смысл, если вспомнить, что .start() и .end() действуют как индексы нарезки. Любой фрагмент строки, в котором начальный и конечный индексы равны, всегда будет пустой строкой.

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

>>> m = re.search(r'(\w+),(\w+),(\w+)?', 'foo,bar,')
>>> print(m.group(3))
None
>>> m.start(3), m.end(3)
(-1, -1)

Как вы уже видели, в этом случае третья группа не участвует. m.start(3) и m.end(3) здесь не имеют смысла, поэтому возвращаются -1.

match.span([<grp>])

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

match.span() возвращает начальный и конечный индексы совпадения в виде кортежа. Если вы указали <grp>, то возвращаемый кортеж относится к данной группе:

>>> s = 'foo123bar456baz'
>>> m = re.search(r'(\d+)\D*(?P<num>\d+)', s)
>>> m
<_sre.SRE_Match object; span=(3, 12), match='123bar456'>

>>> m[0]
'123bar456'
>>> m.span()
(3, 12)

>>> m[1]
'123'
>>> m.span(1)
(3, 6)

>>> m['num']
'456'
>>> m.span('num')
(9, 12)

Следующие пункты фактически эквивалентны:

  • match.span(<grp>)
  • (match.start(<grp>), match.end(<grp>))

match.span() просто предоставляет удобный способ получить и match.start(), и match.end() в одном вызове метода.

Совпадение атрибутов объекта

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

Attribute Meaning
match.pos
match.endpos
The effective values of the <pos> and <endpos> arguments for the match
match.lastindex The index of the last captured group
match.lastgroup The name of the last captured group
match.re The compiled regular expression object for the match
match.string The search string for the match

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

match.pos

match.endpos

Содержит эффективные значения <pos> и <endpos> для поиска.

Помните, что некоторые методы, вызываемые для скомпилированного regex, принимают необязательные аргументы <pos> и <endpos>, которые ограничивают поиск частью указанной строки поиска. Эти значения доступны из объекта match с помощью атрибутов .pos и .endpos:

>>> re_obj = re.compile(r'\d+')
>>> m = re_obj.search('foo123bar', 2, 7)
>>> m
<_sre.SRE_Match object; span=(3, 6), match='123'>
>>> m.pos, m.endpos
(2, 7)

Если аргументы <pos> и <endpos> не включены в вызов, либо потому, что они были опущены, либо потому, что данная функция их не принимает, то атрибуты .pos и .endpos эффективно указывают начало и конец строки:

 1>>> re_obj = re.compile(r'\d+')
 2>>> m = re_obj.search('foo123bar')
 3>>> m
 4<_sre.SRE_Match object; span=(3, 6), match='123'>
 5>>> m.pos, m.endpos
 6(0, 9)
 7
 8>>> m = re.search(r'\d+', 'foo123bar')
 9>>> m
10<_sre.SRE_Match object; span=(3, 6), match='123'>
11>>> m.pos, m.endpos
12(0, 9)

Вызов re_obj.search() выше в строке 2 мог бы принимать аргументы <pos> и <endpos>, но они не указаны. Вызов re.search() на строке 8 вообще не может их принимать. В любом случае m.pos и m.endpos - это 0 и 9, начальный и конечный индексы строки поиска 'foo123bar'.

match.lastindex

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

match.lastindex равен целочисленному индексу последней захваченной группы:

>>> m = re.search(r'(\w+),(\w+),(\w+)', 'foo,bar,baz')
>>> m.lastindex
3
>>> m[m.lastindex]
'baz'

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

>>> m = re.search(r'(\w+),(\w+),(\w+)?', 'foo,bar,baz')
>>> m.groups()
('foo', 'bar', 'baz')
>>> m.lastindex, m[m.lastindex]
(3, 'baz')

>>> m = re.search(r'(\w+),(\w+),(\w+)?', 'foo,bar,')
>>> m.groups()
('foo', 'bar', None)
>>> m.lastindex, m[m.lastindex]
(2, 'bar')

В первом примере третья группа, которая является необязательной из-за метасимвола вопросительного знака (?), все же участвует в совпадении. А во втором примере - нет. Это видно по тому, что m.lastindex является 3 в первом случае и 2 во втором.

В отношении .lastindex есть один тонкий момент, о котором следует знать. Не всегда последняя группа совпадает с последней синтаксической группой. В документации Python приводится такой пример:

>>> m = re.match('((a)(b))', 'ab')
>>> m.groups()
('ab', 'a', 'b')
>>> m.lastindex
1
>>> m[m.lastindex]
'ab'

Крайняя группа - ((a)(b)), которая совпадает с 'ab'. Это первая группа, с которой сталкивается синтаксический анализатор, поэтому она становится группой 1. Но это также последняя группа, которая совпадает, поэтому m.lastindex становится 1.

Вторая и третья группы, которые распознает парсер, - это (a) и (b). Это группы 2 и 3, но они совпадают раньше, чем группа 1.

match.lastgroup

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

Если последняя захваченная группа происходит из последовательности метасимволов (?P<name><regex>), то match.lastgroup возвращает имя этой группы:

>>> s = 'foo123bar456baz'
>>> m = re.search(r'(?P<n1>\d+)\D*(?P<n2>\d+)', s)
>>> m.lastgroup
'n2'

match.lastgroup возвращает None, если последняя захваченная группа не является именованной группой:

>>> s = 'foo123bar456baz'

>>> m = re.search(r'(\d+)\D*(\d+)', s)
>>> m.groups()
('123', '456')
>>> print(m.lastgroup)
None

>>> m = re.search(r'\d+\D*\d+', s)
>>> m.groups()
()
>>> print(m.lastgroup)
None

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

match.re

Содержит объект регулярного выражения для соответствия.

match.re содержит объект регулярного выражения, создавший совпадение. Это тот же объект, который вы получили бы, если бы передали regex в re.compile():

 1>>> regex = r'(\w+),(\w+),(\w+)'
 2
 3>>> m1 = re.search(regex, 'foo,bar,baz')
 4>>> m1
 5<_sre.SRE_Match object; span=(0, 11), match='foo,bar,baz'>
 6>>> m1.re
 7re.compile('(\\w+),(\\w+),(\\w+)')
 8
 9>>> re_obj = re.compile(regex)
10>>> re_obj
11re.compile('(\\w+),(\\w+),(\\w+)')
12>>> re_obj is m1.re
13True
14
15>>> m2 = re_obj.search('qux,quux,corge')
16>>> m2
17<_sre.SRE_Match object; span=(0, 14), match='qux,quux,corge'>
18>>> m2.re
19re.compile('(\\w+),(\\w+),(\\w+)')
20>>> m2.re is re_obj is m1.re
21True

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

После того, как вы получили доступ к объекту регулярного выражения для соответствия, вам станут доступны и все атрибуты этого объекта:

>>> m1.re.groups
3
>>> m1.re.pattern
'(\\w+),(\\w+),(\\w+)'
>>> m1.re.pattern == regex
True
>>> m1.re.flags
32

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

>>> m = re.search(r'(\w+),(\w+),(\w+)', 'foo,bar,baz')
>>> m.re
re.compile('(\\w+),(\\w+),(\\w+)')

>>> m.re.match('quux,corge,grault')
<_sre.SRE_Match object; span=(0, 17), match='quux,corge,grault'>

Здесь .match() вызывается на m.re, чтобы выполнить другой поиск с использованием того же regex, но в другой строке поиска.

match.string

Содержит строку поиска совпадения.

match.string содержит строку поиска, которая является целью совпадения:

>>> m = re.search(r'(\w+),(\w+),(\w+)', 'foo,bar,baz')
>>> m.string
'foo,bar,baz'

>>> re_obj = re.compile(r'(\w+),(\w+),(\w+)')
>>> m = re_obj.search('foo,bar,baz')
>>> m.string
'foo,bar,baz'

Как видно из примера, атрибут .string доступен и тогда, когда объект match происходит от скомпилированного объекта регулярного выражения.

Заключение

На этом вы закончите знакомство с модулем Python re!

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

  • Полностью используйте все функции, которые предоставляет модуль re
  • Предкомпиляция регекса в Python
  • Извлечение информации из объектов соответствия

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

Примечание: Модуль re замечательный, и он, вероятно, будет хорошо служить вам в большинстве случаев. Однако существует альтернативный сторонний модуль Python под названием regex, который предоставляет еще больше возможностей для подбора регулярных выражений. Подробнее о нем вы можете узнать на странице проекта regex.

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

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