Регулярные выражения в Python

Перевод статьи «Python Regular Expression».

Сегодня мы хотим поговорить о регулярных выражениях в Python. Пожалуй, стоит начать с определения. Регулярные выражения, иногда называемые re, regex или regexp, представляют собой последовательности символов, составляющие шаблоны, соответствия которым ищутся в строке или тексте. Для работы с regexp в Python есть встроенный модуль re.

Обычное использование регулярного выражения:

  • Поиск подстроки в строке (search and find)
  • Поиск всех подходящих строк (findall)
  • Разделение строки на подстроки (split)
  • Замена части строки (sub)

Основы

Регулярное выражение – это комбинация символов и метасимволов. Из метасимволов доступны следующие:

  • \ используется для игнорирования специального значения символа
  • [] указывает на класс символов. Например: [a-z] — все буквы латинского алфавита в нижнем регистре, [a-zA-Z0-9] — все буквы в обоих регистрах плюс цифры
  • ^ соответствует началу текста
  • $ обозначает конец текста
  • . соответствует любому символу, кроме символа новой строки
  • ? обозначает одно или ноль вхождений
  • | означает ИЛИ (совпадение с любым из символов, разделенных им)
  • * любое количество вхождений (включая 0 вхождений)
  • + одно и более вхождений
  • {} указывает на несколько совпадений предыдущего RE.
  • () отделяет группу в регулярном выражении

Обратная косая черта (backslash) \ используется в сочетании с другими символами и тогда приобретает особые значения. Если же необходимо использовать backslash просто как символ, без учета специального значения, его нужно «экранировать» еще одной обратной косой чертой – \\. Что касается специальных значений:

  • \d соответствует любой десятичной цифре, это то же самое, что и [0-9]
  • \D соответствует любому нечисловому символу
  • \s соответствует любому пробельному символу
  • \S соответствует любому не пробельному символу
  • \w соответствует любому буквенно-числовому символу; это то же самое, что и [a-zA-Z0-9_].
  • \W соответствует любому не буквенно-числовому символу.

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

re.search()

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

Синтаксис: re.search(шаблон, строка)

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

Давайте разберем пример: поищем в строке месяц и число.

import re  

regexp = r"([a-zA-Z]+) (\d+)"
match = re.search(regexp, "My son birthday is on July 20")
if match != None:
    print("Match at index %s, %s" % (match.start(), match.end()))   #This provides index of matched string
    print("Full match: %s" % (match.group(0)))
    print("Month: %s" % (match.group(1)))
    print("Day: %s" % (match.group(2)))
else:
    print("The given regex pattern does not match")
2 22

re.match()

Этот метод ищет и возвращает первое совпадение. Но надо учесть, что он проверяет соответствие только в начале строки.

Синтаксис: re.match(шаблон, строка)

Возвращаемое значение, как и в search(), может быть либо подстрокой, соответствующей шаблону, либо None, если желаемый результат не найден.

Теперь давайте посмотрим на пример. Проверим, совпадает ли строка с шаблоном.

import re

regexp = r"([a-zA-Z]+) (\d+)"
match = re.match(regexp, "July 20")
if match == None:
    print("Not a valid date")
else:
    print("Given string: %s" % (match.group()))
    print("Month: %s" % (match.group(1)))
    print("Day: %s" % (match.group(2)))
4 20

Рассмотрим другой пример. Здесь «July 20» находится не в начале строки, поэтому результатом кода будет «Not a valid date»

import re

regexp = r"([a-zA-Z]+) (\d+)"
match = re.match(regexp, "My son birthday is on July 20")
if match == None:
    print("Not a valid date")
else:
    print("Given string: %s" % (match.group()))
    print("Month: %s" % (match.group(1)))
    print("Day: %s" % (match.group(2)))
6 15

re.findall()

Этот метод возвращает все совпадения с шаблоном, которые встречаются в строке. При этом строка проверяется от начала до конца. Совпадения возвращаются в том порядке, в котором они идут в исходной строке.

Синтаксис: re.findall(шаблон, строка)

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

Рассмотрим пример. Используем регулярное выражение для поиска чисел в исходной строке.

import re

string  = "Bangalore pincode is 560066 and gulbarga pincode is 585101"
regexp = '\d+'            
match = re.findall(regexp, string)
print(match)
8 10

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

import re

string = "Bangalore office number 1234567891, My number is 8884278690, emergency contact 3456789123 invalid number 898883456"
regexp = '\d{10}'  # Регулярное выражение, соответствующее числу из ровно 10 цифр            
match = re.findall(regexp, string)
print(match)
10 7

re.compile()

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

import re

e = re.compile('[a-e]')
print(e.findall("I born at 11 A.M. on 20th July 1989"))
e = re.compile('\d')  # \d - эквивалент [0-9].
print(e.findall("I born at 11 A.M. on 20th July 1989"))
p = re.compile('\d+')  # группа из одной или более цифр
print(p.findall("I born at 11 A.M. on 20th July 1989"))

# Результат:
# ['b', 'a']
# ['1', '1', '2', '0', '1', '9', '8', '9']
# ['11', '20', '1989']

re.split()

Данный метод разделяет строку по заданному шаблону. Если шаблон найден, оставшиеся символы из строки возвращаются в виде результирующего списка. Более того, мы можем указать максимальное количество разделений для нашей строки.

Синтаксис: re.split(шаблон, строка, maxsplit = 0)

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

Рассмотрим, как работает данный метод, на примере.

import re

# '\W+' совпадает с символами или группой символов, не являющихся буквами или цифрами
# разделение по запятой ',' или пробелу ' '
print(re.split('\W+', 'Good, better , Best'))
print(re.split('\W+', "Book's books Books"))
# Здесь ':', ' ' ,',' - не буквенно-цифровые символы, по которым происходит разделение
print(re.split('\W+', 'Born On 20th July 1989, at 11:00 AM'))

# '\d+' означает цифры или группы цифр
# Разделение происходит по '20', '1989', '11', '00'
print(re.split('\d+', 'Born On 20th July 1989, at 11:00 AM'))

# Указано максимальное количество разделений - 1
print(re.split('\d+', 'Born On 20th July 1989, at 11:00 AM', maxsplit=1))

# Результат:
# ['Good', 'better', 'Best']
# ['Book', 's', 'books', 'Books']
# ['Born', 'On', '20th', 'July', '1989', 'at', '11', '00', 'AM']
# ['Born On ', 'th July ', ', at ', ':', ' AM']
# ['Born On ', 'th July 1989, at 11:00 AM']

re.sub()

Здесь значение «sub» — это сокращение от substring, т.е. подстрока. В данном методе исходный шаблон сопоставляется с заданной строкой и, если подстрока найдена, она заменяется параметром repl.

Кроме того, у метода есть дополнительные аргументы. Это count, счетчик, в нем указывается, сколько раз заменяется регулярное выражение. А также flag, в котором мы можем указать флаг регулярного выражения (например, re.IGNORECASE)

Синтаксис: re.sub(шаблон, repl, строка, count = 0, flags = 0)

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

Посмотрим на работу метода на следующем примере.

import re

# Шаблон 'lly' встречается в строке в "successfully" и "DELLY"
print(re.sub('lly', '#$', 'doctor appointment booked successfully in DELLY'))

# Благодаря использованию флага регистр игнорируется, и 'lly' находит два совпадения
# Когда совпадения найдены, 'lly' заменяется на '~*' в "successfully" и "DELLY".
print(re.sub('lly', '#$', 'doctor appointment booked successfully in DELLY', flags=re.IGNORECASE))

# Чувствительность к регистру: 'lLY' не находит совпадений, и ничего в строке не будет заменено
print(re.sub('lLY', '#$', 'doctor appointment booked successfully in DELLY'))

# С count = 1 заменяется только одно совпадение с шаблоном
print(re.sub('lly', '#$', 'doctor appointment booked successfully in DELLY', count=1, flags=re.IGNORECASE))
16 2

re.subn()

Функциональность subn() во всех отношениях такая же, как и sub(). Единственная разница – это формат вывода. subn() возвращает кортеж, содержащий общее количество замен и новую строку.

Синтаксис: re.subn(шаблон, repl, строка, count = 0, flags = 0)

Рассмотрим такой пример.

import re

print(re.subn('lly', '#$', 'doctor appointment booked successfully in DELLY'))
t = re.subn('lly', '#$', 'doctor appointment booked successfully in DELLY', flags=re.IGNORECASE)
print(t)
print(len(t))
# Это даст такой же вывод, как и sub()
print(t[0])
18

re.escape()

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

Синтаксис: re.escape(строка)

Чтобы лучше понять принцип работы метода, рассмотрим следующий пример.

import re

# Здесь из не буквенно-цифровых символов есть только пробелы.
print(re.escape("doctor appointment booked successfully at 1PM"))

# Здесь есть , ' ', '^', '-', '[]', '\' - все эти символы не относятся к буквенно-цифровым
print(re.escape("He asked what is this [0-9], I said \t ^Numberic class"))
20

Заключение

Сегодня мы поговорили о регулярных выражениях в Python и о том, что необходимо для их понимания в любом приложении. Мы изучили различные методы и метасимволы, присутствующие в регулярных выражениях Python, на примерах.