← Назад к курсу

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

Введение

Регулярные выражения (regex) - мощный инструмент для поиска и обработки текста по шаблонам. В Python они реализованы в модуле re.

1. Основные методы модуля re

Импорт модуля

import re

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

text = "Мой телефон: +7-999-123-45-67, email: test@example.com"

# re.search() - поиск первого совпадения
match = re.search(r'\d{2}', text)  # ищет 2 цифры подряд
print(match.group()) if match else print("Не найдено")

# re.findall() - поиск всех совпадений
numbers = re.findall(r'\d+', text)  # все последовательности цифр
print(numbers)  # ['7', '999', '123', '45', '67']

# re.finditer() - итератор по совпадениям
for match in re.finditer(r'\d+', text):
    print(f"Найдено: {match.group()} на позиции {match.start()}-{match.end()}")

# re.sub() - замена по шаблону
new_text = re.sub(r'\d', '#', text)  # заменяем все цифры на #
print(new_text)

# re.split() - разделение по шаблону
parts = re.split(r'[:,]', text)  # разделяем по : или ,
print(parts)

2. Основные элементы регулярных выражений

Специальные символы:

# . - любой символ, кроме новой строки
print(re.findall(r't.st', 'test tast t3st'))  # ['test', 'tast', 't3st']

# ^ - начало строки
print(re.findall(r'^Мой', text))  # ['Мой']

# $ - конец строки
print(re.findall(r'com$', 'test@example.com'))  # ['com']

# \d - цифра, \D - не цифра
print(re.findall(r'\d+', 'abc123xyz'))  # ['123']
print(re.findall(r'\D+', 'abc123xyz'))  # ['abc', 'xyz']

# \w - буква/цифра/_, \W - не \w
print(re.findall(r'\w+', 'Hello_123! Test.'))  # ['Hello_123', 'Test']

# \s - пробельный символ, \S - не \s
print(re.findall(r'\S+', 'Hello  World\nTest'))  # ['Hello', 'World', 'Test']

# [] - набор символов
print(re.findall(r'[aeiou]', 'Hello World'))  # ['e', 'o', 'o']

# [^] - исключающий набор
print(re.findall(r'[^aeiou\s]', 'Hello World'))  # ['H', 'l', 'l', 'W', 'r', 'l', 'd']

Квантификаторы (указатели количества):

# * - 0 или более раз
print(re.findall(r'\d*', 'a12b3c'))  # ['', '12', '', '3', '']

# + - 1 или более раз
print(re.findall(r'\d+', 'a12b3c'))  # ['12', '3']

# ? - 0 или 1 раз
print(re.findall(r'\d?', 'a12b3c'))  # ['', '1', '2', '', '3', '']

# {n} - ровно n раз
print(re.findall(r'\d{2}', 'a123b4567c'))  # ['12', '45', '67']

# {n,} - n или более раз
print(re.findall(r'\d{2,}', 'a123b4567c'))  # ['123', '4567']

# {n,m} - от n до m раз
print(re.findall(r'\d{2,3}', 'a123b4567c'))  # ['123', '456']

Группировка:

# () - группировка и захват
text = "Иван: 30 лет, Мария: 25 лет"
matches = re.findall(r'(\w+): (\d+)', text)
print(matches)  # [('Иван', '30'), ('Мария', '25')]

# (?:) - не захватывающая группа
matches = re.findall(r'(?:\w+): (\d+)', text)
print(matches)  # ['30', '25']

# | - или
print(re.findall(r'Иван|Мария', text))  # ['Иван', 'Мария']

3. Практические примеры

Валидация email:

def validate_email(email):
    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    return bool(re.match(pattern, email))

emails = ["test@example.com", "invalid.email", "user.name@domain.co.uk"]
for email in emails:
    print(f"{email}: {validate_email(email)}")

Извлечение телефонов:

def extract_phones(text):
    # Российские номера в разных форматах
    pattern = r'(\+7|8)[\s\-]?\(?\d{3}\)?[\s\-]?\d{3}[\s\-]?\d{2}[\s\-]?\d{2}'
    return re.findall(pattern, text)

text = """
Контакты: +7(999)123-45-67, 8-912-345-67-89, 
+7 999 123 45 67, 89123456789
"""
print(extract_phones(text))

Парсинг логов:

log = """
2024-01-15 10:30:45 INFO: User logged in
2024-01-15 10:31:10 ERROR: Database connection failed
2024-01-15 10:32:00 WARNING: High memory usage
"""

pattern = r'(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) (\w+): (.+)'
matches = re.findall(pattern, log)

for timestamp, level, message in matches:
    print(f"{timestamp} [{level}] {message}")

Поиск и замена с callback:

def multiply_numbers(match):
    number = int(match.group())
    return str(number * 2)

text = "Цены: 100, 200, 300 рублей"
result = re.sub(r'\d+', multiply_numbers, text)
print(result)  # Цены: 200, 400, 600 рублей

4. Флаги регулярных выражений

text = "Строка\nс\nразными\nрегистрами\nSTRING"

# re.IGNORECASE (re.I) - игнорирование регистра
print(re.findall(r'строка', text, re.IGNORECASE))  # ['Строка', 'STRING']

# re.MULTILINE (re.M) - многострочный режим
print(re.findall(r'^с', text, re.MULTILINE | re.IGNORECASE))  # ['с']

# re.DOTALL (re.S) - точка включает символ новой строки
print(re.findall(r'Строка.*STRING', text, re.DOTALL | re.IGNORECASE))

# re.VERBOSE (re.X) - разрешает комментарии и пробелы в шаблоне
pattern = re.compile(r'''
    \d{3}          # три цифры
    -?             # необязательный дефис
    \d{2}          # две цифры
    -?             # необязательный дефис
    \d{2}          # две цифры
''', re.VERBOSE)

5. Оптимизация и советы

Компиляция выражений:

# Для многократного использования
pattern = re.compile(r'\d{3}-\d{2}-\d{2}')
texts = ["123-45-67", "abc", "987-65-43"]

for text in texts:
    match = pattern.search(text)
    if match:
        print(f"Найден: {match.group()}")

Использование сырых строк (raw strings):

# Всегда используйте сырые строки для regex
pattern = r'\d+'  # правильно
pattern = '\\d+'  # сложнее читать и писать

Избегайте жадных квантификаторов:

text = "<div>Первый</div><div>Второй</div>"

# Жадный режим (по умолчанию)
print(re.findall(r'<div>.*</div>', text))  
# ['<div>Первый</div><div>Второй</div>']

# Ленивый режим
print(re.findall(r'<div>.*?</div>', text))  
# ['<div>Первый</div>', '<div>Второй</div>']

6. Упражнения для закрепления

# Задание 1: Извлечь все даты в формате DD.MM.YYYY
def extract_dates(text):
    pattern = r'\b\d{2}\.\d{2}\.\d{4}\b'
    return re.findall(pattern, text)

# Задание 2: Проверить сложность пароля
def check_password(password):
    """
    Пароль должен содержать:
    - минимум 8 символов
    - хотя бы одну цифру
    - хотя бы одну букву в верхнем регистре
    - хотя бы одну букву в нижнем регистре
    - хотя бы один специальный символ
    """
    if len(password) < 8:
        return False
    if not re.search(r'\d', password):
        return False
    if not re.search(r'[A-Z]', password):
        return False
    if not re.search(r'[a-z]', password):
        return False
    if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
        return False
    return True

# Задание 3: Очистить текст от HTML-тегов
def remove_html_tags(text):
    pattern = r'<[^>]+>'
    return re.sub(pattern, '', text)

# Тестирование
if __name__ == "__main__":
    # Тест для задания 1
    test_text = "Даты: 15.01.2024, 20.12.2023, не дата 123.456.789"
    print("Даты:", extract_dates(test_text))
    
    # Тест для задания 2
    passwords = ["Simple1!", "Complex123$", "weak", "NoSpecial1"]
    for pwd in passwords:
        print(f"{pwd}: {check_password(pwd)}")
    
    # Тест для задания 3
    html = "<h1>Заголовок</h1><p>Текст <b>жирный</b> и <i>курсив</i></p>"
    print("Без тегов:", remove_html_tags(html))

Заключение

Регулярные выражения - мощный инструмент, который требует практики. Начните с простых паттернов, используйте онлайн-тестеры (regex101.com), и постепенно переходите к более сложным выражениям.

Дополнительные ресурсы: