🐍 Регулярные выражения в Python за 5 минут: теория и практика для новичков и не только

Учимся использовать Regex: немного теории, примеры выражений и 10 практических заданий для отработки навыков.

Что такое Regex

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

Регулярные выражения состоят из набора литералов (букв и цифр) и метасимволов и выглядят примерно так: r'(https?://)?(www\.)?youtube\.(com|nl)/watch\?v=([\w-]+)(&.*?)?(?=[^-\w&=%])'Используя метасимволы, можно создавать сложные шаблоны, содержащие специальные конструкции для работы с определенными последовательностями и группами символов.

Regex-выражения применяют для обработки текстовых данных, в том числе в скриптах для веб-скрапинга. Кроме того, Regex используют в составе OCR-приложений для очистки отсканированного текста.

Regex в Python

Большинство современных языков программирования поддерживают регулярные выражения, однако степень удобства использования Regex в разных языках варьируется. Python предоставляет простые и понятные методы для работы с регулярными выражениями. Все Regex инструменты находятся в модуле re, который входит в стандартный дистрибутив Python – достаточно импортировать его в свой проект:

import re

Для экранирования служебных символов в шаблонах поиска и замены используют два способа – обратный слэш \ и «сырые» строки r''. Второй метод предпочтительнее – он позволяет избежать нагромождения слэшей в шаблонах.

Основные функции Regex

re.match() – находит вхождение фрагмента в начале строки. Обычный формат использования – re.match(r'шаблон', строка):

import re
s = "утка крякает, кукушка кукует, петух кукарекает"
match = re.match(r'ку', s)
print(match)

Этот код вернет None, несмотря на то, что в строке есть 5 фрагментов «ку». Это происходит потому, что оба фрагмента расположены не в начале строки.

re.search() – находит первое вхождение фрагмента в любом месте и возвращает объект match. Если в строке есть другие фрагменты, соответствующие запросу, re.search их проигнорирует. У re.search есть дополнительные методы:

.span() – возвращает кортеж, содержащий начальную и конечную позиции искомого фрагмента.

.string – вернет строку, переданную в функцию re.search.

.group() – возвращает фрагмент строки, в котором было обнаружено совпадение.

#Пример использования re.search с дополнительными методами
import re
s = "oт топота копыт пыль по полю летит"
match = re.search(r'по', s)
print(match, match.span(), match.string, match.group(), sep='\n')
#Вывод:
<re.Match object; span=(5, 7), match='по'>
(5, 7)
oт топота копыт пыль по полю летит
по

re.findall() – находит все вхождения фрагмента, в любом месте. Функция re.findall() учитывает регистр символов. Чтобы в результат вошли фрагменты с символами в другом регистре, применяют флаг re.IGNORECASE:

import re
s = "Не видно, ликвидны акции или неликвидны."
match = re.findall(r'не', s, re.I)
print(match)

re.split() – расщепляет строку по заданному шаблону. Количество расщеплений задается флагом – в этом примере от строки отделяется только первое слово:

import re
s = "Обладаешь ли ты налогооблагаемой благодатью?"
res = re.split(r' ', s, 1)
print(res) 

re.sub() – заменяет фрагмент в соответствии с шаблоном:

import re
s = "Коала какао лениво лакала"
res = re.sub(r'коала', 'макака', s, flags=re.I)
print(res)

re.compile() – создает объект из регулярного выражения. Применяется, если один и тот же поисковый шаблон используется в коде несколько раз:

import re
st = re.compile('угнал')
res1 = st.findall("Карл у Клары угнал Maclaren, а Клара у Карла угнала Corvette.")
res2 = st.findall("Карл у Клары угнал кораллы, а Клара у Карла угнала кларнет.")
print(res1, res2, sep='\n')

Все перечисленные выше примеры предназначены для выполнения самых простых задач по поиску и замене фрагментов текста. Возможности Regex в Python намного шире: шаблоны могут включать условия, учитывать (или игнорировать) группы символов и диапазоны значений. Для создания таких регулярных выражений используют специальные конструкции, состоящие из метасимволов.

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

Основные метасимволы в Regex

[] – используется для указания набора или диапазона символов – re.findall(r'[с-я]', "Камер-юнкер юркнул в бункер", re.I), re.findall(r'[аж]', "ажиотаж, мандраж, багаж").

\ – указывает на начало последовательности (мы рассмотрим их ниже) или экранирует служебные символы.

. – выбирает любой символ, кроме новой строки \n.

^ – проверяет, начинается ли строка с определенного символа / слова / набора символов. Например, r'^Привет' проверит, начинается ли строка с «Привет». Метасимвол ^ в наборе [] имеет другое значение – проверяет, отсутствуют ли в строке определенные символы (подробнее об этом ниже).

$ – проверяет, заканчивается ли строка в соответствии с шаблоном r'До свиданья.$'.

* – ноль или больше совпадений с шаблоном r'ко.*аборация'.

+ – одно и более совпадений r'к.+ператив'.

? – ноль или одно совпадение r'ф.?нтастика'. Кроме того, нейтрализует «жадность» выражений, которые используют ., *, + для выбора любых символов.

{} – точное число совпадений r'Интерсте.{2}ар'.

| – любой из двух вариантов r'уйду|останусь'.

() – захватывает группу для дальнейших манипуляций – re.sub(r'(www)', r'\1.', "wwwwear-gear.com").

<> – создает именованную группу – re.search('(?P<группа1>\w+),(?P<группа2>\w+),(?P<группа3>\w+)', 'дом,улица,фонарь')

Последовательности

Знаком слэша \ обозначается специфическая последовательность символов.

\A – проверяет, начинается ли строка с определенной последовательности символов. Например, re.findall(r"\AДом", txt), проверит, начинается ли предложение со слова «Дом».

\b – возвращает совпадение, если слово начинается или заканчивается нужной последовательностью символов. Выражение re.findall(r".com\b", s) проверит, есть ли в строке хотя бы одно доменное имя зоны .com.

\B – возвращает совпадение, если определенные символы есть в строке, но не в начале или не в конце слова – re.findall(r"\Bро", 'розовая от мороза'), re.findall(r'ин\B', 'синий апельсин').

\d – проверяет, что в строке есть цифры от 0 до 9 – re.findall("\d", 'при пожаре звоните 112').

\D – удостоверяет, что цифр в строке нет – re.findall("\D", 'цифр нет').

\s – проверяет наличие пробелов в строке – re.findall("\s", "один пробел").

\S – возвращает совпадение, если в строке есть любые символы, кроме пробелов – re.findall("\S", "непустая строка").

\w – проверяет, есть ли в строке «словесные» символы – знак нижнего подчеркивания, цифры и буквы – re.findall(r"\w", "_\\\\").

\W – возвращает совпадение по каждому «несловесному» символу – re.findall("\W", "здесь есть такие символы!").

\Z – проверит, заканчивается ли строка нужной последовательностью символов – re.findall("конец\Z", "это конец").

Наборы и диапазоны символов

Наборы и диапазоны в регулярных выражениях заключены в квадратные скобки:

[есн] – проверит, есть ли в строке любой из указанных символов е, с или нre.findall("[есн]", "здесь есть несколько символов из набора"). Наличие любой цифры из набора проверяется так же – [0169].

[а-е] – вернет совпадения по каждому символу из алфавитного диапазона – re.findall("[а-е]", "здесь есть символы из диапазона"). Таким же образом возвращает совпадения по диапазону цифр – [5-9]. Чтобы не использовать флаг re.IGNORECASE, диапазон можно указывать так – [а-еА-Е].

[^абвгд] – проверит наличие в строке символов, кроме указанных в наборе – re.findall("[^абвгд]", "АБВГДейка – детская передача", re.I).

[0-5][0-9] – возвращает совпадения по двузначным цифрам от 00 до 59re.findall("[0-5][0-9]", "будильник сработает в 07:45").

Флаги в Regex

Функциональность регулярных выражений расширяется за счет флагов:

Краткий синтаксис Полный синтаксис Назначение
re.A re.ASCII Возвращает совпадения только по ASCII-символам вместо всей таблицы Unicode.
re.I re.IGNORECASE Игнорирует регистр символов.
re.M re.MULTILINE Используется совместно с метасимволами ^ и $. В первом случае возвращает совпадения в начале каждой новой строки \n, во втором – в конце \n.
re.S re.DOTALL Заставляет метасимвол . возвращать совпадения по абсолютно всем символам, включая \n. Без этого флага точка . соответствует любому символу, кроме \n.
re.X re.VERBOSE Разрешает комментарии в Regex-выражениях.
re.L re.LOCALE Учитывает региональные настройки при использовании \w, \W, \b, \B, \s и \S. Используется только при работе с байтовыми строками, не совместим с re.ASCII.

Онлайн-конструкторы регулярных выражений

Чем сложнее регулярное выражение, тем труднее его правильно составить и протестировать. В интернете есть немало визуализаторов Regex, которые значительно упрощают эту задачу. Самый удобный ресурс – regex101. Сайт предоставляет справочную и отладочную информацию, позволяет визуально тестировать шаблоны для поиска и замены. Помимо Python, поддерживает PHP, Java, Golang и JavaScript.

Конструктор Regex

Примеры использования регулярных выражений в Python

Задача 1:

Написать регулярное выражение для извлечения из текста всех email-адресов.

Решение:

import re
s = 'По всем вопросам пишите на vasiliy-pupkin@gmail.com, или на secondemail@yandex.ru, отвечу сразу. Или пишите моему ассистенту secretary@gmail.com!'
emails = re.findall(r'[\w\.-]+@[\w\.-]+', s)
for email in emails:
    print(email)

Задача 2:

Имеется файл transactions.txt, в котором даты указаны в формате MM/DD/YYYY, при этом в некоторых случаях месяц обозначен первыми тремя буквами: NOV, dec, JAN. Нужно привести даты к формату MM-DD-YYYY.

#формат дат в файле transactions.txt
nov/14/2021
dec/15/2021
12/16/2021
dec/17/2021
jan/03/2022
JAN/10/22

Решение:

import fileinput
import re
fn = "transactions.txt" 
for line in fileinput.input(fn, inplace=True):
    new_line = re.sub('(\d{2}|[a-yA-Y]{3})\/(\d{2})\/(\d{2, 4})', r'\1-\2-\3', line)
    print(new_line)
#Содержимое файла после выполнения кода:
nov-14-2021
dec-15-2021
12-16-2021
dec-17-2021
jan-03-2022
JAN-10-2022

Задача 3:

Вводится последовательность строк. Нужно вывести строки, в которых фрагмент «кот» присутствует в качестве подстроки не менее 2 раз.

#Пример ввода
кот-кот
кот и кот
котофей
котейка кот
кот и котенок

Решение:

import re
import sys
for line in sys.stdin:
    line = line.strip()
    if re.search(r"кот.*?кот", line):
        print(line)

Задача 4:

Дана последовательность строк. Нужно вывести те, в которых «кот» встречается в качестве отдельного слова.

#Пример ввода:
кот в сапогах
кошка и кот
котофей
котяра

Решение:

import re
import sys
for line in sys.stdin:
    line = line.rstrip()
    if re.search(r"\bкот\b", line):
        print(line)
#Вывод
кот в сапогах
кошка и кот

Задача 5:

Вывести слова, состоящие из двух одинаковых слогов.

#Пример ввода
тартар
тик-так
сносно
варвар
барабан

Решение:

import re
import sys
for line in sys.stdin:
    line = line.strip()
    if re.search(r"\b(\w+)\1\b", line):
        print(line)
#Вывод
тартар
сносно
варвар

Задача 6:

Вводится последовательность строк. В каждой строке нужно поменять местами две первые буквы в каждом слове, состоящем из двух и более букв.

#Пример ввода
это пример текста
в котором нужно поменять буквы

Решение:

import sys
import re
for line in sys.stdin:
    line = line.rstrip()
    print(re.sub(r'\b(\w)(\w)', r"\2\1", line))
#Вывод
тэо рпимер еткста
в октором унжно опменять убквы

Задача 7:

Напишите функцию для валидации мобильного номера в международном формате. Корректным считается представление номера в таком виде:

+7(912)15-16-896, 8(912)15-16-896
+79121516896, 89121516896
+7(912)151-68-96, 8(912)151-68-96
+7912-151-6896, 87912-151-6896

Решение:

import re
pattern = re.compile(r'(\+7|8).*?(\d{2,3}).*?(\d{2,3}).*?(\d{2}).*?(\d{2})')
def isValid(number):
    if re.match(pattern, number):
        print("ДА")
    else:
        print("НЕТ")
isValid(input())

Задача 8:

Напишите программу для парсинга номеров телефонов с тестовой страницы.

Решение:

import urllib.request
from re import findall
url = "http://www.summet.com/dmsi/html/codesamples/addresses.html"
response = urllib.request.urlopen(url)
data = response.read()
s = data.decode()
phones = findall("\(\d{3}\) \d{3}-\d{4}", s)
for number in phones:
    print(number)

Задача 9:

Нужно извлечь все имена и фамилии из текста.

Решение:

import re
s = 'На встрече присутствовали: профессор Владимир Успенский, физик-ядерщик Сергей Ковалев, президент клуба Владимир Медведев и космонавт Юрий Титов.'
name = r"[А-Я][а-я]+,?\s+"
last_name = r"[А-Я][а-я]+"
persons = re.findall(name + last_name, s)
for item in persons:
    print(item)

Задача 10:

Нужно получить URL всех png и jpg изображений, использованных на главной странице proglib.io:

import re
import requests
def getURL(text):
    urls = []
    results = re.findall(r'(?:http\:|https\:)?\/\/.*\.(?:png|jpg)', text)
    for x in results:
        if not x.startswith('http:'):
            x = 'http:' + x
            urls.append(x)
    return urls
def getImages(url):
    resp = requests.get(url)
    urls = getURL(resp.text)
    print('urls', urls)
getImages('https://proglib.io')

Заключение

Regex в Python – мощный, гибкий, но достаточно сложный инструмент. Регулярные выражения сложно составлять, поддерживать и редактировать. При работе с текстовыми файлами Regex чаще всего можно заменить методами строк, а при парсинге, в большинстве случаев, использование XPath и CSS-селекторов окажется более эффективным.

***

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




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

admin
11 декабря 2018

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

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

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

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

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

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