Як використовувати модуль регулярного виразу Python re (match, search, sub тощо)

Бізнес

Для обробки регулярних виразів у Python ми використовуємо модуль re зі стандартної бібліотеки. Він дозволяє витягувати, замінювати та розділяти рядки за допомогою шаблонів регулярних виразів.

У цьому розділі ми спочатку пояснимо функції та методи модуля re.

  • Складання шаблонів регулярних виразів:compile()
  • відповідати об’єкту
  • Перевірте, чи збігається початок рядка, витягніть:match()
  • Перевірте збіги, не обмежуючись початком:search()
  • Перевірте, чи відповідає весь рядок:fullmatch()
  • Отримайте список усіх відповідних частин:findall()
  • Отримати всі відповідні частини як ітератор:finditer()
  • Замініть відповідну частину:sub(),subn()
  • Розбиття рядків із шаблонами регулярних виразів:split()

Після цього я поясню метасимволи (спеціальні символи) і спеціальні послідовності регулярних виразів, які можна використовувати в модулі re. По суті, це стандартний синтаксис регулярних виразів, але будьте обережні з встановленням прапорів (особливо re.ASCII).

  • Метасимволи регулярного виразу, спеціальні послідовності та застереження в Python
  • Встановлення прапора
    • Обмежено символами ASCII:re.ASCII
    • Не чутливий до регістру:re.IGNORECASE
    • Установіть відповідність між початком і кінцем кожного рядка:re.MULTILINE
    • Вкажіть кілька прапорів
  • Жадібні та нежадібні матчі

Скомпілювати шаблон регулярного виразу: compile()

Є два способи виконати обробку регулярних виразів у модулі re.

Виконати з функцією

Перший – це функція.re.match(),re.sub()Такі функції доступні для виконання вилучення, заміни та інших процесів за допомогою шаблонів регулярних виразів.

Деталі функцій будуть описані пізніше, але у всіх них першим аргументом є рядок шаблону регулярного виразу, а потім рядок, який потрібно обробити, і так далі. Наприклад, у re.sub(), який виконує заміну, другий аргумент — це рядок підстановки, а третій — рядок, який потрібно обробити.

import re

s = 'aaa@xxx.com, bbb@yyy.com, ccc@zzz.net'

m = re.match(r'([a-z]+)@([a-z]+)\.com', s)
print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>

result = re.sub(r'([a-z]+)@([a-z]+)\.com', 'new-address', s)
print(result)
# new-address, new-address, ccc@zzz.net

Зверніть увагу, що [a-z] у шаблоні регулярного виразу в цьому прикладі означає будь-який символ від a до z (тобто алфавіт у нижньому регістрі), а + означає повторення попереднього шаблону (у цьому випадку [a-z]) один або кілька разів. [a-z]+ відповідає будь-якому рядку, що повторює один або кілька символів нижнього регістру.

. є метасимволом (символом із особливим значенням), і його потрібно екранувати за допомогою зворотної косої риски.

Оскільки рядки шаблону регулярного виразу часто використовують багато зворотних слешів, зручно використовувати необроблені рядки, як у прикладі.

Виконується в методі об’єкта шаблону регулярного виразу

Другим способом обробки регулярних виразів у модулі re є метод об’єкта шаблону регулярного виразу.

Використовуючи re.compile(), ви можете скомпілювати рядок шаблону регулярного виразу, щоб створити об’єкт шаблону регулярного виразу.

p = re.compile(r'([a-z]+)@([a-z]+)\.com')

print(p)
# re.compile('([a-z]+)@([a-z]+)\\.com')

print(type(p))
# <class 're.Pattern'>

re.match(),re.sub()Наприклад, той самий процес, що й ці функції, може виконуватися як методи match(),sub() об’єктів регулярного виразу.

m = p.match(s)
print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>

result = p.sub('new-address', s)
print(result)
# new-address, new-address, ccc@zzz.net

Усі описані нижче функції re.xxx() також надаються як методи об’єкта регулярного виразу.

Якщо ви повторюєте процес, який використовує той самий шаблон, ефективніше створити об’єкт регулярного виразу за допомогою re.compile() і використовувати його.

У наступному прикладі коду функція використовується без компіляції для зручності, але якщо ви хочете використовувати той самий шаблон неодноразово, рекомендується скомпілювати його заздалегідь і виконати як метод об’єкта регулярного виразу.

відповідати об’єкту

match(), search() тощо повертають об’єкт відповідності.

s = 'aaa@xxx.com'

m = re.match(r'[a-z]+@[a-z]+\.[a-z]+', s)
print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>

print(type(m))
# <class 're.Match'>

Узгоджений рядок і позиція отримуються за допомогою наступних методів об’єкта відповідності.

  • Дізнайтеся місце проведення матчу:start(),end(),span()
  • Отримати відповідний рядок:group()
  • Отримайте рядок для кожної групи:groups()
print(m.start())
# 0

print(m.end())
# 11

print(m.span())
# (0, 11)

print(m.group())
# aaa@xxx.com

Якщо ви вклали частину шаблону регулярного виразу в рядок за допомогою дужок(), ця частина буде оброблена як група. У цьому випадку рядок частини, яка відповідає кожній групі в groups(), можна отримати як кортеж.

m = re.match(r'([a-z]+)@([a-z]+)\.([a-z]+)', s)
print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>

print(m.groups())
# ('aaa', 'xxx', 'com')

Перевірте, чи збігається початок рядка, витягніть: match()

match() повертає об’єкт відповідності, якщо початок рядка відповідає шаблону.

Як згадувалося вище, об’єкт відповідності можна використовувати для вилучення відповідного підрядка або просто для перевірки, чи було збігане.

match() перевірить лише початок. Якщо на початку немає відповідного рядка, він повертає None.

s = 'aaa@xxx.com, bbb@yyy.com, ccc@zzz.net'

m = re.match(r'[a-z]+@[a-z]+\.com', s)
print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>

m = re.match(r'[a-z]+@[a-z]+\.net', s)
print(m)
# None

Перевірте збіги, не обмежуючись початком, витягніть: search()

Як і match(), він повертає об’єкт відповідності, якщо він відповідає.

Якщо є декілька відповідних частин, буде повернута лише перша відповідна частина.

s = 'aaa@xxx.com, bbb@yyy.com, ccc@zzz.net'

m = re.search(r'[a-z]+@[a-z]+\.net', s)
print(m)
# <re.Match object; span=(26, 37), match='ccc@zzz.net'>

m = re.search(r'[a-z]+@[a-z]+\.com', s)
print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>

Якщо ви хочете отримати всі відповідні частини, використовуйте findall() або finditer(), як описано нижче.

Перевірте, чи відповідає весь рядок: fullmatch()

Щоб перевірити, чи весь рядок відповідає шаблону регулярного виразу, використовуйте fullmatch(). Це корисно, наприклад, щоб перевірити, чи є рядок дійсним як адреса електронної пошти чи ні.

Якщо весь рядок відповідає, повертається об’єкт відповідності.

s = 'aaa@xxx.com'

m = re.fullmatch(r'[a-z]+@[a-z]+\.com', s)
print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>

Якщо є невідповідні частини (лише часткові збіги або не збігаються взагалі), повертається None.

s = '!!!aaa@xxx.com!!!'

m = re.fullmatch(r'[a-z]+@[a-z]+\.com', s)
print(m)
# None

Функцію fullmatch() було додано в Python 3.4. Якщо ви хочете зробити те саме в попередніх версіях, використовуйте match() і відповідний метасимвол $ в кінці. Якщо весь рядок від початку до кінця не збігається, він повертає None.

s = '!!!aaa@xxx.com!!!'

m = re.match(r'[a-z]+@[a-z]+\.com$', s)
print(m)
# None

Отримайте список усіх відповідних частин: findall()

findall() повертає список усіх відповідних підрядків. Зверніть увагу, що елементи списку є не об’єктами відповідності, а рядками.

s = 'aaa@xxx.com, bbb@yyy.com, ccc@zzz.net'

result = re.findall(r'[a-z]+@[a-z]+\.[a-z]+', s)
print(result)
# ['aaa@xxx.com', 'bbb@yyy.com', 'ccc@zzz.net']

Кількість відповідних частин можна перевірити за допомогою вбудованої функції len(), яка повертає кількість елементів у списку.

print(len(result))
# 3

Групування за допомогою дужок() у шаблоні регулярного виразу повертає список кортежів, елементами яких є рядки кожної групи. Це еквівалентно groups() в об’єкті відповідності.

result = re.findall(r'([a-z]+)@([a-z]+)\.([a-z]+)', s)
print(result)
# [('aaa', 'xxx', 'com'), ('bbb', 'yyy', 'com'), ('ccc', 'zzz', 'net')]

Групові дужки () можуть бути вкладеними, тому, якщо ви також хочете отримати всю відповідність, просто помістіть всю відповідність у дужки ().

result = re.findall(r'(([a-z]+)@([a-z]+)\.([a-z]+))', s)
print(result)
# [('aaa@xxx.com', 'aaa', 'xxx', 'com'), ('bbb@yyy.com', 'bbb', 'yyy', 'com'), ('ccc@zzz.net', 'ccc', 'zzz', 'net')]

Якщо відповідності не знайдено, повертається порожній кортеж.

result = re.findall('[0-9]+', s)
print(result)
# []

Отримати всі відповідні частини як ітератор: finditer()

finditer() повертає всі відповідні частини як ітератор. Елементи не є рядками, як findall(), а є об’єктами відповідності, тому ви можете отримати позицію (індекс) відповідних частин.

Сам ітератор не можна роздрукувати за допомогою print(), щоб отримати його вміст. Якщо ви використовуєте вбудовану функцію next() або оператор for, ви можете отримати вміст один за іншим.

s = 'aaa@xxx.com, bbb@yyy.com, ccc@zzz.net'

result = re.finditer(r'[a-z]+@[a-z]+\.[a-z]+', s)
print(result)
# <callable_iterator object at 0x10b0efa90>

print(type(result))
# <class 'callable_iterator'>

for m in result:
    print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>
# <re.Match object; span=(13, 24), match='bbb@yyy.com'>
# <re.Match object; span=(26, 37), match='ccc@zzz.net'>

Його також можна перетворити на список за допомогою list().

l = list(re.finditer(r'[a-z]+@[a-z]+\.[a-z]+', s))
print(l)
# [<re.Match object; span=(0, 11), match='aaa@xxx.com'>, <re.Match object; span=(13, 24), match='bbb@yyy.com'>, <re.Match object; span=(26, 37), match='ccc@zzz.net'>]

print(l[0])
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>

print(type(l[0]))
# <class 're.Match'>

print(l[0].span())
# (0, 11)

Якщо ви хочете отримати положення всіх відповідних частин, позначення розуміння списку зручніше, ніж list().

print([m.span() for m in re.finditer(r'[a-z]+@[a-z]+\.[a-z]+', s)])
# [(0, 11), (13, 24), (26, 37)]

Ітератор витягує елементи по порядку. Зверніть увагу, що якщо ви спробуєте витягти більше елементів після досягнення кінця, ви залишитеся ні з чим.

result = re.finditer(r'[a-z]+@[a-z]+\.[a-z]+', s)

for m in result:
    print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>
# <re.Match object; span=(13, 24), match='bbb@yyy.com'>
# <re.Match object; span=(26, 37), match='ccc@zzz.net'>

print(list(result))
# []

Замініть відповідні частини: sub(), subn()

Використовуючи sub(), ви можете замінити відповідну частину іншим рядком. Буде повернуто замінений рядок.

s = 'aaa@xxx.com, bbb@yyy.com, ccc@zzz.net'

result = re.sub(r'[a-z]+@[a-z]+\.com', 'new-address', s)
print(result)
# new-address, new-address, ccc@zzz.net

print(type(result))
# <class 'str'>

Під час групування за допомогою дужок() відповідний рядок можна використовувати в заміненому рядку.

За замовчуванням підтримується наступне: Зауважте, що для звичайних рядків, які не є необробленими рядками, перед зворотною косою рискою має бути перерахована зворотна коса риска, щоб уникнути зворотної скісної риски.

\1Перша дужка
\2Друга дужка
\3Третя дужка
result = re.sub(r'([a-z]+)@([a-z]+)\.com', r'\1@\2.net', s)
print(result)
# aaa@xxx.net, bbb@yyy.net, ccc@zzz.net

?P<xxx>
Якщо ви назвати групу, написавши це на початку дужок шаблону регулярного виразу, ви можете вказати її, використовуючи назву замість номера, як показано нижче.
\g<xxx>

result = re.sub(r'(?P<local>[a-z]+)@(?P<SLD>[a-z]+)\.com', r'\g<local>@\g<SLD>.net', s)
print(result)
# aaa@xxx.net, bbb@yyy.net, ccc@zzz.net

Кількість аргументів визначає максимальну кількість замін. Буде замінено лише рахунок з лівого боку.

result = re.sub(r'[a-z]+@[a-z]+\.com', 'new-address', s, count=1)
print(result)
# new-address, bbb@yyy.com, ccc@zzz.net

subn() повертає кортеж заміненого рядка (так само, як і значення, яке повертає sub()) і кількість замінених частин (число, яке відповідає шаблону).

result = re.subn(r'[a-z]+@[a-z]+\.com', 'new-address', s)
print(result)
# ('new-address, new-address, ccc@zzz.net', 2)

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

result = re.subn(r'(?P<local>[a-z]+)@(?P<SLD>[a-z]+)\.com', r'\g<local>@\g<SLD>.net', s)
print(result)
# ('aaa@xxx.net, bbb@yyy.net, ccc@zzz.net', 2)

result = re.subn(r'[a-z]+@[a-z]+\.com', 'new-address', s, count=1)
print(result)
# ('new-address, bbb@yyy.com, ccc@zzz.net', 1)

Розбиття рядків із шаблонами регулярних виразів: split()

split() розбиває рядок на частину, яка відповідає шаблону, і повертає його у вигляді списку.

Зауважте, що перше й останнє збіги будуть містити порожні рядки на початку та в кінці підсумкового списку.

s = '111aaa222bbb333'

result = re.split('[a-z]+', s)
print(result)
# ['111', '222', '333']

result = re.split('[0-9]+', s)
print(result)
# ['', 'aaa', 'bbb', '']

Аргумент maxsplit вказує максимальну кількість розколів (шт.). Розділиться лише рахунок з лівого боку.

result = re.split('[a-z]+', s, 1)
print(result)
# ['111', '222bbb333']

Метасимволи регулярного виразу, спеціальні послідовності та застереження в Python

Основні метасимволи регулярного виразу (спеціальні символи) і спеціальні послідовності, які можна використовувати в модулі Python 3 re, є такими:

метасимволзміст
.Будь-який окремий символ, крім нового рядка (включаючи новий рядок з прапорцем DOTALL)
^Початок рядка (також відповідає початку кожного рядка з прапорцем MULTILINE)
$Кінець рядка (також збігається з кінцем кожного рядка з прапорцем MULTILINE)
*Повторіть попередній візерунок більше 0 разів
+Повторіть попередній візерунок хоча б один раз.
?Повторіть попередній візерунок 0 або 1 раз
{m}Повторіть попередній візерунок m разів
{m, n}Останній візерунок.m~nповторити
[]Набір символів[]Відповідає будь-якому з цих символів
|АБОA|BВідповідає шаблону A або B
особлива послідовністьзміст
\dДесяткові числа Unicode (обмежено номерами ASCII за допомогою прапорця ASCII)
\D\dМається на увазі протилежне цьому.
\sПробіли Unicode (обмежено пробілами ASCII за допомогою прапорця ASCII)
\S\sМається на увазі протилежне цьому.
\wСимволи та символи підкреслення Unicode (обмежено буквено-цифровими символами ASCII і підкресленням за допомогою прапорця ASCII)
\W\wМається на увазі протилежне цьому.

Не всі вони наведені в цій таблиці. Повний список дивіться в офіційній документації.

Також зауважте, що деякі значення відрізняються в Python 2.

Встановлення прапора

Як показано в таблиці вище, деякі метасимволи та спеціальні послідовності змінюють свій режим залежно від прапора.

Тут висвітлені лише основні прапори. Решту дивіться в офіційній документації.

Обмежено символами ASCII: re.ASCII

\wЦе також відповідатиме двобайтові кандзі, буквено-цифрові символи тощо за замовчуванням для рядків Python 3. Це не еквівалентно наступному, оскільки це не стандартний регулярний вираз.[a-zA-Z0-9_]

m = re.match(r'\w+', '漢字ABC123')
print(m)
# <re.Match object; span=(0, 11), match='漢字ABC123'>

m = re.match('[a-zA-Z0-9_]+', '漢字ABC123')
print(m)
# None

Якщо ви вкажете re.ASCII для прапорів аргументів у кожній функції або додасте наступний вбудований прапор на початок рядка шаблону регулярного виразу, він відповідатиме лише символам ASCII (він не відповідатиме двобайтовим японським, буквено-цифровим символам тощо .).
(?a)
У цьому випадку наступні два є еквівалентними.
\w#ERROR![a-zA-Z0-9_]

m = re.match(r'\w+', '漢字ABC123', flags=re.ASCII)
print(m)
# None

m = re.match(r'(?a)\w+', '漢字ABC123')
print(m)
# None

Те ж саме стосується компіляції за допомогою re.compile(). Використовуйте прапорці аргументів або вбудовані прапори.

p = re.compile(r'\w+', flags=re.ASCII)
print(p)
# re.compile('\\w+', re.ASCII)

print(p.match('漢字ABC123'))
# None

p = re.compile(r'(?a)\w+')
print(p)
# re.compile('(?a)\\w+', re.ASCII)

print(p.match('漢字ABC123'))
# None

ASCII також доступний як коротка форма re. В. Ви можете використовувати будь-яке.

print(re.ASCII is re.A)
# True

\W, протилежність \W, також піддається впливу re.ASCII та вбудованих прапорів.

m = re.match(r'\W+', '漢字ABC123')
print(m)
# None

m = re.match(r'\W+', '漢字ABC123', flags=re.ASCII)
print(m)
# <re.Match object; span=(0, 11), match='漢字ABC123'>

Як і у випадку \w, наступні два збігаються як з однобайтовими, так і з двобайтовими символами за замовчуванням, але обмежуються однобайтовими символами, якщо вказано прапори re.ASCII або inline.

  • Установіть відповідність між числами\d
  • Збігається з порожнім пробілом\s
  • Збігається з нечислами\D
  • Відповідає будь-якому непробілу.\S
m = re.match(r'\d+', '123')
print(m)
# <re.Match object; span=(0, 3), match='123'>

m = re.match(r'\d+', '123')
print(m)
# <re.Match object; span=(0, 3), match='123'>

m = re.match(r'\d+', '123', flags=re.ASCII)
print(m)
# <re.Match object; span=(0, 3), match='123'>

m = re.match(r'\d+', '123', flags=re.ASCII)
print(m)
# None

m = re.match(r'\s+', ' ')  # full-width space
print(m)
# <re.Match object; span=(0, 1), match='\u3000'>

m = re.match(r'\s+', ' ', flags=re.ASCII)
print(m)
# None

Не чутливий до регістру:re.IGNORECASE

За замовчуванням він чутливий до регістру. Щоб узгодити обидва, вам потрібно включити у шаблон як великі, так і малі літери.

re.IGNORECASEЯкщо це вказано, воно відповідатиме без урахування регістру. Еквівалент прапора i в стандартних регулярних виразах.

m = re.match('[a-zA-Z]+', 'abcABC')
print(m)
# <re.Match object; span=(0, 6), match='abcABC'>

m = re.match('[a-z]+', 'abcABC', flags=re.IGNORECASE)
print(m)
# <re.Match object; span=(0, 6), match='abcABC'>

m = re.match('[A-Z]+', 'abcABC', flags=re.IGNORECASE)
print(m)
# <re.Match object; span=(0, 6), match='abcABC'>

Ви можете використовувати менше або дорівнює.

  • вбудований прапор(?i)
  • абревіатураre.I

Установіть відповідність між початком і кінцем кожного рядка:re.MULTILINE

^Метасимволи в цьому регулярному виразі відповідають початку рядка.

За замовчуванням збігається лише початок усього рядка, але наступне також відповідатиме початку кожного рядка. Еквівалент прапора m у стандартних регулярних виразах.
re.MULTILINE

s = '''aaa-xxx
bbb-yyy
ccc-zzz'''

print(s)
# aaa-xxx
# bbb-yyy
# ccc-zzz

result = re.findall('[a-z]+', s)
print(result)
# ['aaa', 'xxx', 'bbb', 'yyy', 'ccc', 'zzz']

result = re.findall('^[a-z]+', s)
print(result)
# ['aaa']

result = re.findall('^[a-z]+', s, flags=re.MULTILINE)
print(result)
# ['aaa', 'bbb', 'ccc']

$Збігається з кінцем рядка. За замовчуванням збігається лише кінець усього рядка.
re.MULTILINEЯкщо ви вкажете це, воно також відповідатиме кінця кожного рядка.

result = re.findall('[a-z]+$', s)
print(result)
# ['zzz']

result = re.findall('[a-z]+$', s, flags=re.MULTILINE)
print(result)
# ['xxx', 'yyy', 'zzz']

Ви можете використовувати менше або дорівнює.

  • вбудований прапор(?m)
  • абревіатураre.M

Вкажіть кілька прапорів

|Якщо ви хочете одночасно ввімкнути кілька прапорців, скористайтеся цим. У разі вбудованих прапорів після кожного символу має бути літера, як показано нижче.
(?am)

s = '''aaa-xxx
漢漢漢-字字字
bbb-zzz'''

print(s)
# aaa-xxx
# 漢漢漢-字字字
# bbb-zzz

result = re.findall(r'^\w+', s, flags=re.M)
print(result)
# ['aaa', '漢漢漢', 'bbb']

result = re.findall(r'^\w+', s, flags=re.M | re.A)
print(result)
# ['aaa', 'bbb']

result = re.findall(r'(?am)^\w+', s)
print(result)
# ['aaa', 'bbb']

Жадібні та нежадібні матчі

Це загальна проблема з регулярними виразами, а не лише проблема з Python, але я напишу про неї, тому що це, як правило, викликає у мене проблеми.

За замовчуванням, нижче є жадібний збіг, який відповідає найдовшому рядку.

  • *
  • +
  • ?
s = 'aaa@xxx.com, bbb@yyy.com'

m = re.match(r'.+com', s)
print(m)
# <re.Match object; span=(0, 24), match='aaa@xxx.com, bbb@yyy.com'>

print(m.group())
# aaa@xxx.com, bbb@yyy.com

? після цього призведе до нежадібного, мінімального збігу, що відповідає найкоротшому рядку.

  • *?
  • +?
  • ??
m = re.match(r'.+?com', s)
print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>

print(m.group())
# aaa@xxx.com

Зауважте, що за замовчуванням жадібний збіг може відповідати неочікуваним рядкам.