16 — Замены с помощью регулярных выражений | Питон и КИЛИ

Замены с помощью регулярных выражений

Содержание

  • Замены - функция re.sub(pattern, repl, string)
  • Именованные группы

Замены в регурярных выражениях

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

>>> string = 'Алёна мыла ёлку!'
>>> print(string.replace('ё', 'е'))
Алена мыла елку!

Но, что, делать в более сложных случаях? Ну не писать же несколько раз вызов функции .replace() с разными аргументами

>>> string = 'Ёлку мыла Алёна...'
>>> print(string.replace('ё', 'е').replace('Ё', 'Е'))
Елку мыла Алена...

На помощь приходят регулярные выражения и модуль re со своей функцией re.sub().

Сигнатура методы такая: re.sub(pattern, repl, string), где

  • pattern - это регулярное выражение - шаблон для поиска строки, которую нужно заменить
  • repl - строка, на которую нужно произвести замену
  • string - сама строка, над которой нужно произвести манипуляции

Метод re.sub() ищет шаблон pattern в строке string и заменяет его на repl. Метод возвращает новую строку. Если шаблон не найден в строке, то текст возвращается без изменений.

>>> # Задача: заменить все числа на слово NUM
>>> #
>>> import re
>>> string = 'Мой дядя родился в 48 году и в 2000 ему было 52'
>>> pattern = '[0-9]+'
>>> print(re.sub(pattern, 'NUM', string))
Мой дядя родился в NUM году и в NUM ему было NUM

Пример с Алёной и заглавной и строчной буквой ё нельзя запрограммировать одной регуляркой. Подумайте, как можно сделать такую функцию, используя регулярки.

Использование групп при заменах

Представьте, что вам нужно написать функцию, которая будет менять американский формат записи даты MM/DD/YYYY на русский DD.MM.YYYY. Сейчас не будем говорить, про то, что дни могут быть только в диапазоне от 1 до 31, а месяцы от 1 до 12.

Функция может иметь слудующий вид:

def convert_dates(text):

    pattern = '([0-9]{2})/([0-9]{2})/([0-9]{4})'
    result = re.search(pattern, text)

    if result:
        mm = result.group(1)
        dd = result.group(2)
        yyyy = result.group(3)
        new_date = dd + '/' + mm + '/' + yyyy
        start, stop = result.span()
        text = text[:start] + new_date + text[stop:]

    return text

И работать так:

>>> convert_dates('Я влюбился в тебя 03/21/2017.')
'Я влюбился в тебя 21/03/2017.'

Но, что если, дат в тексте будет больше, чем одна. Да и вообще, неужели нужно писать столь сложный код для такой логики?

На помощь приходят группы. Функцию выше можно переписать так:

def convert_dates(text):

    pattern = '([0-9]{2})/([0-9]{2})/([0-9]{4})'
    repl = r'\2/\1/\3'

    return re.sub(pattern, repl, text)

А использовать так же:

>>> convert_dates('Я влюбился в тебя 03/21/2017. Мои родители познакомились 03/21/1999')
'Я влюбился в тебя 21/03/2017. Мои родители познакомились 21/03/1999'

Здесь repl - это еще один шаблон, который говорит функции re.sub() куда вставить ту или иную группы из шаблона pattern. Выглядит конечно страшно, но куда деваться.

Как, наверное, можно догадаться, \N - это указание на конкретную группу из шаблона pattern, которую нужно подставить. N - это номер группы.

Именованные группы

Когда количество групп в шаблонах увеличивается, то становится трудно с ними работать. Можно легко запутаться в индексах и допустить ошибку. Здесь на помощь приходят именованные группы, с помощью которых можно дать каждой группе своё имя. Осталось только привыкнуть к синтаксису.

Что, если, в наш пример с датой добавится еще и время…

def convert_dates(text):

    pattern = '(?P<m>[0-9]{2})/(?P<d>[0-9]{2})/(?P<y>[0-9]{4}) (?P<hm>[0-9]{2}:[0-9]{2})'
    repl = r'\g<d>/\g<m>/\g<y> в \g<hm>'

    return re.sub(pattern, repl, text)
>>> convert_dates('Я влюбился в тебя 03/21/2017 23:45 по московскому времени')
'Я влюбился в тебя 21/03/2017 в 23:45 по московскому времени'

(?P<m>[0-9]{2}) - ?P<m> - специальный синтаксис задания имени группы. Имя здесь только то, что заключено в скобки.

Обращение к группе происходит тоже с помощью спецального синтаксиса: \g<d>

Имена групп можно использовать в методе .group()

def get_mail_provider(email):

    pattern = '(?P<login>[a-zA-Z0-9_]+)@(?P<provider>(?P<name>[a-zA-Z0-9_]+)\.(?P<domain>[a-zA-Z]+))'
    result = re.search(pattern, email)

    if result:
        return result.group('provider')

    return None
>>> get_mail_provider('ivan@yandex.ru')
'yandex.ru'

Домашнее задание

TBD