Re.sub в Python: мощная замена строк с регулярными выражениями

Пройдите тест, узнайте какой профессии подходите
Сколько вам лет
0%
До 18
От 18 до 24
От 25 до 34
От 35 до 44
От 45 до 49
От 50 до 54
Больше 55

Для кого эта статья:

  • Python-разработчики всех уровней, желающие углубить свои знания о регулярных выражениях
  • Специалисты по обработке и анализу данных, ищущие эффективные инструменты для очистки и трансформации данных
  • Студенты и обучающиеся, заинтересованные в практическом применении методов работы с текстовой информацией в Python

    Метод re.sub() — это настоящая швейцарская армейская бритва в наборе инструментов Python-разработчика. Когда обычные методы строк вроде replace() оказываются бессильны, регулярные выражения приходят на помощь, позволяя выполнять невероятно сложные операции замены текста всего в одну строку кода. Неважно, чистите ли вы данные для анализа, трансформируете XML в JSON или автоматизируете рефакторинг кода — понимание возможностей re.sub() значительно ускорит вашу работу с текстом и откроет новые горизонты в обработке данных. 🔍

Хотите научиться писать по-настоящему эффективный код на Python? Обучение Python-разработке от Skypro погружает студентов в практические аспекты работы с данными, включая мастерство регулярных выражений. Наши выпускники не просто знают синтаксис re.sub() — они умеют создавать элегантные решения для сложных задач обработки текста, которые впечатляют работодателей и экономят часы рабочего времени.

Основы метода re.sub() для замены строк в Python

Метод re.sub() — это мощный инструмент из стандартной библиотеки Python, позволяющий заменять текст с использованием регулярных выражений. В отличие от обычного метода строки replace(), re.sub() обеспечивает гибкий поиск и замену на основе шаблонов, а не только фиксированных подстрок.

Дмитрий Коваленко, Lead Python Developer

Однажды мне поручили задачу очистки 5000+ XML-файлов от устаревшей разметки. Первая мысль — открыть каждый файл и выполнить множественные замены. Я оценил время работы в 2-3 дня. Вместо этого я написал скрипт с re.sub(), который проанализировал все файлы за 8 минут. Регулярное выражение r'<legacy-tag[^>]*>(.*?)</legacy-tag>' с заменой на r'\1' позволило извлечь только полезный контент. Когда руководитель увидел результат и спросил, когда я закончу всю партию, я ответил: "Уже сделано". С тех пор re.sub() — мой верный инструмент для любой задачи массовой обработки текста.

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

import re

text = "Телефон: 123-456-7890"
result = re.sub(r'\d{3}-\d{3}-\d{4}', 'XXX-XXX-XXXX', text)
print(result) # Телефон: XXX-XXX-XXXX

В этом примере мы заменяем номер телефона в формате XXX-XXX-XXXX на маску. Регулярное выражение \d{3}-\d{3}-\d{4} ищет три цифры, тире, ещё три цифры, тире и четыре цифры.

Метод re.sub() чаще всего используется в следующих сценариях:

  • Нормализация и очистка данных (удаление лишних пробелов, спецсимволов)
  • Преобразование форматов (например, даты из одного формата в другой)
  • Анонимизация данных (маскирование личной информации)
  • Форматирование текста (преобразование разметки)
  • Валидация и исправление ввода пользователя
Операция Использование replace() Использование re.sub()
Замена конкретного текста ✅ Просто и быстро ⚠️ Избыточно для простых случаев
Замена с учетом шаблона ❌ Невозможно ✅ Идеальное решение
Замена с учетом регистра ❌ Требует дополнительной логики ✅ Встроенные флаги (re.IGNORECASE)
Производительность на больших текстах ✅ Обычно быстрее ⚠️ Может быть медленнее

Для успешного использования re.sub() необходимо понимать основы регулярных выражений. Регулярные выражения в Python реализованы через модуль re и используют стандартный синтаксис для поиска шаблонов в тексте. 🧩

Пошаговый план для смены профессии

Синтаксис и параметры re.sub() для эффективной работы

Полный синтаксис метода re.sub() выглядит следующим образом:

re.sub(pattern, repl, string, count=0, flags=0)

Рассмотрим каждый параметр в деталях:

  • pattern — регулярное выражение, описывающее текст для поиска
  • repl — строка для замены или функция, которая будет вызвана для каждого совпадения
  • string — исходная строка, в которой производится поиск и замена
  • count — максимальное количество замен (0 = заменить все вхождения)
  • flags — модификаторы поведения регулярного выражения

Наиболее часто используемые флаги:

Флаг Константа Описание Пример использования
Игнорирование регистра re.IGNORECASE Делает выражение нечувствительным к регистру re.sub(r'python', 'Python', text, flags=re.IGNORECASE)
Многострочный режим re.MULTILINE ^ и $ соответствуют началу и концу каждой строки re.sub(r'^#', '', text, flags=re.MULTILINE)
Точка включает перевод строки re.DOTALL Символ . также соответствует символу новой строки re.sub(r'<div>.*?</div>', '', text, flags=re.DOTALL)
Расширенный синтаксис re.VERBOSE Игнорирует пробелы в шаблоне и позволяет добавлять комментарии re.sub(r'''(?x) \d{3} # код города<br>[-\s]? # разделитель<br>\d{3} # первые 3 цифры''', 'XXX', text)

Рассмотрим несколько примеров использования параметров re.sub():

import re

# Базовая замена
text = "Python is awesome. python is powerful."
result = re.sub(r'python', 'Python', text, flags=re.IGNORECASE)
print(result) # Python is awesome. Python is powerful.

# Ограничение количества замен
text = "one two one two one"
result = re.sub(r'one', 'ONE', text, count=2)
print(result) # ONE TWO ONE two one

# Использование нескольких флагов
html = """<div>
First paragraph
</div>
<div>
Second paragraph
</div>"""
result = re.sub(r'<div>.*?</div>', '[CONTENT]', html, flags=re.DOTALL)
print(result) # [CONTENT]

При составлении шаблонов для re.sub() важно учитывать принцип "жадности" и "ленивости" квантификаторов. Жадные квантификаторы (*, +, {n,m}) стремятся захватить как можно больше символов, а ленивые (*?, +?, {n,m}?) — как можно меньше. Правильный выбор между ними может существенно влиять на результат замены. 📝

Группы захвата в регулярных выражениях и обратные ссылки

Одна из самых мощных функций метода re.sub() — возможность использовать группы захвата и обратные ссылки. Группы захвата позволяют выделить части текста, соответствующие определённым фрагментам шаблона, а затем использовать их в строке замены.

Группы захвата создаются с помощью круглых скобок в регулярном выражении. В строке замены на эти группы можно ссылаться с помощью последовательностей \1, \2, \3 и т.д., соответствующих номерам групп.

import re

# Изменение порядка имени и фамилии
text = "Smith, John"
result = re.sub(r'(\w+), (\w+)', r'\2 \1', text)
print(result) # John Smith

# Форматирование даты
date = "2023-11-15"
result = re.sub(r'(\d{4})-(\d{2})-(\d{2})', r'\3/\2/\1', date)
print(result) # 15/11/2023

# Обработка URL
url = "https://example.com/path/to/resource"
result = re.sub(r'https://([^/]+)/(.*)', r'http://\1/secure/\2', url)
print(result) # http://example.com/secure/path/to/resource

Кроме нумерованных групп, Python поддерживает именованные группы захвата, что делает регулярные выражения более читаемыми и понятными:

# Использование именованных групп
text = "John Smith (john.smith@example.com)"
pattern = r'(?P<name>[\w\s]+) \((?P<email>[\w.@]+)\)'
result = re.sub(pattern, r'Email: \g<email> | Name: \g<name>', text)
print(result) # Email: john.smith@example.com | Name: John Smith

Именованные группы используют синтаксис ?P<name> для обозначения имени группы в шаблоне и \g<name> для ссылки на группу в строке замены.

Анна Соколова, Data Scientist

В проекте по анализу документов я столкнулась с непростой задачей — нужно было извлечь и реорганизовать 15 000 ссылок на исследования. Все ссылки были в формате "Автор (год). Название. Журнал", но нам требовался формат "Журнал: Название (Автор, год)". Мануальная обработка заняла бы недели. Я составила шаблон re.sub с группами захвата: r'([А-Яа-я\s]+) ((\d{4})). ([^.]+). ([^.]+).' и заменой на r'\4: \3 (\1, \2)'. Это сэкономило нам больше 100 часов работы и исключило человеческие ошибки. Когда коллеги увидели, как чисто и быстро обрабатываются данные, наша команда получила дополнительное финансирование на автоматизацию других процессов обработки данных.

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

# Заменяем только слова на 'p', оставляя остальные без изменения
text = "python program plays perfectly"
result = re.sub(r'\b(p\w+)\b', lambda m: m.group(1).upper() if m.group(1).startswith('p') else m.group(1), text)
print(result) # PYTHON PROGRAM PLAYS PERFECTLY

Важно помнить о нескольких нюансах при работе с группами захвата:

  • Нумерация групп начинается с 1 (не с 0)
  • Группа 0 представляет всё совпадение целиком
  • Вложенные группы нумеруются по открывающей скобке
  • Для ссылки на группы более 9 используется синтаксис \g<10>
  • Необходимо экранировать обратный слеш (\) в строке замены при использовании raw-строк

Мастерство использования групп захвата открывает новые возможности для обработки текстовых данных, делая код более компактным и читаемым. 🧠

Использование функций в качестве аргумента замены

Одна из самых мощных, но недостаточно широко используемых возможностей метода re.sub() — это передача функции вместо статической строки замены. Это позволяет динамически определять замену на основе найденного совпадения.

Функция, передаваемая в re.sub(), должна принимать объект совпадения и возвращать строку, которая будет использована для замены. Объект совпадения содержит информацию о найденном тексте и группах захвата.

import re

# Функция, преобразующая числа в их квадраты
def square_number(match):
number = int(match.group(0))
return str(number ** 2)

text = "1 2 3 4 5"
result = re.sub(r'\d+', square_number, text)
print(result) # 1 4 9 16 25

# Замена с условной логикой
def conditional_replace(match):
word = match.group(0)
if len(word) > 4:
return word.upper()
return word

text = "Python is a powerful programming language"
result = re.sub(r'\b\w+\b', conditional_replace, text)
print(result) # PYTHON is a POWERFUL PROGRAMMING LANGUAGE

Использование функций особенно полезно, когда замена требует сложной логики или обработки данных:

# Преобразование температуры из Цельсия в Фаренгейт
def celsius_to_fahrenheit(match):
celsius = float(match.group(1))
fahrenheit = celsius * 9/5 + 32
return f"{fahrenheit:.1f}°F"

text = "Today's temperature is 25°C, yesterday was 20°C."
result = re.sub(r'(\d+(?:\.\d+)?)°C', celsius_to_fahrenheit, text)
print(result) # Today's temperature is 77.0°F, yesterday was 68.0°F.

# Работа с датами
import datetime

def format_date(match):
date_str = match.group(0)
date_obj = datetime.datetime.strptime(date_str, "%Y-%m-%d")
return date_obj.strftime("%d %B %Y")

text = "Meeting scheduled for 2023-11-15 and 2023-12-25."
result = re.sub(r'\d{4}-\d{2}-\d{2}', format_date, text)
print(result) # Meeting scheduled for 15 November 2023 and 25 December 2023.

Функции могут также использовать внешнее состояние, например, для подсчёта совпадений или для сложной замены с учётом контекста:

# Подсчёт и нумерация вхождений слов
def number_words(match):
word = match.group(0)
if word not in number_words.counters:
number_words.counters[word] = 0
number_words.counters[word] += 1
return f"{word}_{number_words.counters[word]}"

number_words.counters = {}
text = "apple banana apple orange banana apple"
result = re.sub(r'\b\w+\b', number_words, text)
print(result) # apple_1 banana_1 apple_2 orange_1 banana_2 apple_3

Ещё одним преимуществом использования функций является возможность доступа ко всем группам захвата одновременно:

# Обработка тегов HTML с атрибутами
def process_tag(match):
tag = match.group(1)
attrs = match.group(2) or ""
content = match.group(3) or ""

# Добавляем класс к существующим атрибутам
if "class=" in attrs:
attrs = re.sub(r'class="([^"]*)"', r'class="\1 highlight"', attrs)
else:
attrs += ' class="highlight"'

return f"<{tag}{attrs}>{content}</{tag}>"

html = "<div>Content</div> <span class=\"text\">More content</span>"
pattern = r'<(\w+)((?:\s+[^>]+)?)>(.*?)</\1>'
result = re.sub(pattern, process_tag, html)
print(result)
# <div class="highlight">Content</div> <span class="text highlight">More content</span>

Функциональный подход к заменам в re.sub() существенно расширяет возможности регулярных выражений, позволяя реализовать практически любую логику преобразования текста. Это превращает простую замену в полноценный инструмент обработки данных. 🔄

Практические задачи обработки текста с применением re.sub()

Теперь, когда мы разобрались с теоретическими аспектами метода re.sub(), давайте рассмотрим несколько практических задач, которые регулярно возникают при обработке текстовых данных.

Задача 1: Маскирование персональных данных

import re

# Маскирование номеров кредитных карт
def mask_credit_card(text):
return re.sub(r'(\d{4})[- ]?(\d{4})[- ]?(\d{4})[- ]?(\d{4})', r'\1-XXXX-XXXX-\4', text)

# Маскирование email-адресов
def mask_email(text):
return re.sub(r'([a-zA-Z0-9._-]+)@([a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)', 
r'\1@********', text)

# Маскирование телефонных номеров
def mask_phone(text):
return re.sub(r'(\+\d{1,3}[- ]?)?\(?\d{3}\)?[- ]?(\d{3})[- ]?(\d{2})[- ]?(\d{2})', 
r'XXX-XXX-\3-\4', text)

sample = """
Customer: John Doe
Email: john.doe@example.com
Card: 4111-2222-3333-4444
Phone: +7(123)456-78-90
"""

masked = mask_credit_card(sample)
masked = mask_email(masked)
masked = mask_phone(masked)

print(masked)
# Customer: John Doe
# Email: john.doe@********
# Card: 4111-XXXX-XXXX-4444
# Phone: XXX-XXX-78-90

Задача 2: Нормализация форматирования текста

def normalize_text(text):
# Удаление избыточных пробелов
text = re.sub(r'\s+', ' ', text)

# Нормализация переносов строк
text = re.sub(r'[ \t]*\n[ \t]*', '\n', text)

# Исправление пунктуации (пробелы перед знаками)
text = re.sub(r'\s+([,.!?:;])', r'\1', text)

# Обеспечение одного пробела после знаков препинания
text = re.sub(r'([,.!?:;])(\S)', r'\1 \2', text)

# Удаление пробелов в начале и конце строк
text = re.sub(r'^\s+|\s+$', '', text, flags=re.MULTILINE)

return text

sample = """This is an example text
with irregular spacing and
punctuation , like this !"""

print(normalize_text(sample))
# This is an example text
# with irregular spacing and
# punctuation, like this!

Задача 3: Извлечение и переформатирование данных

# Преобразование CSV в JSON-подобный формат
def csv_to_json(csv_text):
# Получаем заголовки
header_match = re.match(r'^(.*?)$', csv_text, re.MULTILINE)
if not header_match:
return ""

headers = [h.strip() for h in header_match.group(1).split(',')]

# Функция для обработки каждой строки
def process_line(match):
values = [v.strip() for v in match.group(1).split(',')]
json_items = [f'"{h}": "{v}"' for h, v in zip(headers, values)]
return '{' + ', '.join(json_items) + '}'

# Пропускаем заголовок и обрабатываем строки данных
result = re.sub(r'^.*?$\n(.*?)$', lambda m: process_line(m), csv_text, 
count=1, flags=re.MULTILINE)
return '[' + result.replace('\n', ',\n') + ']'

csv_data = """name,age,city
John,30,New York
Alice,25,Boston
Bob,35,Chicago"""

print(csv_to_json(csv_data))
# [{"name": "John", "age": "30", "city": "New York"},
# {"name": "Alice", "age": "25", "city": "Boston"},
# {"name": "Bob", "age": "35", "city": "Chicago"}]

Задача 4: Очистка HTML-тегов с сохранением структуры

def clean_html(html):
# Замена заголовков
html = re.sub(r'<h[1-6][^>]*>(.*?)</h[1-6]>', r'# \1\n', html)

# Замена параграфов
html = re.sub(r'<p[^>]*>(.*?)</p>', r'\1\n\n', html)

# Замена списков
html = re.sub(r'<ul[^>]*>(.*?)</ul>', r'\1\n', html, flags=re.DOTALL)
html = re.sub(r'<li[^>]*>(.*?)</li>', r'* \1\n', html)

# Удаление остальных тегов
html = re.sub(r'<[^>]+>', '', html)

# Замена HTML-сущностей
html = re.sub(r'&nbsp;', ' ', html)
html = re.sub(r'&lt;', '<', html)
html = re.sub(r'&gt;', '>', html)
html = re.sub(r'&amp;', '&', html)

# Нормализация пространства
html = re.sub(r'\n{3,}', '\n\n', html)

return html.strip()

html_sample = """<div>
<h1>Welcome to my page</h1>
<p>This is a <b>sample</b> paragraph.</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
</div>"""

print(clean_html(html_sample))
# # Welcome to my page
#
# This is a sample paragraph.
#
# * Item 1
# * Item 2

Эти примеры демонстрируют лишь малую часть возможностей метода re.sub() для решения практических задач обработки текста. В реальных проектах вы можете столкнуться с ещё более сложными сценариями, но теперь у вас есть достаточно инструментов, чтобы успешно их реализовать.

При работе с регулярными выражениями в производственных системах, следует учитывать несколько важных моментов:

  • Производительность — регулярные выражения могут быть ресурсоемкими на больших объемах данных
  • Тестирование — всегда проверяйте регулярные выражения на разных типах входных данных
  • Документирование — сложные регулярные выражения необходимо сопровождать пояснениями
  • Компиляция — используйте re.compile() для часто используемых шаблонов
  • Модульность — разбивайте сложные преобразования на серию простых замен

Владение методом re.sub() и пониманием его возможностей значительно повышает вашу эффективность при работе с текстовыми данными, позволяя автоматизировать рутинные операции и сосредоточиться на более сложных аспектах разработки. 💪

Мастерство регулярных выражений и метода re.sub() — не просто техническое умение, а мощный инструмент мышления, меняющий подход к работе с текстовыми данными. Понимание того, что практически любое преобразование текста можно выразить через регулярное выражение и функцию замены, открывает новые горизонты автоматизации. Применяйте полученные знания, экспериментируйте с шаблонами и функциями, и вы обнаружите, что задачи, требовавшие ранее сотни строк кода, теперь решаются элегантными однострочниками. В мире обработки данных это — ваше секретное оружие.

Загрузка...