Регулярные выражения — это мини-язык для описания текстовых шаблонов. Модуль `re` компилирует эти шаблоны и применяет их к строкам.
Четыре самые используемые функции отличаются тем, *где* они ищут:
```python
import re
text = "User: alice, age 30"
re.search(r"\d+", text) # первое совпадение ГДЕ УГОДНО в строке -> Match "30"
re.match(r"\d+", text) # совпадение только В НАЧАЛЕ -> None (начинается с "U")
re.fullmatch(r"\d+", "42") # вся строка должна совпасть -> Match "42"
re.findall(r"\d+", text) # все непересекающиеся совпадения -> ["30"]
re.sub(r"\d+", "??", text) # заменить все совпадения -> "User: alice, age ??"
```
**Зачем raw-строки?**
Обратный слеш в строке Python имеет особое значение: `\n` = перевод строки, `\t` = табуляция. Но regex тоже активно использует обратные слеши (`\d`, `\w`, `\s`). Без raw-строк пришлось бы удваивать каждый слеш:
```python
re.search("\\d+", text) # работает, но нечитаемо
re.search(r"\d+", text) # то же самое - всегда используй raw-строки
```
Правило: каждый паттерн regex должен быть raw-строкой `r"..."`.
**Читаем паттерн посимвольно**
Возьмём `r"(\w+)@(\w+\.\w+)"` (простой паттерн email):
```
( открыть группу 1
\w+ один или более символов слова [a-zA-Z0-9_]
) закрыть группу 1
@ литеральный "@"
( открыть группу 2
\w+ один или более символов слова
\. литеральная точка (экранированная - обычная . означает "любой символ")
\w+ один или более символов слова
) закрыть группу 2
```
```python
m = re.search(r"(\w+)@(\w+\.\w+)", "Отправить на [email protected] пожалуйста")
m.group(0) # "[email protected]" - полное совпадение
m.group(1) # "bob" - группа 1
m.group(2) # "example.com" - группа 2
```
**Методы объекта Match**
```python
m = re.search(r"\d{4}", "Год: 2024 или 2025")
if m:
m.group() # "2024" - найденный текст
m.start() # 6 - начальный индекс
m.end() # 10 - конечный индекс
m.span() # (6, 10) - оба сразу
```
Всегда проверяй `if m:` перед вызовом методов - `search` возвращает `None`, если совпадений нет.
**re.sub с функцией**
Замена может быть функцией, получающей каждый объект совпадения:
```python
def double(m):
return str(int(m.group()) * 2)
re.sub(r"\d+", double, "a=3, b=7") # "a=6, b=14"
```
Это мощнее статической замены строки.
Группы, не-захватывающие группы, флаги, re.compile, finditer
**Захватывающие группы** позволяют извлекать части совпадения. Оберни скобками то, что хочешь захватить.
```python
import re
log = "2024-03-15 ERROR: disk full"
m = re.search(r"(\d{4}-\d{2}-\d{2}) (\w+): (.+)", log)
m.group(1) # "2024-03-15"
m.group(2) # "ERROR"
m.group(3) # "disk full"
```
**Именованные группы** делают код самодокументированным: `(?P<name>...)` определяет группу, `m.group('name')` её получает.
```python
m = re.search(r"(?P<date>\d{4}-\d{2}-\d{2}) (?P<level>\w+): (?P<msg>.+)", log)
m.group("date") # "2024-03-15"
m.group("level") # "ERROR"
m.group("msg") # "disk full"
```
**Не-захватывающие группы** `(?:...)` группируют без создания нумерованного захвата - удобны для чередования или квантификаторов, когда не нужно извлекать:
```python
# Найти 'colour' или 'color', не захватывая необязательную 'u'
re.findall(r"colou?r", "colour and color") # ['colour', 'color']
# Не-захватывающая группа для чередования
re.findall(r"(?:cat|dog)s?", "cats and dogs") # ['cats', 'dogs']
```
**Флаги** меняют поведение движка:
```python
re.search(r"hello", "Hello World", re.IGNORECASE) # без учёта регистра
re.findall(r"^\w+", text, re.MULTILINE) # ^ совпадает с началом каждой строки
re.search(r"begin.+end", text, re.DOTALL) # . совпадает и с символами новой строки
re.search(r"\d+", text, re.IGNORECASE | re.MULTILINE) # комбинация через |
```
Встроенный флаг в паттерн: `(?i)hello` означает без учёта регистра (то же что `re.IGNORECASE`).
**re.compile** - предварительная компиляция, если паттерн используется много раз:
```python
phone_pat = re.compile(r"\+?\d[\d\s\-]{7,14}\d")
for line in big_list:
m = phone_pat.search(line) # быстрее, чем re.search(pattern, line) каждый раз
```
**re.finditer vs re.findall**
`findall` возвращает список строк. `finditer` возвращает итератор объектов Match - более экономный по памяти для больших текстов, и даёт информацию о позиции:
```python
for m in re.finditer(r"\d+", "a1 b22 c333"):
print(m.group(), m.start()) # "1" 1 / "22" 4 / "333" 7
```
**re.split** делит по паттерну (не только по фиксированному разделителю):
```python
re.split(r"[,;\s]+", "one, two; three") # ['one', 'two', 'three']
# Захватывающая группа в паттерне -> разделитель включается в результат
re.split(r"(,)", "a,b,c") # ['a', ',', 'b', ',', 'c']
```
Жадные vs ленивые квантификаторы, re.VERBOSE и справочник синтаксиса
**Жадные vs ленивые квантификаторы**
По умолчанию квантификаторы (`*`, `+`, `{n,m}`) являются *жадными* - они захватывают как можно больше. Добавь `?`, чтобы сделать их *ленивыми* - захватывать как можно меньше.
```python
import re
html = "<b>bold</b> and <i>italic</i>"
re.findall(r"<.+>", html) # жадный -> ['<b>bold</b> and <i>italic</i>']
re.findall(r"<.+?>", html) # ленивый -> ['<b>', '</b>', '<i>', '</i>']
```
Распространённая ошибка: использование `.+` внутри угловых скобок и захват слишком большого количества. Всегда спрашивай: должно ли это остановиться на первой возможной позиции?
**re.VERBOSE - читаемые сложные паттерны**
Режим verbose игнорирует пробелы и комментарии `#` внутри паттерна:
```python
date_pat = re.compile(r'''
(?P<year> \d{4} ) # 4-значный год
-
(?P<month> \d{2} ) # 2-значный месяц
-
(?P<day> \d{2} ) # 2-значный день
''', re.VERBOSE)
m = date_pat.search("Дедлайн: 2024-12-31")
m.group("year") # "2024"
m.group("month") # "12"
m.group("day") # "31"
```
Используй VERBOSE для любого паттерна длиннее ~40 символов.
**Справочник синтаксиса паттернов**
```
. любой символ кроме новой строки (re.DOTALL включает новую строку)
^ начало строки (или строки в тексте с re.MULTILINE)
$ конец строки
\d цифра [0-9]
\D не-цифра
\w символ слова [a-zA-Z0-9_]
\W не-символ слова
\s пробельный символ (пробел, табуляция, новая строка)
\S не-пробельный символ
* 0 или более (жадный) *? ленивый
+ 1 или более (жадный) +? ленивый
? 0 или 1
{n} ровно n
{n,m} от n до m (жадный) {n,m}? ленивый
[abc] класс символов: a, b или c
[^abc] отрицательный класс: всё кроме a, b, c
[a-z] диапазон
(...) захватывающая группа
(?:...) не-захватывающая группа
(?P<name>...) именованная захватывающая группа
| чередование: a|b совпадает с a или b
\ экранирует следующий символ
```
**Типичные ловушки**
- `.` совпадает с любым символом - используй `\.` для литеральной точки
- `^` и `$` ведут себя иначе с `re.MULTILINE` - проверяй явно
- `re.match` проверяет только начало; `re.fullmatch` проверяет всю строку - не путай их при валидации
- Откат в сложных паттернах может быть медленным - тестируй на длинных строках, если важна производительность
Используйте `re.match`, чтобы проверить, является ли строка допустимым идентификатором Python (буквы, цифры, подчёркивания; должен начинаться с буквы или подчёркивания).
import re
def is_identifier(s):
# ваш код здесь
pass
print(is_identifier('my_var')) # True
print(is_identifier('_private')) # True
print(is_identifier('2bad')) # False
print(is_identifier('hello world')) # False
Решение
import re
def is_identifier(s):
return bool(re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', s))
Используйте группы захвата, чтобы извлечь год, месяц и день из строки даты в формате 'DD/MM/YYYY'.
import re
def parse_date(s):
# Вернуть (day, month, year) как целые числа или None при неверном формате
pass
print(parse_date('15/03/2024')) # (15, 3, 2024)
print(parse_date('01/12/1999')) # (1, 12, 1999)
print(parse_date('invalid')) # None
Решение
import re
def parse_date(s):
m = re.match(r'^(\d{2})/(\d{2})/(\d{4})$', s)
if not m:
return None
return int(m.group(1)), int(m.group(2)), int(m.group(3))
Используйте `re.sub` с функцией для удвоения всех чисел, найденных в строке.
import re
def double_numbers(s):
# ваш код здесь
pass
print(double_numbers('I have 3 cats and 12 dogs')) # 'I have 6 cats and 24 dogs'
print(double_numbers('price: 9.99')) # 'price: 19.98'
Скомпилируйте шаблон regex один раз и используйте его для фильтрации списка строк, оставляя только те, что соответствуют шаблону номера телефона: `+X-XXX-XXX-XXXX` (где X — цифра).
import re
def filter_phones(strings):
# ваш код здесь
pass
items = ['+1-800-555-1234', 'not a phone', '+44-020-555-9876', '123-456']
print(filter_phones(items)) # ['+1-800-555-1234', '+44-020-555-9876']
Решение
import re
def filter_phones(strings):
pattern = re.compile(r'^\+\d+-\d{3}-\d{3}-\d{4}$')
return [s for s in strings if pattern.match(s)]
Используйте именованные группы для разбора строки лога в формате `[LEVEL] YYYY-MM-DD: message`. Верните словарь с ключами 'level', 'date' и 'message'.
import re
def parse_log(line):
# ваш код здесь
pass
print(parse_log('[ERROR] 2024-03-15: Connection refused'))
# {'level': 'ERROR', 'date': '2024-03-15', 'message': 'Connection refused'}
print(parse_log('[INFO] 2024-01-01: Server started'))
# {'level': 'INFO', 'date': '2024-01-01', 'message': 'Server started'}
Решение
import re
def parse_log(line):
m = re.match(r'^\[(?P<level>\w+)\] (?P<date>\d{4}-\d{2}-\d{2}): (?P<message>.+)$', line)
return m.groupdict() if m else None
Используйте `re.split` для разбивки строки на любой знак пунктуации (`.`, `,`, `!`, `?`, `;`, `:`). Отфильтруйте пустые строки из результата.
import re
def split_on_punctuation(s):
# ваш код здесь
pass
print(split_on_punctuation('Hello, world! How are you?'))
# ['Hello', ' world', ' How are you']
print(split_on_punctuation('one.two;three:four'))
# ['one', 'two', 'three', 'four']
Решение
import re
def split_on_punctuation(s):
return [part for part in re.split(r'[.,!?;:]', s) if part]
Используйте флаг `re.VERBOSE` (или `re.X`) для написания читаемого regex с комментариями, проверяющего надёжный пароль: минимум 8 символов, минимум одна заглавная, одна строчная, одна цифра, один специальный символ (`!@#$%^&*`).
import re
def is_strong_password(s):
# ваш код здесь
pass
print(is_strong_password('Abc@1234')) # True
print(is_strong_password('abcdefgh')) # False (нет заглавной/цифры/спец.)
print(is_strong_password('Abc@12')) # False (слишком короткий)
Решение
import re
def is_strong_password(s):
pattern = re.compile(r'''
^ # начало строки
(?=.*[A-Z]) # минимум одна заглавная
(?=.*[a-z]) # минимум одна строчная
(?=.*\d) # минимум одна цифра
(?=.*[!@#$%^&*]) # минимум один спецсимвол
.{8,} # минимум 8 символов всего
$ # конец строки
''', re.VERBOSE)
return bool(pattern.match(s))
No split tab
Настройки cookies
Мы используем необходимые cookies для работы сайта. С вашего разрешения мы также можем сохранять настройки сайта и использовать аналитические и рекламные cookies, чтобы понимать использование сайта и поддерживать развитие проекта.
* Вы всегда можете изменить свой выбор в настройках сайта.
Выберите категории cookies
Настройки аналитики
Можно отключить аналитику использования платформы. Также можно отправить в Google Analytics запрос на удаление данных об использовании этого сайта, связанных с этим браузером.
Учебный workspace
Учитесь, читая, запуская код и решая задачи.
Практикуйте программирование с пояснениями тем, упражнениями, инструментами browser IDE, проверкой regex и тренировкой печати кода в одном workspace.
Открывайте инструменты во вкладках.Упражнения, IDE-инструменты и тренажеры остаются доступными как вкладки сайта.
Переключайтесь без потери контекста.Переходите между пояснениями, кодом и утилитами, сохраняя свое место.
Используйте sidebar как карту.Левые панели содержат навигацию, настройки, файлы, libraries и управление инструментами.
PythonJavaScriptSQLite
Одна IDE, три практичных режима
Python в браузере.Запускайте небольшие скрипты, пробуйте библиотеки и тренируйте API-запросы без установки.
JavaScript для быстрых экспериментов.Проверяйте код для браузера и сравнивайте идеи рядом с учебными материалами.
SQLite для практики с данными.Открывайте обозреватель базы данных, изучайте таблицы, пишите запросы и учитесь SQL локально.
ТемаIDE
Работайте рядом в split tabs
Держите инструкции перед глазами.Откройте упражнение или справочную страницу рядом с IDE, вместо постоянных переключений.
Сравнивайте инструменты во время обучения.Размещайте проверки regex, пояснения и эксперименты с кодом рядом, когда это нужно для задачи.
Закройте split, когда закончите.Workspace вернётся к одной сфокусированной вкладке, а открытые вкладки сайта останутся доступны.
Тренажер слепой печати кода
Или просто текста
Тренажер рассчитан на физическую клавиатуру.Откройте этот раздел на ноутбуке или компьютере с широким экраном. На телефоне тренировка слепой печати не будет корректной.
Скорость: 0 зн/мин
0 слов/мин
Лучшая скорость (60с): 0 зн/мин
0 слов/мин
Ошибки: 0
Общее время: 0.0 с
Для активации режима слепого набора не подсматривайте на физическую клавиатуру.