Время прочтения: 6 мин.

Я искал информацию в интернете и часто встречал статьи, содержащие описание документации, либо статьи с отсутствием примеров, что ухудшало понимание, либо с примерами шаблонов, но из той же самой документации. Здесь же я собрал несколько примеров, встречавшихся в моей работе, и способы их решения. Надеюсь, вы узнаете что-то новое и полезное.

Улучшаю читаемость

Рано или поздно большинству разработчиков приходится читать свой старый код и пытаться вспомнить за что отвечал тот или иной участок кода. Все это происходит из-за отсутствия документации в коде.

Например, такой паттерн для поиска номеров сотовых операторов РФ воспринимается трудно:

pattern =  r'((8|\+7)[\- ]?)?(\(?\d{3}\)?[- ]?)?[\d\- ]{7,10}'  

Для решения этой проблемы следует использовать комментарии внутри паттерна регулярного выражения (?#…).

К примеру, возьму простой паттерн поиска номера телефона с жесткой структурой:

 pattern = r'\+7\(\d{3}\)(?# код оператора)\d{3}(?# первые 3 цифры номера)-\d{2}-\d{2}(?# последние 4 разделенные по 2)'

Комментарии есть, но они записаны в 1 строку и усложняют восприятие паттерна. В своей практике я использую такой вид:

pattern = '''+7\(\d{3}\)   # код оператора
            \d{3}-         # первые 3 цифры номера
            \d{2}-\d{2}    # последние 4 разделенные по 2
            '''

Читаемость кода намного улучшается. Но если использовать такой паттерн, то результатом работы кода будет пустое значение. Для того, чтобы код отработал так, как мы хотим, нужно добавить модификатор или, по-другому, флаг re.X или re.VERBOSE, который  позволяет использовать многострочный синтаксис.

text = 'Мои номера телефона +7(923)111-22-33 и +7(923)444-55-66 звонить с 9:00 до 18:00'
re.findall(pattern, text, re.X)

Вывод:

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

Ссылки на группы

Иногда требуется найти повторяющиеся слова, символы или числа в предложении. С помощью методов преобразования строк это делать очень долго и приходится учитывать множество условий. Чем больше в коде переменных и условий, тем больше вероятность сделать ошибку. Регулярными выражениями это сделать намного проще и быстрее, не обращая внимания на знаки препинания.

Возьму датафрейм с дублями чисел, слов:

В поиске дублей поможет группировка:

pattern = r'\b([a-zA-Zа-яА-Я0-9]+)\b.+\1'

Конструкция \1 используется как ссылка, которая указывает на то, что после слова или числа, которое подошло под условия, прописанные в скобках, через произвольное количество символов должно идти тоже самое слово или число. Тогда при использовании этого паттерна:

df['Организация'].apply(lambda x:  '' if re.search(pattern, x) == None else re.search(pattern, x).group(0))

Вижу дубликаты:

Удалю их.

def clean_text(x):
    wh = re.search(pattern, x).group(0)
    fwh = re.search(pattern, x).group(1)
    return re.sub(wh, fwh, x)

df['Организация'].apply(lambda x: clean_text(x))

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

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

text = r'''PYTHON (питОн или пАйтон) - высокоуровневый язык программирования общего назначения, python универсален, поэтому подходит для решения разнообразных задач.'''

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

pattern = r'(\b[А-ЯA-Z]+\b).*\s*(?=\b[а-яa-z]+\b)(?i)\1'
re.findall(pattern, text)

В результате работы паттерна в тексте нашлось слово «PYTHON»:

Условные операторы в регулярных выражениях

Изучая информацию по использованию условного оператора, я провел много времени не только в поиске самой структуры, но и в поиске правильного его использования. Это все исходит от того, что разные языки программирования поддерживают разные методы, и использование условного оператора с несколькими условиями являлось для меня сложной задачей. Обходным решением было использование стандартной конструкции if-then-else.

Для демонстрации использования условного оператора создадим паттерн:

pattern = r'\b(P)?(?(1)YTHON|Python)'

Следует немного пояснить работу паттерна. Создается группа №1, если слово начинается с заглавной буквы «P». Конструкция  ?(1) проверяет существует ли группа №1. Если группа №1 существует, запускаются 2 сценария. Первый проверяет остальную часть слова на соответствие «YTHON», если не соответствует, проверяется на слово «Python». Если какое-либо условие отрабатывается, возвращается совпадение с помощью метода match.

Если группы №1 не существует, слово пропускается.

Проверю работу паттерна на примере:

text = r'''PYTHON (питОн или пАйтон) - высокоуровневый язык программирования общего назначения. Python универсален, поэтому подходит для решения разнообразных задач.'''
pattern = r'\b(P)?(?(1)YTHON|Python)'
for match in re.finditer(pattern, text):
    print(match.group(), end=' ')

На вывод получил ожидаемые слова:

Черный список слов

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

Для этого возьму текст, который использовал в предыдущем примере:

text = r'''PYTHON (питОн или пАйтон) - высокоуровневый язык программирования общего назначения. Python универсален, поэтому подходит для решения разнообразных задач.'''

И составлю паттерн:

pattern = r'\b(?:язык|программирования|решения)\b|([\w\. \(\),-]+)'

В паттерне две группы и условие ИЛИ между ними, в результате которого возвращается либо None, либо ответ, подходящий под одно из условий. Первое условие со скобочной группой (?:…), которая создает группу №1, если слово из текста совпадет с одним из слов списка, но не сохранит результат т.к. шаблон (?:…) группирует, но не сохраняет результат. В случае успешного выполнения ответом будет пустое значение. Если слово не подходит под первое условие, то выполнится второе условие, под которое подходит слово, состоящее из любых символов, цифр и знаков точки, запятой, скобок и тире. В нашем тексте первым словом будет «PYTHON».

В результате метод findall с условием ИЛИ создаст группы из слов текста и выдаст результат, в котором не будет ненужных нам слов.

print(*re.findall(pattern, text))

Результат работы регулярного выражения:

При использовании регулярных выражений погружаешься в них все глубже, и, приобретая опыт, паттерны становятся эффективнее, большие алгоритмы обработки текста становятся проще и изящнее. Никогда не прекращайте развиваться, и я желаю вам удачи в получении новых знаний.

Для тех, кто хочет немного потренироваться в работе с регулярными выражениями в игровой форме рекомендую сайт regexcrossword.com