Наталья Кайда 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._-]+@[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.)?[-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
***

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

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

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

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

Комментарии

ВАКАНСИИ

Добавить вакансию
Разработчик C++
Москва, по итогам собеседования

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