Модуль re

Конспект посвящён регулярным выражениям и модулю re

Регулярные выражения

Регулярные выражения (regexp, regex) — формальный язык, используемый в программах для поиска и осуществления манипуляций с подстрокой в тексте, используя шаблон (pattern).

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

  • Поиска подстрок в строке;
  • Разделения строки на подстроки;
  • Замены части строки.

Любая строка сама по себе уже является регулярным выражением, так, шаблону Soup будет соответствовать подстрока Soup. Регулярные выражения являются регистрозависимыми. В них присутствуют специальные символы, которые нужно экранировать, если их требуется использовать по прямому назначению (просто как символы): .^$*+?{}[]\|().

Основные символы

PatternDescription
.Любой символ, кроме \n
^Начало строки
$Конец строки
*0 или более повторений предшествующего элемента
+1 или более повторений предшествующего элемента
?0 или 1 повторение предшествующего элемента
{n}Ровно n повторений предшествующего элемента
{n,}n или более повторений предшествующего элемента
{n,m}От n до m повторений предшествующего элемента
[]Любой одиночный символ из указанных в квадратных скобках
[^]Любой одиночный символ, не указанный в квадратных скобках
\Экранирование специальных символов
|Логическое ИЛИ
()Группировка выражений и сохранение совпадений

Метасимволы

PatternDescription
\dЛюбая цифра
\DЛюбой символ, кроме цифры
\wЛюбой алфавитный символ, цифра или _
\WЛюбой символ, кроме алфавитного, цифры и _
\sЛюбой пробельный символ ([ \f\n\r\t\v])
\SЛюбой непробельный символ
\bГраница слова. Соответствует позиции, а не символу
\BНе начало и не конец слова

Наборы и диапазоны

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

Инфо
В пределах [ ] символ . можно не экранировать

Так, регулярному выражению [ns]a.\.csv будет соответствовать:

sales1.csv
orders3.csv
sales2.csv
sales3.csv
apac1.csv
europe2.csv
sam.csv
na1.csv
na2.csv
sa1.csv
ca1.csv

Для указания диапазона символов используется -. Например: [а-яА-Я] (буквы ё и Ё не включаются). Если в набор требуется включить -, нужно указывать его вне диапазона.

Так, регулярному выражению [ns]a[0-9]\.csv будет соответствовать:

sales1.csv
orders3.csv
sales2.csv
sales3.csv
apac1.csv
europe2.csv
sam.csv
na1.csv
na2.csv
sa1.csv
ca1.csv
Инфо
При использовании диапазонов нужно следить, чтобы конец диапазона не был меньше, чем его начало. К примеру, диапазоны [4-1] или [z-a] недопустимы.

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

Так, регулярному выражению [ns]a[^0-9]\.csv будет соответствовать:

sales1.csv
orders3.csv
sales2.csv
sales3.csv
apac1.csv
europe2.csv
sam.csv
na1.csv
na2.csv
sa1.csv
ca1.csv

Соответствие нескольким символам

Символ + устанавливает соответствие с одним или более символами. Таким образом, если регулярное выражение a соответствует одному символу a, то регулярное выражение a+ соответствует одному или нескольким символам a. Точно так же, учитывая, что регулярное выражение [0-9] соответствует любой цифре, регулярное выражение [0-9]+ соответствует последовательности, состоящей из одной или нескольких цифр.

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

Чтобы установить соответствие с необязательным символом, используется ?.

Так, регулярному выражению https?://[\w./]+ будет соответствовать:

URL http://stepik.org/ уже не безопасен. Для безопасного подключения вместо него используйте https://stepik.org/

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

  • Чтобы указать точное количество совпадений, нужно вставить число в фигурные скобки: #[0-9A-Fa-f]{6}.
  • Чтобы указать интервал-диапазон, нужно указать два числа через запятую: \d{1,2}[-/]\d{1,2}[-/]\d{2,4}.
  • Чтобы указать “не более” или “не менее”, нужно ставить запятые перед или после числа-границы: \d{,2} или \d{2,}.
Инфо
При использовании интервал-диапазона не нужно ставить пробелы между запятой и числом

Жадность и ленивость

Специальные символы * и + являются жадными. Они ищут наибольшее возможное соответствие. Например, есть регулярное выражение <B>.*</B>:

Подписку нельзя оформить клиентам, живущим в <B>России</B>, а также <B>Беларуси</B>. Приносим свои извинения.

Если требуется использовать ленивое поведение, нужно ставить ? после символа: |Жадный|Ленивый| |*|*?| |+|+?| |?|??| |{m,n}|{m,n}?| |{,n}|{,n}?| |{m,}|{m,}?|

Теперь рассмотрим регулярное выражение <B>.*?</B>:

Подписку нельзя оформить клиентам, живущим в <B>России</B>, а также <B>Беларуси</B>. Приносим свои извинения.

Группы

Группа — часть регулярного выражения (подвыражение), заключённое в круглые скобки: ( и ). Группы обрабатываются как единый объект, поэтому, например, выражение (\daf){2} будет означать повторение \daf два раза.

Таким образом, если нужно найти в строке IP-адрес (без проверки на правильность), можно использовать (\d{1,3}\.){3}\d{1,3}.

Группы можно вкладывать друг в друга, поэтому, если для уже озвученной задачи также необходима проверка на правильность адреса, можно использовать: (((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))\.){3}((2[0-4]\d)|(25[0-5])|(1\d{2})|(\d{1,2}))

Чтобы сослаться на существующую группу, нужно экранировать её порядковый номер. Номера распределяются по открывающим скобкам, начиная с 1. Например, регулярному выражению (\b\w+\b)[ ]+\1 соответствует:

Это текст, в котором котором несколько слов    слов повторяются, хотя они и не   не должны повторяться повторяться.

В новых реализациях регулярных выражений можно именовать группу. В Python это делается так: (?P<name><regex>).


Модуль re

Для использования: import re

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

Объект Match

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

Рассмотрим методы объекта.

group()

Метод Match.group([group1, ...]) возвращает одну или несколько подгрупп совпадения.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from re import search

match = search('(\w+),(\w+),(\w+)', 'foo,bar,baz')

print(match.group())
print(match.group(0))
print(match.group(1))
print(match.group(2))
print(match.group(3))
print(match.group(1, 2, 3))
1
2
3
4
5
6
7
# Вывод:
foo,bar,baz
foo,bar,baz
foo
bar
baz
('foo', 'bar', 'baz')
Ошибка
Если методу передать индекс несуществующей группы, будет возбуждено исключение IndexError

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

1
2
3
4
5
6
7
from re import search

match = search('(\w+),(\w+),(\w+)', 'foo,bar,baz')

print(match.group(1, 2, 3, 1, 2, 2, 3, 3, 3, 3))

# Вывод: ('foo', 'bar', 'baz', 'foo', 'bar', 'bar', 'baz', 'baz', 'baz', 'baz')

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

1
2
3
4
5
6
7
8
9
from re import search

match = search('(?P<w1>\w+),(?P<w2>\w+),(?P<w3>\w+)', 'foo,bar,baz')

print(match.group())
print(match.group('w1'))
print(match.group('w2'))
print(match.group('w3'))
print(match.group('w1', 'w2', 'w3', 'w2', 'w3'))

groups()

Метод Match.groups(default=None) возвращает кортеж, содержащий все захваченные группы.

1
2
3
4
5
6
7
from re import search

match = search('(\w+),(\w+),(\w+)?', 'foo,bar,')

print(match.groups())

# Вывод: ('foo', 'bar', None)

Группы, которые не смогли захватить какой-либо результат, по умолчанию будут иметь значение None. Если в такой ситуации требуется вернуть значение, отличное от None, то используется необязательный аргумент default.

groupdict()

Метод Match.groupdict(default=None) возвращает словарь, содержащий все захваченные именованные группы.

Если именованных групп в исходном регулярном выражении нет, метод groupdict() возвращает пустой словарь.

start() и end()

Методы Match.start([group]) и Match.stop([group]) возвращают индексы начала и конца подстроки, которая совпала с регулярным выражением.

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

span()

Метод `Match.span([group]) возвращает индексы начала и конца подстроки в виде кортежа, которая совпала с регулярным выражением. В него также можно передать номер или название группы, в этом случае метод вернет индексы начала и конца подстроки в виде кортежа, совпадающей с нужной группой.

Функция re.search(pattern, string, flags=0) сканирует строку в поисках первого совпадения с регулярным выражением и возвращает объект типа Match или значение None, если ни одна позиция в строке не соответствует регулярному выражению.

Аргументы:

  • pattern — шаблон регулярного выражения
  • string — строка для поиска
  • flags — один или несколько флагов
1
2
3
4
5
6
7
8
9
from re import search

match1 = search('super', 'superstition')
match2 = search('super', 'insuperable')
match3 = search('super', 'without')

print(match1)
print(match2)
print(match3)
1
2
3
4
# Вывод:
<re.Match object; span=(0, 5), match='super'>
<re.Match object; span=(2, 7), match='super'>
None

Функция match()

Функция re.match(pattern, string, flags=0) возвращает объект типа Match, если начало строки соответствуют регулярному выражению, или значение None в противном случае.

1
2
3
4
5
6
7
from re import match

match1 = match('super', 'superstition')
match2 = match('super', 'insuperable')

print(match1)
print(match2)
1
2
<re.Match object; span=(0, 5), match='super'>
None

Функция fullmatch()

Функция re.fullmatch(pattern, string, flags=0) возвращает объект типа Match, если вся строка соответствует регулярному выражению, или значение None в противном случае. Её удобно использовать для валидации правильности данных.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from re import fullmatch

match1 = fullmatch('\d+', '123foo')
match2 = fullmatch('\d+', 'foo123')
match3 = fullmatch('\d+', 'foo123bar')
match4 = fullmatch('\d+', '123')

print(match1)
print(match2)
print(match3)
print(match4)
1
2
3
4
5
# Вывод:
None
None
None
<re.Match object; span=(0, 3), match='123'>

Функция escape()

Функция re.escape(pattern) экранирует специальные символы в строке.

1
2
3
4
5
from re import escape

print(escape('http://www.stepik.org'))

# Вывод: http://www\.stepik\.org

Функция findall()

Функция re.findall(pattern, string, flags=0) возвращает все неперекрывающиеся совпадения с регулярным выражением в виде списка строк. Строка сканируется слева направо, и совпадения возвращаются в найденном порядке.

1
2
3
4
5
6
7
8
import re

text = 'ул. Часовая, дом № 25, корпус 2, квартира 69'
result = re.findall('\d+', text)

print(result)

# Вывод: ['25', '2', '69']

Если регулярное выражение содержит одну группу, то findall() вернёт список соответствующих групп, а не список полных совпадений с регулярным выражением:

1
2
3
4
5
6
7
import re

result = re.findall('#(\w+)#', '#foo#.#bar#.#baz#')

print(result)

# Вывод: ['foo', 'bar', 'baz']

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

1
2
3
4
5
6
7
import re

result1 = re.findall('(\w+),(\w+)', 'foo,bar,baz,qux,quux,corge')
result2 = re.findall('(\w+),(\w+),(\w+)', 'foo,bar,baz,qux,quux,corge')

print(result1)
print(result2)
1
2
[('foo', 'bar'), ('baz', 'qux'), ('quux', 'corge')]
[('foo', 'bar', 'baz'), ('qux', 'quux', 'corge')]

Функция finditer()

Функция re.finditer(pattern, string, flags=0) возвращает все неперекрывающиеся совпадения с регулярным выражением в виде итератора, содержащего объекты типа Match. Строка сканируется слева направо, и совпадения возвращаются в найденном порядке.

1
2
3
4
5
6
7
import re

text = 'ул. Часовая, дом № 25, корпус 2, квартира 69'
result = re.finditer('\d+', text)

print(type(result))
print(list(result))
1
2
3
# Вывод:
<class 'callable_iterator'>
[<re.Match object; span=(19, 21), match='25'>, <re.Match object; span=(30, 31), match='2'>, <re.Match object; span=(42, 44), match='69'>]

Функции findall() и finditer() очень похожи, но есть два отличия:\

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

Функция sub()

Функция re.sub(pattern, repl, string, count=0, flags=0) возвращает строку, полученную путем замены всех найденных неперекрывающихся вхождений регулярного выражения pattern в строке string на строку замены repl. Если шаблон регулярного выражения не найден, строка возвращается без изменений.

Аргумент repl может быть строкой или функцией. Если repl это строка, то в ней обрабатываются все обратные слеши, то есть \n преобразуется в символ новой строки, \r преобразуется в возврат каретки и т. д.

Аргумент count используется для ограничения количеств замен.

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

Если repl является строкой, то sub() вставляет ее в строку поиска string вместо любых последовательностей, соответствующих регулярному выражению pattern.

1
2
3
4
5
6
7
8
9
import re

text = 'foo.123.bar.456.baz.789.geek'

result1 = re.sub(r'\d+', r'#', text)
result2 = re.sub(r'[a-z]+', r'(*)', text)

print(result1)
print(result2)
1
2
3
# Вывод:
foo.#.bar.#.baz.#.geek
(*).123.(*).456.(*).789.(*)

При использовании функции sub() также можно использовать пронумерованные обратные ссылки (\<n>) в аргументе repl, которым будет соответствовать текст захваченной группы. Обратные ссылки, такие как \2, заменяются подстрокой, соответствующей группе №2 в шаблоне регулярного выражения:

1
2
3
4
5
6
7
import re

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

print(result)

# Вывод: qux,bar,baz,foo

Аналогично работает и с именованными группами.

Замена с помощью функции

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import re
def func(match_obj):
    s = match_obj.group(0)         # строка совпадения
    if s.isdigit():
        return str(int(s) * 10)
    else:
        return s.upper()

result = re.sub(r'\w+', func, r'foo.10.bar.20.baz30.40')

print(result)

# Вывод: FOO.100.BAR.200.BAZ30.400

Функция subn()

Функция re.subn(pattern, repl, string, count=0, flags=0) идентична функции sub(), за тем исключением, что она возвращает кортеж, состоящий из изменённой строки и количества сделанных замен.

1
2
3
4
5
6
7
8
9
import re

text = 'foo.123.bar.456.baz.789.geek'

result1 = re.subn(r'\d+', r'#', text)
result2 = re.subn(r'[a-z]+', r'(*)', text, count=2)

print(result1)
print(result2)
1
2
3
# Вывод:
('foo.#.bar.#.baz.#.geek', 3)
('(*).123.(*).456.baz.789.geek', 2)

Функция split()

Функция re.split(pattern, string, maxsplit=0, flags=0) разбивает строку на подстроки, используя регулярное выражение в качестве разделителя, и возвращает подстроки в виде списка.

Аргумент maxsplit используется для ограничения количества разбиений.

1
2
3
4
5
6
7
import re

result = re.split(r'[,;.]', 'foo,bar.baz;qux;stepik,beegeek')

print(result)

# Вывод: ['foo', 'bar', 'baz', 'qux', 'stepik', 'beegeek']

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

1
2
3
4
5
6
7
import re

result1 = re.split(r'\s*([,;.])\s*', 'foo,   bar. baz   ;    qux ;  stepik   ,   beegeek')
result2 = re.split(r'(\s*[,;.]\s*)', 'foo,   bar. baz   ;    qux ;  stepik   ,   beegeek')

print(result1)
print(result2)
1
2
3
# Вывод:
['foo', ',', 'bar', '.', 'baz', ';', 'qux', ';', 'stepik', ',', 'beegeek']
['foo', ',   ', 'bar', '. ', 'baz', '   ;    ', 'qux', ' ;  ', 'stepik', '   ,   ', 'beegeek']

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

1
2
3
4
5
6
7
import re

result = re.split(r'(?:\s*[,;.]\s*)', 'foo,   bar. baz   ;    qux ;  stepik   ,   beegeek')

print(result)

# Вывод: ['foo', 'bar', 'baz', 'qux', 'stepik', 'beegeek']

Функция compile()

Функция re.compile(pattern, flags=0) предварительно компилирует регулярное выражение в специальный объект (Pattern), который можно повторно использовать позже.

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

Инфо
Модуль re компилирует и кэширует регулярное выражение, когда оно используется в вызове функции. Если одно и то же регулярное выражение используется впоследствии в том же коде, оно не перекомпилируется. Вместо этого скомпилированное значение извлекается из кэша. В общем, нет никаких веских причин для компиляции регулярного выражения. Это всего лишь еще один инструмент в вашем наборе инструментов, который вы можете использовать, если считаете, что он улучшит читабельность или структуру вашего кода.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import re

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

regex_obj = re.compile('\d+')

print(regex_obj.search(s1))
print(regex_obj.search(s2))
print(regex_obj.search(s3))
print(regex_obj.search(s4))

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

  • search(string, pos, endpos)
  • match(string, pos, endpos)
  • fullmatch(string, pos, endpos)
  • findall(string, pos, endpos)
  • finditer(string, pos, endpos)

Данные методы ведут себя так же, как соответствующие (одноименные) им функции модуля re, за исключением того, что они также поддерживают необязательные аргументы pos и endpos.

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

1 способ

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

1
2
3
4
5
6
7
8
9
import re

regex_obj = re.compile('\d+')
text = 'ул. Часовая, дом № 25, корпус 2, квартира 69'
result = re.findall(regex_obj, text)

print(result)

# Вывод: ['25', '2', '69']

2 способ

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

1
2
3
4
5
6
7
8
9
import re

regex_obj = re.compile('\d+')
text = 'ул. Часовая, дом № 25, корпус 2, квартира 69'
result = regex_obj.findall(text)

print(result)

# Вывод: ['25', '2', '69']

Флаги

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

ShortnameFullnamePurpose
re.Ire.IGNORECASEИгнорирует регистр символов
re.Mre.MULTILINEИспользуется совместно со специальными символами ^ и $, в первом случае возвращает совпадения в начале каждой новой строки \n, во втором – в конце \n
re.Sre.DOTALLЗаставляет символ . возвращать совпадения по абсолютно всем символам, включая \n
re.Xre.VERBOSEРазрешает комментарии в регулярном выражении
-re.DEBUGПоказывает отладочную информацию о скомпилированном регулярном выражении
re.Are.ASCIIУказывает кодировку ASCII для классификации символов \w, \W, \b, \B, \d, \D, \s, \S
re.Ure.UNICODEУказывает кодировку Unicode для классификации символов \w, \W, \b, \B, \d, \D, \s, \S
re.Lre.LOCALEУчитывает региональные настройки при использовании метасимволов \w, \W, \b, \B, \s, \S
Инфо
Чтобы использовать несколько флагов одновременно, нужно перечислить их через оператор |
1
2
3
4
5
6
7
import re

match = re.search('^bar', 'FOO\nBAR\nBAZ', re.I | re.M)

print(match)

# Вывод: <re.Match object; span=(4, 7), match='BAR'>

Основной источник: https://stepik.org/course/82541

Дополнительные источники:

Поддержать автора
NoisyCake cloudtipscloudtips
0%