Наталья Кайда 20 февраля 2023

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

Рассмотрим встроенные функции модуля re, научимся компилировать Regex-выражения и узнаем, как делать опережающие и ретроспективные проверки – позитивные и негативные. В конце статьи, как всегда, – 10 интересных заданий с решениями.
🐍 Самоучитель по Python для начинающих. Часть 16: Регулярные выражения

Регулярные выражения (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._-][email protected][a-zA-Z-.]+$'
    

Этот шаблон – один из простейших, Regex-выражения для проверки email-адресов могут выглядеть гораздо сложнее. Для разработки и тестирования сложных Regex шаблонов используют специальные сервисы, например, Regex101:

regex101 – популярный онлайн конструктор Regex-шаблонов
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.)?[[email protected]:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()[email protected]:%_+.~#?&/=]*)'
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 = 'П#$%^рив&*ет, ка@!к успе[email protected]хи с 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 = '!#[email protected]#@$%^номер начинается с +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 = '[email protected]$^^$%&*()@', 's5tf7_'
>>> re.findall(r'\w', st1)
[]
>>> re.findall(r'\w', st2)
['s', '5', 't', 'f', '7', '_']

    

\W – совпадает с любым специальным символом, игнорирует буквы, цифры и _:

        >>> st1, st2 = '[email protected]~#$%^&*(', '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], [[email protected]%^] – находит совпадения с указанными буквами (цифрами, спецсимволами) в строке:

        >>> st1, st2 = 'строка без чисел', 'строка с ч1и2с3л4а5м6и'
>>> re.findall(r'[сбч]', st1)
['с', 'б', 'ч', 'с']
>>> re.findall(r'[6789]')
 ['6']
>>> re.findall(r'[[email protected]#$%^]', '[email protected]')
['@']

    

[а-е], [а-еА-Е], [5-7] – находят совпадения с буквами и цифрами из указанных диапазонов:

        >>> st1, st2 = 'Мурзилка - советский журнал для детей', 'любимая цифра - 5'
>>> re.findall(r'[дежз]', st1)
['з', 'е', 'ж', 'д', 'д', 'е', 'е']
>>> re.findall(r'[м-оМО]', st1)
['М', 'о', 'н']
>>> re.findall(r'[4-7]', st2)
['5']

    

[^абв], [^123], [^[email protected]#$] – совпадает с любым символом, не входящим в указанный набор (диапазон):

        >>> 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 = ['[email protected]', '@4##@%@mail.ru',
          '[email protected]', '[email protected]@mail.com']

for email in emails:
    if pattern.fullmatch(email):
        print(email)

    

Вывод:

        [email protected]
[email protected]

    

Опережающие и ретроспективные проверки

Как и большинство других современных языков программирования, 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:

        [email protected]!ka


    

Вывод:

        Ненадежный пароль
    

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

        [email protected]#ka5


    

Вывод:

        Надежный пароль
    

Решение:

        import re
valid = re.compile(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\[email protected]$!#%*?&]{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. Основы скрапинга и парсинга
***

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

<a href="https://t.me/progbook2/2043" target="_blank" rel="noopener noreferrer nofollow">Скачать в формате .pdf</a>
Скачать в формате .pdf
***

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

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

МЕРОПРИЯТИЯ

Комментарии

ВАКАНСИИ

Добавить вакансию
Аналитик 1С
Москва, по итогам собеседования
Junior Backend Developer
Москва, от 72000 RUB до 96000 RUB
Project Manager
Москва, от 100000 RUB до 200000 RUB

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