Продолжаем тему
регулярных выражений и на этом занятии поговорим о квантификаторах. В
общем виде они записываются в виде (без пробелов):
{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.