🐍 Самоучитель по Python для начинающих. Часть 16: Регулярные выражения

Рассмотрим встроенные функции модуля 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:

regex101 – популярный онлайн конструктор Regex-шаблонов

Основные 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
https://abra.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

Если нужный фрагмент содержится в тексте, но не в начале строки – 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'>

.* – используется для поиска любого количества любых символов, кроме \n:

>>> html = "<p>Hello, World!</p>"
>>> print(re.findall('<p>(.*?)</p>', html)[0])
Hello, World!

.+ – соответствует любой строке, которая содержит хотя бы один символ, кроме \n:

import re
html = '''<h2>Пример использования регулярного выражения</h2><p>Другой пример</p>
<p>Третий пример с новой строки</p>'''
pattern = r"<p>.+</p>"
match = re.search(pattern, html)
print(match) #выведет <re.Match object; span=(51, 71), match='<p>Другой пример</p>'>

? – обнаруживает наличие 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

Напишите регулярные выражения, которые:

  1. Заменяют все вхождения слова «красный» на «зеленый», но только в том случае, если перед словом «красный» нет союза «и».
  2. Находят все слова, которые не заканчиваются на «и» или «ый».
  3. Находят все слова, которые не начинаются с букв «к», «ф», «о» и имеют длину 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

Напишите регулярные выражения, которые:

  1. Удаляют все html-теги из полученной от пользователя строки.
  2. Вставляют пробелы перед заглавными буквами в тексте и ставят точки в конце предложений.

Пример ввода:

<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 парсером.

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

Содержание самоучителя

  1. Особенности, сферы применения, установка, онлайн IDE
  2. Все, что нужно для изучения Python с нуля – книги, сайты, каналы и курсы
  3. Типы данных: преобразование и базовые операции
  4. Методы работы со строками
  5. Методы работы со списками и списковыми включениями
  6. Методы работы со словарями и генераторами словарей
  7. Методы работы с кортежами
  8. Методы работы со множествами
  9. Особенности цикла for
  10. Условный цикл while
  11. Функции с позиционными и именованными аргументами
  12. Анонимные функции
  13. Рекурсивные функции
  14. Функции высшего порядка, замыкания и декораторы
  15. Методы работы с файлами и файловой системой
  16. Регулярные выражения
  17. Основы скрапинга и парсинга
  18. Основы ООП – инкапсуляция и наследование
  19. Основы ООП – абстракция и полиморфизм
  20. Графический интерфейс на Tkinter
  21. Основы разработки игр на Pygame
  22. Основы работы с SQLite
  23. Основы веб-разработки на Flask
  24. Основы работы с NumPy
  25. Основы анализа данных с Pandas
***

Шпаргалка по регулярным выражениям

Скачать в формате .pdf
***

Материалы по теме

Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека питониста»

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ

admin
11 декабря 2018

ООП на Python: концепции, принципы и примеры реализации

Программирование на Python допускает различные методологии, но в его основе...
admin
28 июня 2018

3 самых важных сферы применения Python: возможности языка

Существует множество областей применения Python, но в некоторых он особенно...
admin
13 февраля 2017

Программирование на Python: от новичка до профессионала

Пошаговая инструкция для всех, кто хочет изучить программирование на Python...