Шаг 26.
Python: сборник рецептов.
Строки и текст. Поиск совпадений и поиск текстовых паттернов

    На этом шаге мы рассмотрим использование некоторых функций из модуля re.

Задача

    Вы хотите отыскать совпадение или провести поиск по определенному шаблону.

Решение

    Если текст, который вы хотите найти, является простым литералом, в большинстве случаев вам подойдут базовые строковые методы, такие как str.find(), str.endswith(), str.startwith() и другие подобные. Например:

>>> text = 'yeah, but no, but yeah, but no, but yeah'
>>> # Точное совпадение 
>>> text == 'yeah'
False
>>> # Совпадение по началу или концу 
>>> text.startswith('yeah')
True
>>> text.endswith('no')
False
>>> # Поиск места первого вхождения 
>>> text.find('no')
10
>>>

    Для более сложного поиска совпадений используйте регулярные выражения и модуль re. Чтобы проиллюстрировать базовые механики использования регулярных выражений, предположим, что вы хотите найти даты, определенные цифрами, такие как "11/27/2012". Вот пример того, как вы можете это сделать:

>>> text1 = '11/27/2012'
>>> text2 = 'Nov 27, 2012'
>>> import re
>>> # Простое сопоставление: \d+ означает совпадение одной или более цифр 
>>> if re.match(r'\d+/\d+/\d+', text1):
	print('yes')
else:
	print('no')

	
yes
>>> if re.match(r'\d+/\d+/\d+', text2):
	print('yes')
else:
	print('no')

	
no
>>> 

    Если вы собираетесь много раз искать по одному и тому же шаблону, часто окупается предварительная компиляция шаблона регулярного выражения в объект шаблона. Например:

>>> datepat = re.compile(r'\d+/\d+/\d+')
>>> if datepat.match(text1):
	print('yes')
else:
	print('no')

	
yes
>>> if datepat.match(text2):
	print('yes')
else:
	print('no')

	
no
>>> 

    match() всегда пытается найти совпадения в начале строки. Если вы хотите провести поиск по всем случаям соответствия шаблону, используйте метод findall(). Например:

>>> text = 'Today is 11/27/2012. PyCon starts 3/13/2013.'
>>> datepat.findall(text)
['11/27/2012', '3/13/2013']
>>>

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

>>> datepat = re.compile(r'(\d+)/(\d+)/(\d+)')
>>>

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

>>> m = datepat.match('11/27/2012')
>>> m
<re.Match object; span=(0, 10), match='11/27/2012'>
>>> # Извлекаем содержимое каждой группы
>>> m.group(0)
'11/27/2012'
>>> m.group(1)
'11
>>> m.group(2)
'27'
>>> m.group(3)
'2012'
>>> m.groups()
('11', '27', '2012')
>>> month, day, year = m.groups()
>>> 
>>> # Найти все совпадения (обратите внимание на
>>> # разрезание на кортежи)
>>> text = 'Today is 11/27/2012. PyCon starts 3/13/2013.'
>>> datepat.findall(text)
[('11', '27', '2012'), ('3', '13', '2013')]
>>> for month, day, year in datepat.findall(text):
	print('{}-{}-{}'.format(year, month, day))

	
2012-11-27
2013-3-13
>>> 

    Метод findall() проходит по тексту и находит все совпадения, возвращая их в списке. Если вы хотите искать совпадения итеративно, используйте метод finditer():

>>> for m in datepat.finditer(text):
	print(m.groups())

	
('11', '27', '2012')
('3', '13', '2013')
>>> 


Обсуждение

    Вводного курса в теорию регулярных выражений здесь вы не найдете. Однако этот рецепт демонстрирует простейшие примеры использования модуля re для поиска совпадений в тексте. Самые основные приемы - компилирование шаблонов с использованием re.compile() и последующее использование таких методов, как match(), findall() или finditer().

    При составлении шаблонов часто нужно использовать "сырые" (raw) строки, такие как r'(+)/(+)/(+)'. Подобные строки оставляют символы обратных слешей необработанными, что может быть полезно в контексте применения регулярных выражений. С другой стороны, вы можете использовать двойные обратные слеши: '(\\d+)/(\\d+)/(\\d+)'.

    Учтите, что метод match() проверяет только начало строки. Возможно, что он найдет вещи, которых вы не ожидаете. Например:

>>> m = datepat.match('11/27/2012abcdef')
>>> m
<re.Match object; span=(0, 10), match='11/27/2012'>
>>> m.group()
'11/27/2012'
>>> 

    Если вам нужно точное совпадение, убедитесь, что шаблон включает символ завершения ($), как в примере ниже:

>>> datepat = re.compile(r'(\d+)/(\d+)/(\d+)$')
>>> datepat.match('11/27/2012abcdef')
>>> datepat.match('11/27/2012')
<re.Match object; span=(0, 10), match='11/27/2012'>
>>> 

    И последнее: если вы проводите простые операции поиска, вы часто можете пропустить шаг компиляции и использовать функции уровня модуля из модуля re. Например:

>>> re.findall(r'(\d+)/(\d+)/(\d+)', text)
[('11', '27', '2012'), ('3', '13', '2013')]
>>>

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

    На следующем шаге мы рассмотрим поиск и замену текста.




Предыдущий шаг Содержание Следующий шаг