W3docs

Python RegEx

Регулярные выражения Python: синтаксис, специальные последовательности, группы, просмотр вперёд, флаги и функции модуля re с понятными примерами.

Регулярные выражения (regex) позволяют искать, извлекать и заменять текст по гибким шаблонам, а не по точным строкам. Встроенный модуль re в Python предоставляет всё необходимое. В этой главе рассматривается полный набор инструментов для работы с regex: синтаксис, специальные последовательности, квантификаторы, группы, просмотр вперёд/назад, флаги и все ключевые функции re — с корректными, рабочими примерами.

Зачем использовать регулярные выражения?

Обычные строковые методы (str.find(), str.replace(), str.split()) хорошо работают с фиксированным текстом. Регулярные выражения незаменимы, когда шаблон может меняться:

  • Проверить, выглядит ли строка как адрес электронной почты или номер телефона.
  • Извлечь все даты из документа, независимо от точного формата.
  • Удалить HTML-теги из строки.
  • Заменить несколько различных последовательностей пробелов одним пробелом.

Когда задача связана с описанием формы, а не фиксированного значения, используйте re.

Необработанные строки

Шаблоны regex почти всегда записываются как необработанные строки (r'...'). Необработанные строки обрабатывают обратные слэши как обычные символы, что важно, поскольку regex использует множество последовательностей с обратным слэшем (\d, \w, \s, \b). Без префикса r везде потребовались бы двойные обратные слэши:

import re

# Both patterns are identical — raw string is easier to read
re.findall(r'\d+', 'abc 123')   # raw:    r'\d+'
re.findall('\\d+', 'abc 123')   # normal: '\\d+'

Используйте необработанные строки для каждого шаблона regex — это общепринятое соглашение.

Базовый синтаксис: метасимволы

Метасимволы — это символы со специальным значением внутри шаблона. Литеральные символы соответствуют себе в точности.

МетасимволЗначение
.Любой символ кроме символа новой строки
^Начало строки (или строки текста в режиме MULTILINE)
$Конец строки (или строки текста в режиме MULTILINE)
*Ноль или более предшествующих элементов
+Один или более предшествующих элементов
?Ноль или один предшествующий элемент
{n}Ровно n повторений
{n,m}От n до m повторений
[...]Класс символов — любой один символ из перечисленных
[^...]Отрицательный класс — любой символ, не перечисленный
|Чередование — левое или правое выражение
()Захватывающая группа
\Экранирование метасимвола или начало специальной последовательности

Чтобы найти буквальный метасимвол, например . или *, экранируйте его обратным слэшем: \. соответствует настоящей точке.

Специальные последовательности

Специальные последовательности — это сокращённые классы символов, которые часто встречаются в реальных шаблонах.

ПоследовательностьСоответствует
\dЛюбая цифра — то же самое, что [0-9]
\DЛюбой нецифровой символ
\wСимвол слова: буквы, цифры, подчёркивание
\WСимвол, не являющийся символом слова
\sПробельный символ: пробел, табуляция, новая строка
\SНепробельный символ
\bГраница слова (нулевой ширины)
\BНе-граница слова
import re

print(re.findall(r'\d+', 'I have 3 cats and 12 dogs'))
# ['3', '12']

print(re.findall(r'\w+', 'hello_world 123'))
# ['hello_world', '123']

print(re.findall(r'\bPython\b', 'Python Pythonista Python3'))
# ['Python']  — word boundary prevents partial matches

\b особенно полезна: она соответствует позиции между символом слова и символом-не-словом, поэтому \bPython\b совпадает с отдельным словом «Python», но не с «Pythonista» или «Python3».

Квантификаторы

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

import re

print(re.findall(r'a*', 'baaa'))   # ['', 'aaa', '']
print(re.findall(r'a+', 'baaa'))   # ['aaa']
print(re.findall(r'a?', 'baaa'))   # ['', 'a', 'a', 'a', '']
print(re.findall(r'a{3}', 'baaa')) # ['aaa']

Жадное и ленивое совпадение

По умолчанию квантификаторы жадные — они совпадают с как можно большим количеством текста. Добавьте ? после квантификатора, чтобы сделать его ленивым (совпадать с как можно меньшим количеством).

import re

html = '<b>bold</b> and <i>italic</i>'

print(re.findall(r'<.*>', html))
# ['<b>bold</b> and <i>italic</i>']  — greedy: matches from first < to last >

print(re.findall(r'<.*?>', html))
# ['<b>', '</b>', '<i>', '</i>']  — lazy: matches each individual tag

Ленивые квантификаторы незаменимы при разборе структурированного текста, например HTML или фрагментов JSON.

Классы символов

Класс символов [...] совпадает с любым одним символом из указанного набора. Используйте - для диапазонов и ^ в начале для отрицания класса.

import re

print(re.findall(r'[aeiou]', 'hello world'))
# ['e', 'o', 'o']

print(re.findall(r'[0-9]', 'a1b2c3'))
# ['1', '2', '3']

print(re.findall(r'[^aeiou\s]+', 'hello world'))
# ['h', 'll', 'w', 'rld']  — consonants only (not vowels, not spaces)

Распространённые готовые диапазоны: [a-z] строчные буквы, [A-Z] прописные, [0-9] цифры, [a-zA-Z0-9] буквенно-цифровые символы.

Якоря

Якоря не поглощают символы — они указывают на позицию в строке.

import re

print(re.findall(r'^Python', 'Python is great'))
# ['Python']  — matches only if 'Python' is at the start

print(re.findall(r'great$', 'Python is great'))
# ['great']  — matches only if 'great' is at the end

print(re.findall(r'^Python', 'Learn Python'))
# []  — 'Python' is not at the start of this string

Смотрите флаг re.MULTILINE далее в этой главе, чтобы применять ^ и $ к каждой строке текста, а не ко всей строке целиком.

Чередование

Символ | работает как логическое ИЛИ между двумя выражениями.

import re

print(re.findall(r'cat|dog', 'I have a cat and a dog'))
# ['cat', 'dog']

print(re.findall(r'colou?r|colour', 'color and colour'))
# ['color', 'colour']

Захватывающие группы

Скобки () создают захватывающую группу. Группы позволяют извлекать подчасти совпадения. re.search() возвращает объект совпадения; вызовите .group(n) или .groups() на нём.

import re

match = re.search(r'(\d{4})-(\d{2})-(\d{2})', '2023-10-05')
if match:
    print(match.group(0))   # '2023-10-05'  — entire match
    print(match.group(1))   # '2023'
    print(match.groups())   # ('2023', '10', '05')

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

Дайте группе имя с помощью (?P<name>...), чтобы обращаться к ней по имени, а не по позиции. Это делает шаблоны значительно проще в поддержке.

import re

match = re.search(
    r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})',
    '2023-10-05'
)
if match:
    print(match.group('year'))   # '2023'
    print(match.groupdict())     # {'year': '2023', 'month': '10', 'day': '05'}

Незахватывающие группы

Используйте (?:...), когда нужно сгруппировать, но не нужно захватывать значение.

import re

print(re.findall(r'(?:Mr|Mrs|Ms)\.? \w+', 'Mr. Smith and Mrs. Jones'))
# ['Mr. Smith', 'Mrs. Jones']

Просмотр вперёд и просмотр назад

Просмотр вперёд ((?=...)) и просмотр назад ((?<=...)) проверяют, что за совпадением следует или предшествует что-то, не включая это в результат. Они имеют нулевую ширину: не поглощают символы.

import re

# Positive lookahead — find numbers followed by 'dollars'
print(re.findall(r'\d+(?= dollars)', '100 dollars and 200 euros'))
# ['100']

# Negative lookahead — find numbers NOT followed by 'dollars'
print(re.findall(r'\b\d+\b(?! dollars)', '100 dollars and 200 euros'))
# ['200']

# Positive lookbehind — extract domain from email addresses
emails = 'Contact [email protected] or [email protected]'
print(re.findall(r'(?<=@)\w+\.\w+', emails))
# ['example.com', 'test.org']

Шаблоны просмотра назад в Python должны иметь фиксированную ширину — внутри них нельзя использовать * или +.

Модуль re: основные функции

re.search() — найти первое совпадение

Возвращает объект совпадения для первой позиции, где шаблон совпадает, или None, если совпадений нет.

import re

text = 'apple banana apple'
match = re.search(r'banana', text)
if match:
    print(match.group())   # 'banana'
    print(match.span())    # (6, 12)

re.match() — совпадение только в начале

Как re.search(), но шаблон должен совпасть в начале строки.

import re

print(bool(re.match(r'\d+', 'abc123')))   # False — no digit at start
print(bool(re.search(r'\d+', 'abc123')))  # True  — digit found anywhere

Используйте re.search(), когда нужно найти шаблон в любом месте; используйте re.match(), когда шаблон должен находиться в начале.

re.fullmatch() — совпадение со всей строкой

Шаблон должен совпасть со всей строкой от начала до конца.

import re

print(bool(re.fullmatch(r'\d{5}', '12345')))   # True  — exactly 5 digits
print(bool(re.fullmatch(r'\d{5}', '123456')))  # False — too long
print(bool(re.fullmatch(r'\d{5}', '1234X')))   # False — non-digit present

re.fullmatch() идеально подходит для проверки входных данных (почтовые индексы, номера телефонов и т. д.).

re.findall() — все непересекающиеся совпадения

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

import re

print(re.findall(r'\d+', 'abc 123 def 456'))
# ['123', '456']

# With a group — returns list of group values
print(re.findall(r'(\w+)@(\w+\.\w+)', '[email protected] [email protected]'))
# [('alice', 'example.com'), ('bob', 'test.org')]

re.finditer() — итератор объектов совпадений

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

import re

for m in re.finditer(r'\d+', 'abc 123 def 456'):
    print(m.group(), m.start(), m.end())
# 123 4 7
# 456 12 15

re.sub() — замена совпадений

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

import re

text = 'apple banana apple'
print(re.sub(r'apple', 'orange', text))
# 'orange banana orange'

# Limit replacements
print(re.sub(r'apple', 'orange', text, count=1))
# 'orange banana apple'

# Backreferences in replacement — reformat a date
print(re.sub(r'(\d{4})-(\d{2})-(\d{2})', r'\3/\2/\1', '2023-10-05'))
# '05/10/2023'

re.split() — разделение по шаблону

Разделяет строку при каждом вхождении шаблона.

import re

print(re.split(r',\s*', 'apple, banana, cherry, date'))
# ['apple', 'banana', 'cherry', 'date']

# Limit the number of splits
print(re.split(r',\s*', 'apple, banana, cherry, date', maxsplit=2))
# ['apple', 'banana', 'cherry, date']

Компиляция шаблонов с re.compile()

Когда один и тот же шаблон используется многократно, скомпилируйте его один раз с помощью re.compile() для повышения производительности. Полученный объект шаблона имеет все те же методы (findall, search, sub и т. д.).

import re

# Compile once
digit_pattern = re.compile(r'\d+')

# Reuse many times
print(digit_pattern.findall('abc 123 def 456'))   # ['123', '456']
print(digit_pattern.sub('NUM', 'abc 123 def 456')) # 'abc NUM def NUM'
print(digit_pattern.search('xyz 99'))              # <re.Match object ...>

Компиляция особенно ценна внутри циклов или часто вызываемых функций.

Флаги

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

ФлагКраткая формаЭффект
re.IGNORECASEre.IСопоставление без учёта регистра
re.MULTILINEre.M^ и $ совпадают с началом/концом каждой строки текста
re.DOTALLre.S. совпадает также с символами новой строки
re.VERBOSEre.XРазрешает пробелы и комментарии внутри шаблона
import re

# IGNORECASE
print(re.findall(r'hello', 'Hello HELLO hello', re.IGNORECASE))
# ['Hello', 'HELLO', 'hello']

# MULTILINE — ^ matches the start of each line
text = 'first line\nsecond line\nthird line'
print(re.findall(r'^\w+', text, re.MULTILINE))
# ['first', 'second', 'third']

# DOTALL — . matches newline characters
print(re.findall(r'<.*?>', '<div>\n<p>text</p>\n</div>', re.DOTALL))
# ['<div>', '<p>', '</p>', '</div>']

# VERBOSE — write readable patterns with comments
date_pattern = re.compile(r'''
    (?P<year>\d{4})   # four-digit year
    -
    (?P<month>\d{2})  # two-digit month
    -
    (?P<day>\d{2})    # two-digit day
''', re.VERBOSE)
print(date_pattern.search('2023-10-05').groupdict())
# {'year': '2023', 'month': '10', 'day': '05'}

Экранирование пользовательского ввода с re.escape()

Если вы строите шаблон из текста, введённого пользователем, всегда сначала экранируйте его, чтобы предотвратить непреднамеренную интерпретацию метасимволов.

import re

user_input = 'hello.world'
# Without escaping, '.' matches any character
# With escaping, '\.' matches a literal dot
safe_pattern = re.escape(user_input)
print(safe_pattern)                                    # 'hello\\.world'
print(bool(re.search(safe_pattern, 'say hello.world')))  # True
print(bool(re.search(safe_pattern, 'say helloXworld')))  # False

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

Проверка адреса электронной почты

import re

def is_valid_email(email):
    pattern = r'^[\w.+-]+@[\w-]+\.[a-zA-Z]{2,}$'
    return bool(re.fullmatch(pattern, email))

print(is_valid_email('[email protected]'))   # True
print(is_valid_email('bad-email@'))         # False

Извлечение всех URL из текста

import re

text = 'Visit https://www.example.com or http://test.org for details.'
urls = re.findall(r'https?://[\w./-]+', text)
print(urls)
# ['https://www.example.com', 'http://test.org']

Удаление лишних пробелов

import re

messy = '  hello    world   Python  '
clean = re.sub(r'\s+', ' ', messy).strip()
print(clean)  # 'hello world Python'

Разбор строки журнала

import re

log = '2023-10-05 14:32:11 ERROR Failed to connect to database'
pattern = re.compile(
    r'(?P<date>\d{4}-\d{2}-\d{2}) '
    r'(?P<time>\d{2}:\d{2}:\d{2}) '
    r'(?P<level>\w+) '
    r'(?P<message>.+)'
)
m = pattern.match(log)
if m:
    print(m.group('level'))    # 'ERROR'
    print(m.group('message'))  # 'Failed to connect to database'

Типичные ошибки

re.match() против re.search()re.match() проверяет только начало строки. Новые пользователи часто ожидают, что он ищет в любом месте, и удивляются, когда получают None.

Забытые необработанные строки'\d' в обычной строке Python — это просто 'd' с нераспознанным escape-символом (или SyntaxWarning в Python 3.12+). Всегда пишите r'\d'.

Жадные совпадения, захватывающие лишнее — если шаблон .* захватывает больше, чем нужно, переключитесь на .*? (ленивый).

Специальные символы в строках замены — в re.sub() последовательности с обратным слэшем, такие как \1 в замене, ссылаются на захваченные группы. Чтобы включить буквальный обратный слэш в замену, напишите \\.

re.findall() с группами — когда шаблон содержит группы, re.findall() возвращает группы, а не полное совпадение. Используйте незахватывающую группу (?:...), если нужно полное совпадение.

Краткая справка

ЗадачаФункция
Найти первое совпадениеre.search(pattern, text)
Найти все совпаденияre.findall(pattern, text)
Перебрать совпаденияre.finditer(pattern, text)
Совпадение в началеre.match(pattern, text)
Совпадение со всей строкойre.fullmatch(pattern, text)
Заменаre.sub(pattern, repl, text)
Разделениеre.split(pattern, text)
Компиляция для повторного использованияre.compile(pattern)
Экранирование пользовательского вводаre.escape(text)

Связанные главы

  • Python Strings — строковые методы, дополняющие regex для более простых текстовых задач.
  • Modify Strings — встроенные методы, такие как replace() и split(), которые позволяют обойтись без regex при фиксированных шаблонах.
  • Python File Handling — чтение файлов построчно для применения regex в большом масштабе.
  • Python Try/Except — обработка ошибок, возникающих при компиляции шаблонов regex из пользовательского ввода.
  • Python Functions — оборачивание логики regex в повторно используемые функции.

Практика

Практика
What does the 're' module in Python provide?
What does the 're' module in Python provide?
Was this page helpful?