Регулярные выражения
Рассмотрим встроенные функции модуля re, научимся компилировать Regex-выражения и узнаем, как делать опережающие и ретроспективные проверки – позитивные и негативные. В конце статьи, как всегда, – 10 интересных заданий с решениями.
Регулярные выражения (Regex) – это особые шаблоны для поиска определенных подстрок в текстовых документах и на веб-страницах. Концепция Regex появилась в 1951 году, стала популярной к 1968 году, и с тех пор в той или иной степени поддерживается в большинстве языков программирования общего назначения. Регулярные выражения используются в текстовых редакторах, в файловых менеджерах ОС, в OCR-приложениях для распознавания текста, в онлайн-поисковиках и браузерах. Кроме того, они применяются для:
- валидации данных;
- лексического анализа;
- определения директив конфигурации и преобразования URL (Apache http.conf, mod_rewrite);
- составления сложных SQL-запросов;
- создания кастомных шаблонов URL-диспетчера (re_path() Django).
Регулярные выражения в Python
Для работы с Regex в Python используют встроенный модуль re, в который входят:
- Набор функций для поиска и замены подстрок – ниже мы подробно рассмотрим примеры использования основных методов.
- Компилятор re.compile – он создает Regex-объекты для повторного использования и ускоряет работу регулярных выражений, как мы увидим чуть позже.
Регулярные выражения состоят из литералов (букв и цифр) и метасимволов. Для экранирования спецсимволов применяют обратные слэши\
, или же заключают выражение в r-строку . Такой шаблон, к примеру, можно использовать для валидации email-адреса:
r'^[a-zA-Z0-9._-]+@[a-zA-Z-.]+$'
Этот шаблон – один из простейших, Regex-выражения для проверки email-адресов могут выглядеть гораздо сложнее. Для разработки и тестирования сложных Regex шаблонов используют специальные сервисы, например, Regex101:
Основные Regex методы в Python
re.match() – проверяет, начинается ли строка с нужного фрагмента:
import re
lst = ['abrakadabra', 'https://kadabra.com', 'https://proglib.io/p/weekly-23-novosti-podkasty-otbornye-stati-i-obuchayushchie-materialy-po-frontendu-2023-02-14 - статья по этой ссылке',
'http//:mysite.ru', 'www.abra.com', 'http//abra.com', 'https://abra.com/', 'это мой сайт - https://abrakadabra.com/',
'https://ru.wikipedia.org/wiki/%D0%9B%D1%8F%D0%B3%D1%83%D1%88%D0%BA%D0%B0-%D0%B3%D0%BE%D0%BB%D0%B8%D0%B0%D1%84']
url = r'https?://(www.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()!@:%_+.~#?&/=]*)'
for i in lst:
m = re.match(url, i)
if m:
print(m.group(0)) # валидная ссылка извлекается из начала строки
Вывод:
https://kadabra.com
https://proglib.io/p/weekly-23-novosti-podkasty-otbornye-stati-i-obuchayushchie-materialy-po-frontendu-2023-02-14
Abra – Conquer Crypto
https://ru.wikipedia.org/wiki/%D0%9B%D1%8F%D0%B3%D1%83%D1%88%D0%BA%D0%B0-%D0%B3%D0%BE%D0%BB%D0%B8%D0%B0%D1%84
Если нужный фрагмент содержится в тексте, но не в начале строки – re.match() вернет None
:
>>> import re
>>> s = 'ой, мороз, мороз, не морозь меня'
>>> print(re.match('мороз', s))
None
Метод re.fullmatch() возвращает совпадение, если вся строка полностью соответствует шаблону:
>>> st1, st2 = 'одна строка', 'строка'
>>> print(re.fullmatch(r'строка', st1))
None
>>> print(re.fullmatch(r'строка', st2))
<re.Match object; span=(0, 6), match='строка'>
Чтобы найти первое вхождение подстроки в текст, используют re.search(), при необходимости – с флагом re.I
для игнорирования регистра:
>>> s = 'Синий, синий иней лег на провода'
>>> print(re.search('синий', s, re.I))
<re.Match object; span=(0, 5), match='Синий'>
Метод re.search() можно использовать с дополнительными параметрами span()
, string
и group()
.
span возвращает начальный и конечный индексы вхождения:
>>> text = 'Однажды весною, в час небывало жаркого заката, в Москве, на Патриарших прудах, появились два гражданина.'
>>> print(re.search('пруд', text).span())
(71, 75)
string возвращает строку, содержащую искомый фрагмент:
>>> st = 'Дракула Брэма Стокера'
>>> print(re.search('Сток', st).string)
Дракула Брэма Стокера
group вернет подстроку, совпадающую с запросом:
>>> st = 'пример домашнего хищника: кот'
>>> print(' текст найден - ', re.search(r'хищника:\s\w\w\w', st).group())
текст найден - хищника: кот
Все вхождения фрагмента можно найти с помощью re.findall():
>>> st = 'Eins Hier kommt die Sonne, Zwei Hier kommt die Sonne'
>>> print(re.findall('Hier kommt die Sonne', st))
['Hier kommt die Sonne', 'Hier kommt die Sonne']
Метод re.split() разделяет строку по заданному шаблону:
>>> st = 'мороз и солнце, день чудесный'
>>> print(re.split(r'\sи\s', st, 1))
['мороз', 'солнце, день чудесный']
Для замены символов и подстрок используют re.sub(). В этом примере регулярное выражение предусматривает удаление из текста всех символов, кроме букв, цифр, знака перевода на новую строку, точки, пробела, вопросительного знака и запятой:
>>> st = 'П#$%^рив&*ет, ка@!к успе~@хи с Py$%^*&thon?'
>>> print(re.sub('[^а-яА-Яa-zA-Z0-9,? \n\.]', '', st))
Привет, как успехи с Python?
Скорость работы скомпилированных Regex-выражений
Компилятор re.compile() применяют в тех случаях, когда шаблон выражения используется повторно:
import re
url_lst = ['https://mysite.ru/uploads/2023/2/1/image.jpg',
'https://mysite.ru/uploads/2023/2/1/image.html',
'http://www.mysite.ru/uploads/2022/2/1/another_image.png',
'http://mysite.ru/uploads/2022/12/15/images.doc',
'https://www.mysite.ru/uploads/2022/12/11/image22.jpg',
'http://mysite.ru/images/2023/2/5/gifimage.gif',
'https://mysite.ru/texts/2023/2/1/novel.txt',
'https://mysite.ru/books/2023/2/1/book.epub']
img_url = re.compile(r'https?://(www)?.*.(png|jpg|gif)')
for url in url_lst:
if img_url.match(url):
print(url)
Вывод:
https://mysite.ru/uploads/2023/2/1/image.jpg
http://www.mysite.ru/uploads/2022/2/1/another_image.png
https://www.mysite.ru/uploads/2022/12/11/image22.jpg
http://mysite.ru/images/2023/2/5/gifimage.gif
Как уже упоминалось выше, скомпилированные выражения удобны не только потому, что их можно использовать многократно – они, к тому же, быстрее работают. Чем сложнее выражение и чем больше объем обрабатываемых данных – тем очевиднее преимущество. Проверим, насколько отличается скорость работы обычного регулярного выражения от скомпилированного – возьмем объемный файл («Преступление и наказание» Ф. М. Достоевского) и проведем поиск всех строк, в которых одновременно содержатся имя «Родион» и фамилия «Раскольников» в любых возможных склонениях, при этом между именем и фамилией должно быть не более 5 других слов:
import re
import time
start = time.time()
with open('prestuplenie-i-nakazanie.txt', 'r', encoding='utf8') as book:
result = [line for line in book if re.findall(r'\bРодио\w{0,3}(?:\s+\S+){0,5}\s+Раскольнико\w{0,3}\b', line)]
print(f'Найдено {len(result)} совпадений. Поиск без компиляции занял {time.time() - start:.2f} секунд')
start = time.time()
with open('prestuplenie-i-nakazanie.txt', 'r', encoding='utf8') as book:
find_name = re.compile(r'\bРодио\w{0,3}(?:\s+\S+){0,5}\s+Раскольнико\w{0,3}\b')
result2 = [line for line in book if find_name.findall(line)]
print(f'Найдено {len(result2)} совпадений. Поиск с компиляцией занял {time.time() - start:.2f} секунд')
Результат:
Найдено 5 совпадений. Поиск без компиляции занял 0.11 секунд
Найдено 5 совпадений. Поиск с компиляцией занял 0.07 секунд
Конструирование регулярных выражений в Python
Regex-шаблоны в Python, как уже упоминалось выше, состоят из метасимволов и литералов, которые определяют символьные комбинации – последовательности, наборы и диапазоны. Регулярные выражения ищут совпадения в обрабатываемом тексте в соответствии с этими комбинациями.
Метасимволы
Рассмотрим основные метасимволы, которые используются для составления Regex-шаблонов.
[]
– определяет набор (или диапазон) символов:
>>> st = 'Роман "Война и мир", автор - Лев Николаевич Толстой'
>>> re.findall(r'[о-с]', st, re.I)
['Р', 'о', 'о', 'р', 'о', 'р', 'о', 'о', 'с', 'о']
>>> re.findall(r'[абвлнт]', st, re.I)
['а', 'н', 'В', 'н', 'а', 'а', 'в', 'т', 'Л', 'в', 'Н', 'л', 'а', 'в', 'Т', 'л', 'т']
\
– задает начало последовательности, а также экранирует служебные символы:
>>> st = 'www.google.com, www.yandex.ru'
>>> re.findall(r'.com\b', st)
['.com']
.
– позволяет выбрать любой символ, кроме \n
:
>>> st = 'Python\n'
>>> re.findall(r'.', st)
['P', 'y', 't', 'h', 'o', 'n']
^
– определяет, начинается ли строка с определенного символа (слова, набора слов или символов). При совместном использовании с []
, напротив, игнорирует набор заданных символов:
>>> st = 'Регулярные выражения в Python'
>>> re.match(r'^Р', st)
<re.Match object; span=(0, 1), match='Р'>
>>> re.findall(r'регулярные^[pyt]', st, re.I)
[]
$
– определяет, заканчивается ли строка нужным словом, символов или набором символов:
>>> st1, st2 = 'JavaScript', 'Python'
>>> print(re.search('pt$', st2))
None
>>> re.search('pt$', st1)
<re.Match object; span=(8, 10), match='pt'>
.
– соответствует 0 или более символов:
>>> st = 'кооперация, координация, коллаборация'
>>> re.findall(r'коо.*', st)
['кооперация, координация, коллаборация']
+
– соответствует 1 и более символов:
>>> st = 'лаборант'
>>> re.findall(r'лоб.+', st)
[]
>>> st = 'лоббирование'
>>> re.findall(r'лоб.+', st)
['лоббирование']
?
– обнаруживает наличие 0 или 1 совпадения с шаблоном, а также нейтрализует «жадные» выражения с метасимволами .
, *
, и +
:
>>> st = 'инновационный'
>>> re.findall(r'.?нн', st)
['инн', 'онн']
{}
– ищет точное число совпадений, которое указывается в скобках:
>>> st = 'паллиативный, плеоназм, баллистическая, конгрегация, аллопеция'
>>> re.findall(r'л{2}', st)
['лл', 'лл', 'лл']
|
– обнаруживает совпадение с любым из указанных вариантов:
>>> st = 'есть карандаши двух цветов - красные и синие'
>>> re.findall(r'красные|синие', st)
['красные', 'синие']
()
– выделяет группу символов:
>>> st = 'адреса наших сайтов - www.site1.ru, www.site2.com, www.site3.io'
>>> print(re.sub(r'(www.)', r'https://', st))
адреса наших сайтов - https://site1.ru, https://site2.com, https://site3.io
<>
– используется для работы с именованными группами:
>>> info = 'января 12, 2002'
>>> pattern = r'^(?P<месяц>\w+)\s(?P<день>\d+)\,?\s(?P<год>\d+)'
>>> matches = re.search(pattern, info)
>>> print(f"Писатель родился в {matches.group('год')} году, {matches.group('день')} {matches.group('месяц')}")
Писатель родился в 2002 году, 12 января
Последовательности
Как уже упоминалось выше, знак\
обозначает определенную последовательность символов.
\A
– проверяет, начинается ли строка с заданной последовательности символов или слов:
>>> text = 'О бойся Бармаглота, сын! Он так свирлеп и дик, А в глуще рымит исполин – Злопастный Брандашмыг.'
>>> re.match(r'\AО бойся', text)
<re.Match object; span=(0, 7), match='О бойся'>
\b
– проверяет, 1) начинается ли 2) заканчивается ли слово специфической последовательностью символов:
>>> info = 'www.mysite.com, www.yoursite.com, www.oursite.io'
>>> re.findall(r'(www.)\b', info)
['www.', 'www.', 'www.']
>>> re.findall(r'(.io)\b', info)
['.io']
\B
– возвращает совпадение, если указанные символы присутствуют в строке, но 1) не в начале 2) не в конце слов:
>>> st = 'красный, зеленый, нытик'
>>> re.findall(r'\Bны', st)
['ны', 'ны']
>>> re.findall(r'й\B', st)
[]
\d
– определяет, есть ли в строке цифры от 0 до 9:
>>> st = 'собеседование назначено на 12 мая'
>>> re.findall(r'\d', st)
['1', '2']
\D
– соответствует всем символам, кроме цифр:
>>> st = '!#!@#@$%^номер начинается с +7'
>>> re.findall(r'\D', st)
['!', '#', '!', '@', '#', '@', '$', '%', '^', 'н', 'о', 'м', 'е', 'р', ' ', 'н', 'а', 'ч', 'и', 'н', 'а', 'е', 'т', 'с', 'я', ' ', 'с', ' ', '+']
\s
– соответствует одному пробелу:
>>> st = 'один пробел'
>>> re.search(r'\s', st)
<re.Match object; span=(4, 5), match=' '>
\S
– напротив, соответствует любому символу, кроме пробела:
>>> st = ' '
>>> re.findall(r'\S', st)
[]
\w
– соответствует любой букве, цифре или символу _
:
>>> st1, st2 = '!@$^^$%&*()@', 's5tf7_'
>>> re.findall(r'\w', st1)
[]
>>> re.findall(r'\w', st2)
['s', '5', 't', 'f', '7', '_']
\W
– совпадает с любым специальным символом, игнорирует буквы, цифры и _
:
>>> st1, st2 = '!@~#$%^&*(', 'a28df_r4ghgh'
>>> re.findall(r'\W', st1)
['!', '@', '~', '#', '$', '%', '^', '&', '*', '(']
>>> re.findall(r'\W', st2)
[]
\Z
– проверяет, заканчивается ли строка нужной последовательностью:
>>> st1, st2 = 'самый популярный язык - Python', 'главный язык интернета - JavaScript'
>>> re.search(r'Script\Z', st2)
<re.Match object; span=(29, 35), match='Script'>
>>> print(re.search(r'Java\Z', st1))
None
Наборы и диапазоны символов
При составлении регулярных выражений диапазоны и наборы символов заключают в скобки []
. Рассмотрим примеры таких шаблонов.
[абвгд]
,[1234]
, [!@%^]
– находит совпадения с указанными буквами (цифрами, спецсимволами) в строке:
>>> st1, st2 = 'строка без чисел', 'строка с ч1и2с3л4а5м6и'
>>> re.findall(r'[сбч]', st1)
['с', 'б', 'ч', 'с']
>>> re.findall(r'[6789]')
['6']
>>> re.findall(r'[!@#$%^]', 'abrakadabr@')
['@']
[а-е]
, [а-еА-Е]
, [5-7]
– находят совпадения с буквами и цифрами из указанных диапазонов:
>>> st1, st2 = 'Мурзилка - советский журнал для детей', 'любимая цифра - 5'
>>> re.findall(r'[дежз]', st1)
['з', 'е', 'ж', 'д', 'д', 'е', 'е']
>>> re.findall(r'[м-оМО]', st1)
['М', 'о', 'н']
>>> re.findall(r'[4-7]', st2)
['5']
[^абв]
, [^123]
, [^!@#$]
– совпадает с любым символом, не входящим в указанный набор (диапазон):
>>> st = 'пример строки с ц1и2ф3рами и с!мвол@ми'
>>> re.findall(r'[^3-5]', st)
['п', 'р', 'и', 'м', 'е', 'р', ' ', 'с', 'т', 'р', 'о', 'к', 'и', ' ', 'с', ' ', 'ц', '1', 'и', '2', 'ф', 'р', 'а', 'м', 'и', ' ', 'и', ' ', 'с', '!', 'м', 'в', 'о', 'л', '@', 'м', 'и']
>>> re.findall(r'[^а-о]', st)
['п', 'р', 'р', ' ', 'с', 'т', 'р', ' ', 'с', ' ', 'ц', '1', '2', 'ф', '3', 'р', ' ', ' ', 'с', '!', '@']
>>> re.findall(r'[^#$%^&*()_+]', st)
['п', 'р', 'и', 'м', 'е', 'р', ' ', 'с', 'т', 'р', 'о', 'к', 'и', ' ', 'с', ' ', 'ц', '1', 'и', '2', 'ф', '3', 'р', 'а', 'м', 'и', ' ', 'и', ' ', 'с', '!', 'м', 'в', 'о', 'л', '@', 'м', 'и']
[0-9][0-9]
– позволяет задавать совпадения по двузначным цифрам:
>>> st = 'встреча назначена на 10:45'
>>> re.findall(r'[0-9][0-9]', st)
['10', '45']
Флаги в регулярных выражениях
В Python предусмотрены дополнительные параметры для Regex-шаблонов – флаги, причем использовать можно и полную, и краткую форму параметра. Ранее мы уже встречались с флагом re.I
– это краткий вариант re.IGNORECASE
. На практике флаг re.I
используется чаще всего, но остальные флаги тоже могут пригодиться.
re.I
, re.IGNORECASE
– игнорирует регистр:
>>> st = 'Яблоко от яблони недалеко падает'
>>> re.findall('ябл', st, re.I)
['Ябл', 'ябл']
re.A
, re.ASCII
– находит ASCII-символы, игнорируя все остальные:
st = 'одно из слов для обозначения дракона в японском - ドラゴン, doragon'
>>> re.findall(r'\w+', st, re.A)
['doragon']
re.M
, re.MULTILINE
– находит совпадения в начале ^
и конце $
каждой строки в многострочном фрагменте текста:
>>> st = 'Это пример текста,\n состоящего из\n нескольких строк\n'
>>> print(re.search(r'^\sсостоящего', st))
None
>>> print(re.search(r'^\sсостоящего', st, re.M))
<re.Match object; span=(19, 30), match=' состоящего'>
re.S
, re.DOTALL
– позволяет метасимволу .
возвращать совпадения по всем символам, включая \n
:
>>> st = 'пример\n строки\n \nс \nсимволом "\n"'
>>> re.findall('.', st)
['п', 'р', 'и', 'м', 'е', 'р', ' ', 'с', 'т', 'р', 'о', 'к', 'и', ' ', 'с', ' ', 'с', 'и', 'м', 'в', 'о', 'л', 'о', 'м', ' ', '"', '"']
>>> re.findall('.', st, re.S)
['п', 'р', 'и', 'м', 'е', 'р', '\n', ' ', 'с', 'т', 'р', 'о', 'к', 'и', '\n', ' ', '\n', 'с', ' ', '\n', 'с', 'и', 'м', 'в', 'о', 'л', 'о', 'м', ' ', '"', '\n', '"']
re.X
, re.VERBOSE
– позволяет использовать комментарии в Regex-шаблонах:
import re
pattern = re.compile(r'''
^[a-zA-Z0-9._-]+ # первая часть адреса содержит буквы, цифры, подчеркивание и дефис
@ # первая часть адреса соединяется со второй символом @
[a-zA-Z.-]+$ # заключительная часть - доменное имя (может содержать дефис) и доменная зона
''', re.X)
emails = ['python4ik@python.org', '@4##@%@mail.ru',
'python@yandex.ru', 'my_em@il@mail.com']
for email in emails:
if pattern.fullmatch(email):
print(email)
Вывод:
python4ik@python.org
python@yandex.ru
Опережающие и ретроспективные проверки
Как и большинство других современных языков программирования, Python поддерживает опережающие и ретроспективные проверки – позитивные и негативные.
Позитивная опережающая проверка:
X(?=Y) – вернуть X только в том случае, если выполняется Y.
Негативная опережающая проверка:
X(?!Y) – вернуть X только в том случае, если не выполняется Y.
Ретроспективная позитивная проверка:
(?<=Y)X – вернуть X при условии, что перед ним есть Y.
Ретроспективная негативная проверка:
(?<!Y)X – вернуть совпадение с X при условии, что перед ним нет Y.
Вот пример позитивной опережающей проверки – здесь Regex-шаблон находит в тексте слова, которые 1) имеют длину ровно 10 символов; 2) включают в себя подстроку «кофе»:
import re
pattern = re.compile(r'(?=\b\w{10}\b)\w*?кофе\w*', re.I)
text = '''В кофейне появились новые кофемашина и кофемолка.
Кофемашина делает все виды кофеиносодержащих
и некофейных напитков (чай и какао).'''
print(pattern.findall(text))
Вывод:
['кофемашина', 'Кофемашина', 'некофейных']
А это пример негативной опережающей проверки – шаблон находит совпадения только по тем цифрам, после которых нет «руб»:
>>> import re
>>> st = '1 месяц хостинга стоит 200 руб'
>>> re.findall(r'\d+(?!\sруб)\b', st)
['1']
Ретроспективная позитивная проверка здесь находит числа, перед которыми стоит «рублей»:
>>> st = 'VPN на 2 месяца стоит рублей 300'
>>> re.findall(r'(?<=рублей\s)\d+', st)
['300']
А здесь ретроспективная негативная проверка находит числа, перед которыми нет «примерно»:
>>> st = 'Стоимость 2 приличных ноутбуков - примерно 4 тысячи долларов'
>>> re.findall(r'(?<!примерно\s)\d+', st)
['2']
Практика
Задание 1
Напишите регулярное выражения, которое находит в полученной от пользователя строке все слова с дефисом.
Пример ввода:
Почему-то часто никак как-то получилось что-то зачем-то опять Кто-то
Вывод:
['Почему-то', 'как-то', 'что-то', 'зачем-то', 'Кто-то']
Решение с Regex:
import re
st = input()
print(re.findall(r'\b[а-я]+-[а-я]+\b', st, re.I))
Решение без Regex:
st = input().lower().split()
print([i for i in st if '-' in i])
Задание 2
Напишите программу, которая с помощью Regex-шаблона определяет, сколько слов в полученной от пользователя строке начинаются с «ко» или «коо».
Пример ввода:
Книга компьютер крот Колобок колхоз кооперация ноутбук карандаш координатор
Вывод:
5
Решение с Regex:
import re
st = input()
print(len(re.findall(r'ко{1,2}', st, re.I)))
Решение без Regex:
st = input().lower().split()
print(len([i for i in st if i.startswith('ко') or i.startswith('коо')]))
Задание 3
Напишите регулярное выражение, которое удаляет из текста все знаки препинания, кроме дефиса.
Пример ввода:
"Это" - фрагмент текста, для обработки?!..
Вывод:
Это - фрагмент текста для обработки
Решение с Regex:
import re
st = input()
print(re.sub(r'[,.?!:;"]', '', st))
Решение без Regex:
st = input()
print(''.join([i for i in st if i.isalpha() or i == ' ' or i == '-']))
Задание 4
Напишите регулярное выражение, которое находит в полученной от пользователя строке все слова, содержащие подстроку «круж», но не в начале и не в конце слова.
Пример ввода:
окружность кружево кружка окружение головокружение кружок кружкруж
Вывод:
окружность
окружение
головокружение
Решение с Regex:
import re
st = input().split()
for i in st:
if re.search(r'\Bкруж\B', i):
print(i)
Решение без Regex:
st = input().split()
for i in st:
if 'круж' in i:
if i.startswith('круж'):
continue
elif i.endswith('круж'):
continue
else:
print(i)
Задание 5
Напишите регулярное выражение, которое меняет формат даты в URL с ГГГГ/ММ/ДД на ДД/ММ/ГГГГ.
Пример ввода:
https://www.washingtonpost.com/technology/2023/02/14/what-is-temu-super-bowl-commercial/
Вывод:
https://www.washingtonpost.com/technology/14/02/2023/what-is-temu-super-bowl-commercial/
Решение:
import re
url = input()
print(re.sub(r'(\d{4})/(\d{1,2})/(\d{1,2})', r'\3/\2/\1', url))
Задание 6
Напишите программу, которая:
- Получает от пользователя n строк с данными студентов.
- Извлекает имена, фамилии и оценки по предметам без использования методов строк и словарей.
- Создает и выводит список словарей.
Пример ввода:
5
Денис,Ефремов,5,5,3,4
Юлия,Демидова,5,3,4,5
Евгения,Артемова,4,4,4,5
Сергей,Егоров,4,4,4,3
Кирилл,Антонов,4,5,3,3
Вывод:
[{'имя': 'Денис', 'фамилия': 'Ефремов', 'математика': '5', 'физика': '5', 'химия': '3', 'биология': '4'}, {'имя': 'Юлия', 'фамилия': 'Демидова', 'математика': '5', 'физика': '3', 'химия': '4', 'биология': '5'}, {'имя': 'Евгения', 'фамилия': 'Артемова', 'математика': '4', 'физика': '4', 'химия': '4', 'биология': '5'}, {'имя': 'Сергей', 'фамилия': 'Егоров', 'математика': '4', 'физика': '4', 'химия': '4', 'биология': '3'}, {'имя': 'Кирилл', 'фамилия': 'Антонов', 'математика': '4', 'физика': '5', 'химия': '3', 'биология': '3'}]
Решение:
import re
pattern = re.compile(r'''(?P<имя>[^,]+), # именованная группа 1
(?P<фамилия>[^,]+), # именованная группа 2 и так далее
(?P<математика>[^,]+),
(?P<физика>[^,]+),
(?P<химия>[^,]+),
(?P<биология>[^,]+)
''', re.X)
grades = []
for i in range(int(input())):
line = input()
grades.append(pattern.search(line).groupdict())
print(grades)
Задание 7
Напишите регулярные выражения, которые:
- Заменяют все вхождения слова «красный» на «зеленый», но только в том случае, если перед словом «красный» нет союза «и».
- Находят все слова, которые не заканчиваются на «и» или «ый».
- Находят все слова, которые не начинаются с букв «к», «ф», «о» и имеют длину 2 и более символов.
Пример текста:
st = '''красноватый фиолетовый и красный
красный и желтый красный желтый и красный
красный и оранжевый прекрасный окрас
розоватый и красный краснота'''
Вывод:
красноватый фиолетовый и красный
зеленый и желтый зеленый желтый и красный
зеленый и оранжевый прекрасный окрас
розоватый и красный краснота
окрас краснота
желтый желтый прекрасный розоватый
Решение:
import re
st = '''красноватый фиолетовый и красный
красный и желтый красный желтый и красный
красный и оранжевый прекрасный окрас
розоватый и красный краснота'''
print(re.sub(r'(?<!\и\s)\b\красный\b', 'зеленый', st), '\n')
print(*re.findall(r'\b\w+\b(?<![иый])', st), '\n')
print(*re.findall(r'(?![кфо])\b\w{2,}', st))
Задание 8
Напишите регулярные выражения, которые:
- Удаляют все html-теги из полученной от пользователя строки.
- Вставляют пробелы перед заглавными буквами в тексте и ставят точки в конце предложений.
Пример ввода:
<h1>Это заголовок первого уровня</h1><p>Это текст параграфа<strong>Это важный текст внутри параграфа</strong></p><p>Это второй параграф</p>
Вывод:
Это заголовок первого уровня. Это текст параграфа. Это важный текст внутри параграфа. Это второй параграф.
Решение:
import re
st = input()
st = re.sub('<[^<>]+>', '', st)
print(re.sub(r'(\w)([А-Я]|$)', r'\1. \2', st))
Задание 9
Напишите регулярное выражение для валидации пароля. Надежный пароль имеет длину от 8 до 20 символов и включает в себя хотя бы:
- один символ в верхнем регистре;
- один символ в нижнем регистре;
- одну цифру;
- один спецсимвол из набора
@$!%*#?&
.
Пример ввода 1:
cheBur@sh!ka
Вывод:
Ненадежный пароль
Пример ввода 2:
cheBur@sh#ka5
Вывод:
Надежный пароль
Решение:
import re
valid = re.compile(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!#%*?&]{8,20}$')
passw = input()
print('Надежный пароль' if valid.fullmatch(passw) else 'Ненадежный пароль')
Задание 10
Напишите программу, которая получает от пользователя строку с IP-адресом и определяет, является ли этот адрес:
- корректным IPv4-адресом;
- корректным IPv6-адресом;
- адресом некорректного формата.
Корректный IPv4-адрес соответствует формату x1.x2.x3.x4, где 0 <= xi <= 255, и не содержит ведущих нулей. В корректный IPv6 адрес, состоящий из 128 битов, входят восемь групп из четырех шестнадцатеричных цифр; группы разделены двоеточиями.
Пример ввода 1:
192.168.1.0
Вывод:
Корректный IPv4
Пример ввода 2:
2001:0db8:85a3:0000:0000:8a2e:0370:7334
Вывод:
Корректный IPv6
Пример ввода 3:
192.168.1.000
Вывод:
Адрес имеет некорректный формат
Решение:
import re
valid_ip4 = re.compile(r'(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])')
valid_ip6 = re.compile(r'((([0-9a-fA-F]){1,4}):){7}([0-9a-fA-F]){1,4}')
ip = input()
if valid_ip4.fullmatch(ip):
print('Корректный IPv4')
elif valid_ip6.fullmatch(ip):
print('Корректный IPv6')
else:
print('Адрес имеет некорректный формат')
Подведем итоги
Regex – мощный, но достаточно сложный инструмент: для конструирования и тестирования шаблонов лучше пользоваться специальными сервисами, которые помогают визуализировать результат работы выражения. Во многих случаях регулярные выражения можно заменить методами строк или специальным html/xml/ txt парсером.