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

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

Рассмотрим встроенные функции модуля re, научимся компилировать Regex-выражения и узнаем, как делать опережающие и ретроспективные проверки – позитивные и негативные. В конце статьи, как всегда, – 10 интересных заданий с решениями.
2
🐍 Самоучитель по 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 секунд

    
***

Отлично! Вы освоили все основные функции для работы с регулярными выражениями.

Вы умеете применять готовые шаблоны с помощью findall, sub и search, и даже знаете, как ускорить их работу с помощью re.compile.

Но настоящая сила Regex — в умении писать собственные, даже самые сложные шаблоны. Готовы стать тем, кто не ищет их в интернете, а создает сам? В полной версии урока вас ждёт:

  • Полный справочник по всем метасимволам, последовательностям и диапазонам для создания Regex-шаблонов любой сложности.
  • Продвинутые техники, включая именованные группы, флаги и мощные опережающие проверки (lookarounds).
  • 10 практических задач, где вы с нуля напишете регулярные выражения для парсинга, валидации и сложной замены текста.


МЕРОПРИЯТИЯ

Комментарии

 
 
22 февраля 2023

Нашёл несколько ошибок, если бы сам не владел определёнными знаниями об этом модуле -- запутался бы. Пример одной из ошибок: «.– соответствует 0 или более символов», тогда как «.» это любой символ, а речь должна идти о метасимволе «» >> « – соответствует 0 или более символов».

22 февраля 2023

Опечатка. Выше написано, что . соответствует любому символу, кроме \n. А 0 и более соответствует .*, как и показано в примере:

>>> st = 'zoo'
>>> re.findall(r'zoo.*', st)
['zoo']

ВАКАНСИИ

Добавить вакансию
Backend developer (PHP / Go)
Москва, по итогам собеседования
Senior Go / Kubernetes Engineer
от 3000 USD до 7000 USD
Ведущий SRE инженер
Москва, по итогам собеседования

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

LIVE >

Подпишись

на push-уведомления