Квантификаторы регулярных выражений

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

{m,n}

где m – минимальное число совпадений с выражением; n – максимальное число совпадений с выражением. Например:

import re
 
text = "Google, Gooogle, Goooooogle"
match = re.findall(r"o{2,5}", text)
 
print(match)

Здесь будут выделены следующие комбинации:

"Google, Gooogle, Goooooogle"

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

match = re.findall(r"o{2,5}?", text)

В результате получим вхождения:

['oo', 'oo', 'oo', 'oo', 'oo']

которые образуются так:

"Google, Gooogle, Goooooogle"

Краткие формы записи квантификаторов

Квантификаторы можно записывать и в кратких формах, например:

  • {m} – повторение выражения ровно m раз (эквивалент {m,m});
  • {m,} – повторения от m и более раз;
  • {, n} – повторения не более n раз.

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

{m,}?    {,n}?

Например:

text = "Google, Gooogle, Goooooogle"
match = re.findall(r"Go{2,}gle", text)

Будет находить все записи слова «Google» с двумя символами ‘o’ и более. Или, так:

text = "Google, Gooogle, Goooooogle"
match = re.findall(r"Go{,4}gle", text)

Найдет первые два слова, последнее не соответствует шаблону. И, наконец, такой пример:

phone = "89123456789"
match = re.findall(r"8\d{10}", phone)

выделяет телефонные номера с первой цифрой 8 и следующими 10 цифрами.

Для квантификаторов {0,} и {1,} существуют специальные символы:

  • ? – от нуля до одного (аналог {0,1});
  • * – от нуля и до «бесконечности» (в действительности, большого числа – от 32767), соответствует квантификатору {0,};
  • + – от единицы и до «бесконечности» (также большого числа – от 32767), соответствует квантификатору {1,}.

Все эти сокращения также можно использовать в минорном режиме:

??    *?    +?

Первое сокращение можно реализовать так:

text = "стеклянный, стекляный"
match = re.findall(r"стеклянн?ый", text)

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

text = "author=Пушкин А.С.; title = Евгений Онегин; price =200; year= 2001"

и мы хотим выполнить парсинг (разбор) по ключам и значениям. Это можно реализовать так:

match = re.findall(r"\w+\s*=\s*[^;]+", text)

на выходе получим следующий список:

['author=Пушкин А.С.', 'title = Евгений Онегин', 'price =200', 'year= 2001']

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

d = text.split(";")
print(d)

Но, в регулярных выражениях мы, во-первых, точно определяем шаблон данных: ключ=значение. И, во-вторых, с этими данными можно выполнять тонкую обработку, например, сразу выделить и ключ и значение:

match = re.findall(r"(\w+)\s*=\s*([^;]+)", text)

В результате, получим список кортежей:

[('author', 'Пушкин А.С.'), ('title', 'Евгений Онегин'), ('price', '200'), ('year', '2001')]

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

Далее рассмотрим примеры минорных квантификаторов. Часто это бывает необходимо при разборе HTML-документа. Например, у нас имеется следующий текст:

text = "Картинка <img src='bg.jpg'> в тексте</p>"

и мы хотим выделить фрагмент с тегом <img …>. Если записать выражение так:

match = re.findall(r"<img.*>", text)

то результат будет следующий:

"<img src='bg.jpg'> в тексте</p>"

Видите, мажорный квантификатор дошел до последней угловой скобки и захватил «лишнюю» часть текста. Решить эту задачу можно несколькими способами. В самом простом случае записать минорный квантификатор:

match = re.findall(r"<img.*?>", text)

тогда результатом будет строка:

"<img src='bg.jpg'>"

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

match = re.findall(r"<img[^>]*>", text)

Результат будет тем же. И здесь может возникнуть вопрос: какой вариант регулярного выражения лучше? В действительности, оба могут быть использованы. Далее, вы увидите еще много таких случаев, когда одно и то же решение может быть реализовано различными выражениями. Умение составлять короткие и понятные шаблоны – это часть искусства программирования. И, как любое искусство, его постижение приходит с опытом и длится всю жизнь. Здесь я лишь показываю основные моменты, кирпичики языка регулярных выражений, но умение использовать их на практике приходит только с опытом. Поэтому, больше программируйте, тренируйтесь, развивайтесь и только это приведет вас к желаемым результатам. А теория – это лишь основа, отправная точка для дальнейшего развития. И мы продолжаем ее изучение.

Теперь, смотрите. Оба приведенных варианта, с точки зрения HTML-разметки могут приводить к некорректным результатам, например, просто записи тега

<img>

Это неверная строка, т.к. у этого тега обязательно должен присутствовать атрибут src:

<img src="путь к картинке">

Давайте улучшим наше выражение, чтобы оно учитывало этот атрибут. Это можно сделать так:

match = re.findall(r"<img\s+[^>]*?src\s*=\s*[^>]*>", text)

Мы здесь берем начало тега <img, затем, обязательно должен идти один или несколько пробелов, далее, могут быть записаны другие атрибуты, потом указываем, что должен быть атрибут src, за которым следует символ = и, затем, идем до ближайшей закрывающей скобки. Такой вариант регулярного выражения будет корректно выделять разные теги img:

"<p>Картинка <img alt='картинка' src='bg.jpg'> в тексте</p>"
"<p>Картинка <img src='bg.jpg'> в тексте</p>"
"<p>Картинка <img src='bg.jpg' title='картинка'> в тексте</p>"

Но, вот такие варианты зафиксированы не будут:

"<img>"
"<p>Картинка <img src2='bg.jpg'> в тексте</p>"
"<p>Картинка <img alt='картинка'> в тексте</p>"

И другие, где нет атрибута src.