Модуль re – регулярные выражения

Цель: Поиск и изменение текста с помощью шаблонов.
Доступно в версиях: 1.5 и более поздних.

Регулярные выражения – это шаблоны для поиска, описываемые с помощью специального синтаксиса. Термин «регулярные выражение» (“regular expressions”) часто сокращается до “regex” или “regexp”. Выражения могут включать в себя строки текста, повторы, составные шаблоны, ветвления и другие сложные правила.

Регулярные выражения чаще всего применяются для обработки текста. Например, в шаблонах поиска, в редакторах текста и современных IDE. Они также являются важной частью таких утилит командной строки Unix: sed, grep и awk.

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

Существует несколько спецификаций регулярных выражений с открытым исходным кодом. Каждая из них использует базовый синтаксис, но имеет различные расширения дополнительного функционала. Синтаксис, используемый в модуле re, основан на синтаксисе языка программирования Perl с улучшениями, свойственными для Python.

Содержание

Поиск в тексте по шаблону

Наиболее часто модуль re используется для поиска в тексте по шаблону. Приведенный ниже пример ищет слова ‘this’ и ‘that’ в строке текста.

import re

patterns = [ 'this', 'that' ]
text = 'Does this text match the pattern?'

for pattern in patterns:
    print 'Looking for "%s" in "%s" ->' % (pattern, text),

    if re.search(pattern,  text):
        print 'found a match!'
    else:
        print 'no match'

Метод search() принимает шаблон, а также текст для поиска, и возвращает объект Match, когда найдено соответствующая строка. Если нет, то search() возвращает значение None.

$ python re_simple.py

Looking for "this" in "Does this text match the pattern?" -> found a match!
Looking for "that" in "Does this text match the pattern?" -> no match

Объект Match содержит оригинал исходной строки, использованное регулярное выражение и позицию в тексте, где найдено совпадение.

import re

pattern = 'this'
text = 'Does this text match the pattern?'

match = re.search(pattern, text)

s = match.start()
e = match.end()

print 'Found "%s" in "%s" from %d to %d ("%s")' % 
    (match.re.pattern, match.string, s, e, text[s:e])

Методы start() и end() содержат целочисленные индексы строки, обозначающие, где встречается подходящий под шаблон текст.

$ python re_simple_match.py

Found "this" in "Does this text match the pattern?" from 5 to 9 ("this")

Компиляция выражений

Модуль re включает в себя функции для работы с регулярными выражениями. Но он более эффективен при компиляции выражений, которые использует программа. Функция compile() преобразует выражение в объект RegexObject.

import re

# Предкомпилируем эти шаблоны
regexes = [ re.compile(p) for p in [ 'this',
                                     'that',
                                     ]
            ]
text = 'Does this text match the pattern?'

for regex in regexes:
    print 'Looking for "%s" in "%s" ->' % (regex.pattern, text),

    if regex.search(text):
        print 'found a match!'
    else:
        print 'no match'

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

$ python re_simple_compiled.py

Looking for "this" in "Does this text match the pattern?" -> found a match!
Looking for "that" in "Does this text match the pattern?" -> no match

Множественные вхождения

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

import re

text = 'abbaaabbbbaaaaa'

pattern = 'ab'

for match in re.findall(pattern, text):
    print 'Found "%s"' % match

Подстрока ab встречается дважды.

$ python re_findall.py

Found "ab"
Found "ab"

Метод finditer() возвращает итератор, который создает объекты Match вместо строк, возвращаемых методом findall().

import re

text = 'abbaaabbbbaaaaa'

pattern = 'ab'

for match in re.finditer(pattern, text):
    s = match.start()
    e = match.end()
    print 'Found "%s" at %d:%d' % (text[s:e], s, e)

Этот пример находит те же два вхождения подстроки ab, а объект Match показывает их место в оригинальной строке.

$ python re_finditer.py

Found "ab" at 0:2
Found "ab" at 5:7

Синтаксис шаблонов

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

import re

def test_patterns(text, patterns=[]):
    """ Given source text and a list of patterns, look for
    matches for each pattern within the text and print
    them to stdout.
    """
    # Показываем позиции символов и введённый текст
    print
    print ''.join(str(i/10 or ' ') for i in range(len(text)))
    print ''.join(str(i%10) for i in range(len(text)))
    print text

    # Ищем каждый шаблон в тексте и выводим результаты
    for pattern in patterns:
        print
        print 'Matching "%s"' % pattern
        for match in re.finditer(pattern, text):
            s = match.start()
            e = match.end()
            print '  %2d : %2d = "%s"' % 
                (s, e-1, text[s:e])
    return

if __name__ == '__main__':
    test_patterns('abbaaabbbbaaaaa', ['ab'])

Функция test_patterns() отображает текст, позиции символов, а также набор строк, соответствующий каждому шаблону.

$ python re_test_patterns.py


          11111
012345678901234
abbaaabbbbaaaaa

Matching "ab"
   0 :  1 = "ab"
   5 :  6 = "ab"

Повторы

Существует пять способов задать повтор символов в шаблоне. Паттерн, за которым следует метасимвол *, повторяется ноль или более раз. При этом ноль значит, что он не обязан присутствовать в тексте.

Замените * на +, и искомый паттерн должен присутствовать в тексте хотя бы раз. Использование ? означает, что шаблон должен встречаться ноль или один раз. Для определённого количества вхождений используйте {m} после шаблона, где m заменяется количеством вхождений.

Чтобы задать ограниченное количество вхождений, используйте {m,n}, где m – это минимальное количество вхождений, а n – максимальное. Если опустить n ({m,}), то это будет означать, что шаблон должен встречаться хотя бы m раз, но без ограничений на максимальное количество.

from re_test_patterns import test_patterns

test_patterns('abbaaabbbbaaaaa',
              [ 'ab*',     # a с последующей ноль или более раз b
                'ab+',     # a с последующей одной или более b
                'ab?',     # a с последующей ни одной или одной b
                'ab{3}',   # a с последующими тремя b
                'ab{2,3}', # a с последующими двумя или тремя b
                ])

Заметьте, насколько больше вхождений у шаблонов ab* и ab?, чем у ab+.

$ python re_repetition.py


          11111
012345678901234
abbaaabbbbaaaaa

Matching "ab*"
   0 :  2 = "abb"
   3 :  3 = "a"
   4 :  4 = "a"
   5 :  9 = "abbbb"
  10 : 10 = "a"
  11 : 11 = "a"
  12 : 12 = "a"
  13 : 13 = "a"
  14 : 14 = "a"

Matching "ab+"
   0 :  2 = "abb"
   5 :  9 = "abbbb"

Matching "ab?"
   0 :  1 = "ab"
   3 :  3 = "a"
   4 :  4 = "a"
   5 :  6 = "ab"
  10 : 10 = "a"
  11 : 11 = "a"
  12 : 12 = "a"
  13 : 13 = "a"
  14 : 14 = "a"

Matching "ab{3}"
   5 :  8 = "abbb"

Matching "ab{2,3}"
   0 :  2 = "abb"
   5 :  8 = "abbb"

Стандартная обработка инструкции повторения заключается в том, чтобы добавить в результат как можно больше символов, пока результат соответствует шаблону. Это так называемое жадное поведение, которое может находить меньшее количество отдельных совпадений. Либо совпадения могут включать в себя больше исходного текста, чем требовалось. Такое поведение паттерна можно отключить с помощью символа «?», следующего за инструкцией повторения.

from re_test_patterns import test_patterns

test_patterns('abbaaabbbbaaaaa',
              [ 'ab*?',     # a с последующей ноль или более раз b
                'ab+?',     # a с последующей одной или более b
                'ab??',     # a с последующей ни одной или одной b
                'ab{3}?',   # a с последующими тремя b
                'ab{2,3}?', # a с последующими двумя или тремя b
                ])

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

$ python re_repetition_non_greedy.py


          11111
012345678901234
abbaaabbbbaaaaa

Matching "ab*?"
   0 :  0 = "a"
   3 :  3 = "a"
   4 :  4 = "a"
   5 :  5 = "a"
  10 : 10 = "a"
  11 : 11 = "a"
  12 : 12 = "a"
  13 : 13 = "a"
  14 : 14 = "a"

Matching "ab+?"
   0 :  1 = "ab"
   5 :  6 = "ab"

Matching "ab??"
   0 :  0 = "a"
   3 :  3 = "a"
   4 :  4 = "a"
   5 :  5 = "a"
  10 : 10 = "a"
  11 : 11 = "a"
  12 : 12 = "a"
  13 : 13 = "a"
  14 : 14 = "a"

Matching "ab{3}?"
   5 :  8 = "abbb"

Matching "ab{2,3}?"
   0 :  2 = "abb"
   5 :  7 = "abb"

Наборы символов

Наборы символов – это группы символов, каждый из которых может соответствовать определённому месту в шаблоне. Например, шаблону [ab] соответствует a или b.

from re_test_patterns import test_patterns

test_patterns('abbaaabbbbaaaaa',
              [ '[ab]',    # либо a, либо b
                'a[ab]+',  # a с последующими одной или несколькими a или b
                'a[ab]+?', # a с последующими одной или несколькими a или b, 
не жадный
                ])

«Жадный» вариант шаблона a[ab]+ вернет всю строку целиком, поскольку первый символ – это a, а каждый последующий – либо a, либо b.

$ python re_charset.py


          11111
012345678901234
abbaaabbbbaaaaa

Matching "[ab]"
   0 :  0 = "a"
   1 :  1 = "b"
   2 :  2 = "b"
   3 :  3 = "a"
   4 :  4 = "a"
   5 :  5 = "a"
   6 :  6 = "b"
   7 :  7 = "b"
   8 :  8 = "b"
   9 :  9 = "b"
  10 : 10 = "a"
  11 : 11 = "a"
  12 : 12 = "a"
  13 : 13 = "a"
  14 : 14 = "a"

Matching "a[ab]+"
   0 : 14 = "abbaaabbbbaaaaa"

Matching "a[ab]+?"
   0 :  1 = "ab"
   3 :  4 = "aa"
   5 :  6 = "ab"
  10 : 11 = "aa"
  12 : 13 = "aa"

Набор символов также может быть использован для исключения определённых значений. Специальный маркер «^» включает поиск символов, не входящих в следующий за ним набор.

from re_test_patterns import test_patterns

test_patterns('This is some text -- with punctuation.',
              [ '[^-. ]+',  # последовательности без -, . или пробелов
                ])

Этот шаблон находит все подстроки, которые не содержат символы «-», «.» или пробелы.

$ python re_charset_exclude.py


          1111111111222222222233333333
01234567890123456789012345678901234567
This is some text -- with punctuation.

Matching "[^-. ]+"
   0 :  3 = "This"
   5 :  6 = "is"
   8 : 11 = "some"
  13 : 16 = "text"
  21 : 24 = "with"
  26 : 36 = "punctuation"

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

from re_test_patterns import test_patterns

test_patterns('This is some text -- with punctuation.',
              [ '[a-z]+',      # последовательности строчных букв
                '[A-Z]+',      # последовательности заглавных букв
                '[a-zA-Z]+',   # последовательности строчных или заглавных букв
                '[A-Z][a-z]+', # одна заглавная буква, за которой следуют строчные
                ])

Диапазон a-z это строчные буквы ASCII, а диапазон A-Z — заглавные буквы ASCII.

$ python re_charset_ranges.py


          1111111111222222222233333333
01234567890123456789012345678901234567
This is some text -- with punctuation.

Matching "[a-z]+"
   1 :  3 = "his"
   5 :  6 = "is"
   8 : 11 = "some"
  13 : 16 = "text"
  21 : 24 = "with"
  26 : 36 = "punctuation"

Matching "[A-Z]+"
   0 :  0 = "T"

Matching "[a-zA-Z]+"
   0 :  3 = "This"
   5 :  6 = "is"
   8 : 11 = "some"
  13 : 16 = "text"
  21 : 24 = "with"
  26 : 36 = "punctuation"

Matching "[A-Z][a-z]+"
   0 :  3 = "This"

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

from re_test_patterns import test_patterns

test_patterns('abbaaabbbbaaaaa',
              [ 'a.',    # a с последующим любым символом
                'b.',    # b с последующим любым символом
                'a.*b',  # a, за которым следует что угодно, заканчивающееся b
                'a.*?b', # a, за которым следует что угодно, заканчивающееся b
 (не жадный)
                ])

Сочетание точки с метасимволом повтора может привести к длинным совпадениям, если только не используется нежадная форма поиска.

$ python re_charset_dot.py


          11111
012345678901234
abbaaabbbbaaaaa

Matching "a."
   0 :  1 = "ab"
   3 :  4 = "aa"
   5 :  6 = "ab"
  10 : 11 = "aa"
  12 : 13 = "aa"

Matching "b."
   1 :  2 = "bb"
   6 :  7 = "bb"
   8 :  9 = "bb"

Matching "a.*b"
   0 :  9 = "abbaaabbbb"

Matching "a.*?b"
   0 :  1 = "ab"
   3 :  6 = "aaab"

Escape-коды

Более компактное представление шаблона использует символьные коды для поиска нескольких наборов символов. Escape-коды, использующиеся в модуле re:

Код Значение
d Цифра.
D Не цифра.
s Пробел (табуляция, пробел, новая строка и т.п.).
S Не пробел.
w Буква или цифра.
W Не буква и не цифра.

Замечание

Escape-коды начинаются с обратного слеша (). Но в коде Python этот символ должен быть экранирован. Использование литерала r решает эту проблему.

from re_test_patterns import test_patterns

test_patterns('This is a prime #1 example!',
              [ r'd+', # последовательность цифр
                r'D+', # последовательность без цифр
                r's+', # последовательность пробелов
                r'S+', # последовательность без пробелов
                r'w+', # последовательность букв или цифр
                r'W+', # последовательность без букв и цифр
                ])

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

$ python re_escape_codes.py


          11111111112222222
012345678901234567890123456
This is a prime #1 example!

Matching "d+"
  17 : 17 = "1"

Matching "D+"
   0 : 16 = "This is a prime #"
  18 : 26 = " example!"

Matching "s+"
   4 :  4 = " "
   7 :  7 = " "
   9 :  9 = " "
  15 : 15 = " "
  18 : 18 = " "

Matching "S+"
   0 :  3 = "This"
   5 :  6 = "is"
   8 :  8 = "a"
  10 : 14 = "prime"
  16 : 17 = "#1"
  19 : 26 = "example!"

Matching "w+"
   0 :  3 = "This"
   5 :  6 = "is"
   8 :  8 = "a"
  10 : 14 = "prime"
  17 : 17 = "1"
  19 : 25 = "example"

Matching "W+"
   4 :  4 = " "
   7 :  7 = " "
   9 :  9 = " "
  15 : 16 = " #"
  18 : 18 = " "
  26 : 26 = "!"

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

from re_test_patterns import test_patterns

test_patterns(r'd+ D+ s+ S+ w+ W+',
              [ r'd+',
                r'D+',
                r's+',
                r'S+',
                r'w+',
                r'W+',
                ])

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

$ python re_escape_escapes.py


          1111111111222
01234567890123456789012
d+ D+ s+ S+ w+ W+

Matching "d+"
   0 :  2 = "d+"

Matching "D+"
   4 :  6 = "D+"

Matching "s+"
   8 : 10 = "s+"

Matching "S+"
  12 : 14 = "S+"

Matching "w+"
  16 : 18 = "w+"

Matching "W+"
  20 : 22 = "W+"

Привязка

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

Код Значение
^ начало текста или строки
$ конец текста или строки
A начало текста
Z конец текста
b пустой символ в начале или конце слова
B пустой символ не с начала и не с конца слова
from re_test_patterns import test_patterns

test_patterns('This is some text -- with punctuation.',
        [ r'^w+',     # слово в начале строки
         r'Aw+',    # слово в начале текста
            r'w+S*$',  # слово в конце строки с необязательным знаком препинания
            r'w+S*Z', # слово в конце текста с необязательным знаком препинания
                r'w*tw*',  # слово, содержащее 't'
                r'btw+',   # 't' в начале слова
                r'w+tb',   # 't' в конце слова
                r'BtB',    # 't' не в начале и не в конце слова
                ])

В этом примере шаблоны для поиска слов в начале и в конце строки отличаются. За словом в конце строки следует знак пунктуации, заканчивающий предложение. Шаблон w+$ не подойдёт, поскольку точка не обозначает букву или цифру.

$ python re_anchoring.py


          1111111111222222222233333333
01234567890123456789012345678901234567
This is some text -- with punctuation.

Matching "^w+"
   0 :  3 = "This"

Matching "Aw+"
   0 :  3 = "This"

Matching "w+S*$"
  26 : 37 = "punctuation."

Matching "w+S*Z"
  26 : 37 = "punctuation."

Matching "w*tw*"
  13 : 16 = "text"
  21 : 24 = "with"
  26 : 36 = "punctuation"

Matching "btw+"
  13 : 16 = "text"

Matching "w+tb"
  13 : 16 = "text"

Matching "BtB"
  23 : 23 = "t"
  30 : 30 = "t"
  33 : 33 = "t"

Ограничение поиска

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

import re

text = 'This is some text -- with punctuation.'
pattern = 'is'

print 'Text   :', text
print 'Pattern:', pattern

m = re.match(pattern, text)
print 'Match  :', m
s = re.search(pattern, text)
print 'Search :', s

Строка is расположена не в начале текста, поэтому она не находится с помощью метода match(). Но эта последовательность появляется в тексте два раза, поэтому метод search() находит её.

$ python re_match.py

Text   : This is some text -- with punctuation.
Pattern: is
Match  : None
Search : <_sre.SRE_Match object at 0x100452988>

Метод search()принимает необязательные параметры начальной и конечной позиций, чтобы ограничить поиск до части исходной строки.

import re

text = 'This is some text -- with punctuation.'
pattern = re.compile(r'bw*isw*b')

print 'Text:', text
print

pos = 0
while True:
    match = pattern.search(text, pos)
    if not match:
        break
    s = match.start()
    e = match.end()
    print '  %2d : %2d = "%s"' % 
        (s, e-1, text[s:e])
    # Двигаемся далее по тексту для следующего поиска
    pos = e
    

Этот пример реализует менее эффективную форму метода iterall(). Каждый раз, когда находится совпадение, конечная позиция этого совпадения используется для следующего поиска.

$ python re_search_substring.py

Text: This is some text -- with punctuation.

   0 :  3 = "This"
   5 :  6 = "is"

Группировка в шаблонах

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

from re_test_patterns import test_patterns

test_patterns('abbaaabbb p576ey 68a7dv baaaaa',
[ 'a(ab)',    # 'a', за которым следует 'ab'
'a(a*b*)',  # 'a', за которым следует 0 или несколько 'a' и ноль или несколько 'b'
                'a(ab)*',   # 'a', за которым следует 0 или несколько 'ab'
                'a(ab)+',   # 'a', за которым следует 1 или несколько 'ab'
                ])

Любое регулярное выражение может быть включено в более крупное выражение. Все модификаторы повторов могут быть применены к группе как к единому целому, требуя повторения группы в шаблоне.

$ python re_groups.py


          11111
012345678901234
abbaaabbbbaaaaa

Matching "a(ab)"
   4 :  6 = "aab"

Matching "a(a*b*)"
   0 :  2 = "abb"
   3 :  9 = "aaabbbb"
  10 : 14 = "aaaaa"

Matching "a(ab)*"
   0 :  0 = "a"
   3 :  3 = "a"
   4 :  6 = "aab"
  10 : 10 = "a"
  11 : 11 = "a"
  12 : 12 = "a"
  13 : 13 = "a"
  14 : 14 = "a"

Matching "a(ab)+"
   4 :  6 = "aab"

Чтобы найти части строки, совпадающие с каждой отдельной группой в шаблоне, используйте метод groups() объекта Match.

import re

text = 'This is some text -- with punctuation.'

print text
print

for pattern in [ r'^(w+)',           # слово в начале строки string
          r'(w+)S*$',        # слово в конце строки с необязательной пунктуацией
          r'(btw+)W+(w+)', # слово, начинающееся с 't', затем другое слово
                 r'(w+t)b',         # слово, заканчивающиеся на 't'
                 ]:
    regex = re.compile(pattern)
    match = regex.search(text)
    print 'Matching "%s"' % pattern
    print '  ', match.groups()
    print

Match.groups() возвращает последовательность строк в порядке чередования групп в шаблоне.

$ python re_groups_match.py

This is some text -- with punctuation.

Matching "^(w+)"
   ('This',)

Matching "(w+)S*$"
   ('punctuation',)

Matching "(btw+)W+(w+)"
   ('text', 'with')

Matching "(w+t)b"
   ('text',)

Если вам не нужны все части, которые соответствуют группам, можно искать совпадение только по одной группе при помощи метода group().

import re

text = 'This is some text -- with punctuation.'

print 'Input text            :', text

# слово, начинающееся с 't', затем другое слово
regex = re.compile(r'(btw+)W+(w+)')
print 'Pattern               :', regex.pattern

match = regex.search(text)
print 'Entire match          :', match.group(0)
print 'Word starting with "t":', match.group(1)
print 'Word after "t" word   :', match.group(2)

Группа 0 представляет собой строку, совпадающую со всем регулярным выражением. Подгруппы пронумерованы, начиная с 1 в порядке чередования в выражении.

$ python re_groups_individual.py

Input text            : This is some text -- with punctuation.
Pattern               : (btw+)W+(w+)
Entire match          : text -- with
Word starting with "t": text
Word after "t" word   : with

Python расширяет базовый синтаксис группировки, добавляя именованные группы. Использование имён для обращения к группам упрощает редактирование шаблона в будущем. Чтобы задать группе имя, используйте синтаксис (P?<name>pattern).

import re

text = 'This is some text -- with punctuation.'

print text
print

for pattern in [ r'^(?P<first_word>w+)',
                 r'(?P<last_word>w+)S*$',
                 r'(?P<t_word>btw+)W+(?P<other_word>w+)',
                 r'(?P<ends_with_t>w+t)b',
                 ]:
    regex = re.compile(pattern)
    match = regex.search(text)
    print 'Matching "%s"' % pattern
    print '  ', match.groups()
    print '  ', match.groupdict()
    print

Используйте метод groupdict(), чтобы получить словарь, сопоставляющий имена групп с совпадающими подстроками. Именованные шаблоны также включаются в упорядоченную последовательность метода groups().

$ python re_groups_named.py

This is some text -- with punctuation.

Matching "^(?P<first_word>w+)"
   ('This',)
   {'first_word': 'This'}

Matching "(?P<last_word>w+)S*$"
   ('punctuation',)
   {'last_word': 'punctuation'}

Matching "(?P<t_word>btw+)W+(?P<other_word>w+)"
   ('text', 'with')
   {'other_word': 'with', 't_word': 'text'}

Matching "(?P<ends_with_t>w+t)b"
   ('text',)
   {'ends_with_t': 'text'}

Обновлённая версия функции test_patterns(), показывающая нумерованные и именованные группы совпадений по шаблону, облегчит понимание следующих примеров.

import re

def test_patterns(text, patterns=[]):
    """Имея исходный текст и список шаблонов, ищем
    совпадения для каждого шаблона в тексте и выводим
    их в stdout.
    """
    # Показываем позиции символов и введённый текст
    print
    print ''.join(str(i/10 or ' ') for i in range(len(text)))
    print ''.join(str(i%10) for i in range(len(text)))
    print text

    # Ищем каждый шаблон в тексте и выводим результаты
    for pattern in patterns:
        print
        print 'Matching "%s"' % pattern
        for match in re.finditer(pattern, text):
            s = match.start()
            e = match.end()
            print '  %2d : %2d = "%s"' % 
                (s, e-1, text[s:e])
            print '    Groups:', match.groups()
            if match.groupdict():
                print '    Named groups:', match.groupdict()
            print
    return

Группы могут наследовать другие группы для создания более сложных выражений.

from re_test_patterns_groups import test_patterns

test_patterns('abbaaabbbbaaaaa',
              [r'a((a*)(b*))', # 'a' followed by 0-n 'a' and 0-n 'b'
               ])

В данном случае группе (a*) соответствует пустая строка, поэтому метод groups() вернет пустую строку как подходящее значение.

$ python re_groups_nested.py


          11111
012345678901234
abbaaabbbbaaaaa

Matching "a((a*)(b*))"
   0 :  2 = "abb"
    Groups: ('bb', '', 'bb')

   3 :  9 = "aaabbbb"
    Groups: ('aabbbb', 'aa', 'bbbb')

  10 : 14 = "aaaaa"
    Groups: ('aaaa', 'aaaa', '')

Группы также полезны для создания альтернативных шаблонов. Используйте символ «|» для поиска строки, которая должна соответствовать одному или нескольким шаблонам.

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

from re_test_patterns_groups import test_patterns

test_patterns('abbaaabbbbaaaaa',
 [r'a((a+)|(b+))', # 'a', за которым следует последовательность из 'a' 
или последовательность из 'b'
   r'a((a|b)+)',    # 'a', за которым следует последовательность из 'a' или 'b'
               ])

Когда в строке нет совпадений с альтернативной группой, но весь шаблон имеет совпадения, то результат работы метода groups() равен None в том месте, где должна располагаться альтернативная группа.

$ python re_groups_alternative.py


          11111
012345678901234
abbaaabbbbaaaaa

Matching "a((a+)|(b+))"
   0 :  2 = "abb"
    Groups: ('bb', None, 'bb')

   3 :  5 = "aaa"
    Groups: ('aa', 'aa', None)

  10 : 14 = "aaaaa"
    Groups: ('aaaa', 'aaaa', None)


Matching "a((a|b)+)"
   0 : 14 = "abbaaabbbbaaaaa"
    Groups: ('bbaaabbbbaaaaa', 'a')

Определение группы с подшаблоном полезно, когда строка, совпадающая с подшаблоном, не является частью искомого. Такие группы называются незахватными. Чтобы создать незахватную группу, используйте синтаксис (?:pattern).

from re_test_patterns_groups import test_patterns

test_patterns('abbaaabbbbaaaaa',
              [r'a((a+)|(b+))',     # захватываем совпадения
               r'a((?:a+)|(?:b+))', # не захватываем совпадения
               ])

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

$ python re_groups_non_capturing.py


          11111
012345678901234
abbaaabbbbaaaaa

Matching "a((a+)|(b+))"
   0 :  2 = "abb"
    Groups: ('bb', None, 'bb')

   3 :  5 = "aaa"
    Groups: ('aa', 'aa', None)

  10 : 14 = "aaaaa"
    Groups: ('aaaa', 'aaaa', None)


Matching "a((?:a+)|(?:b+))"
   0 :  2 = "abb"
    Groups: ('bb',)

   3 :  5 = "aaa"
    Groups: ('aa',)

  10 : 14 = "aaaaa"
    Groups: ('aaaa',)

Опции поиска

Для изменения обработки регулярных выражений используйте флаги. Они могут объединяться, используя битовую операцию «или», и передаваться в compile(), search(), match()и другие функции, которые принимают шаблон для поиска.

Регистронезависимый поиск

Использование флага IGNORECASE приводит к тому, чтобы буквенные символы и буквенные диапазоны в шаблоне будут совпадать как для строчных, так и для заглавных букв.

import re

text = 'This is some text -- with punctuation.'
pattern = r'bTw+'
with_case = re.compile(pattern)
without_case = re.compile(pattern, re.IGNORECASE)

print 'Text            :', text
print 'Pattern         :', pattern
print 'Case-sensitive  :', with_case.findall(text)
print 'Case-insensitive:', without_case.findall(text)

Поскольку выражение включает в себя букву «T», то без использования IGNORECASE единственным подходящим словом будет «This». А когда регистр игнорируется, слово «text» также подходит.

$ python re_flags_ignorecase.py

Text            : This is some text -- with punctuation.
Pattern         : bTw+
Case-sensitive  : ['This']
Case-insensitive: ['This', 'text']

Исходный текст из нескольких строк

Есть два флага, которые влияют на поиск в многострочном тексте. Флаг MULTILINE, управляет тем, как шаблон обрабатывает инструкции привязки текста, содержащего символы новой строки. Когда многострочный режим включён, правила привязки для ^ и $ применяются к началу и концу каждой строки в дополнение к привязке ко всему тексту.

import re

text = 'This is some text -- with punctuation.nAnd a second line.'
pattern = r'(^w+)|(w+S*$)'
single_line = re.compile(pattern)
multiline = re.compile(pattern, re.MULTILINE)

print 'Text        :', repr(text)
print 'Pattern     :', pattern
print 'Single Line :', single_line.findall(text)
print 'Multline    :', multiline.findall(text)

Шаблону в этом примере соответствуют первое или последнее слово исходной строки. Ему соответствует line. в конце текста даже при том, что там нет символа новой строки.

$ python re_flags_multiline.py

Text        : 'This is some text -- with punctuation.nAnd a second line.'
Pattern     : (^w+)|(w+S*$)
Single Line : [('This', ''), ('', 'line.')]
Multline    : [('This', ''), ('', 'punctuation.'), ('And', ''), ('', 'line.')]

DOTALL – ещё один флаг для настройки поиска в многострочном тексте. Символу точки в шаблоне соответствует всё, кроме символа новой строки. Данный флаг позволяет символу новой строки также соответствовать метасимволу «.».

import re

text = 'This is some text -- with punctuation.nAnd a second line.'
pattern = r'.+'
no_newlines = re.compile(pattern)
dotall = re.compile(pattern, re.DOTALL)

print 'Text        :', repr(text)
print 'Pattern     :', pattern
print 'No newlines :', no_newlines.findall(text)
print 'Dotall      :', dotall.findall(text)

Без этого флага каждая строка исходного текста совпадает с шаблоном по отдельности. Добавление флага приводит к тому, что под шаблон подходит весь текст.

$ python re_flags_dotall.py

Text        : 'This is some text -- with punctuation.nAnd a second line.'
Pattern     : .+
No newlines : ['This is some text -- with punctuation.', 'And a second line.']
Dotall      : ['This is some text -- with punctuation.nAnd a second line.']

Unicode

В Python 2 объекты str используют набор символов ASCII. Поэтому шаблон и исходный текст должны быть представлены в кодировке ASCII. Escape-коды, описанные выше, также по умолчанию определяются в терминах ASCII.

Вследствие этого шаблону w+ будет соответствовать слово “French”, но не слово “Français”, поскольку ç не является частью набора символов ASCII. Чтобы включить поиск по символам Unicode в Python 2, добавьте флаг UNICODE при компиляции шаблона.

import re
import codecs
import sys

# Устанавливаем стандартную кодировку вывода в UTF-8.
sys.stdout = codecs.getwriter('UTF-8')(sys.stdout)

text = u'Français złoty Österreich'
pattern = ur'w+'
ascii_pattern = re.compile(pattern)
unicode_pattern = re.compile(pattern, re.UNICODE)

print 'Text    :', text
print 'Pattern :', pattern
print 'ASCII   :', u', '.join(ascii_pattern.findall(text))
print 'Unicode :', u', '.join(unicode_pattern.findall(text))

Другие Escape-последовательности (W, b, B, d, D, s и S) также иначе обрабатываются для текста в кодировке Unicode. Поэтому обработчик регулярных выражений использует базу Unicode для поиска свойств каждого символа.

$ python re_flags_unicode.py

Text    : Français złoty Österreich
Pattern : w+
ASCII   : Fran, ais, z, oty, sterreich
Unicode : Français, złoty, Österreich

Замечание

Python 3 по умолчанию использует Unicode для всех строк, поэтому данный флаг не требуется.

Многословные регулярные выражения

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

Шаблон проверки email — адреса проиллюстрирует использование многословного режима. Первая часть распознаёт адрес электронной почты, который заканчивается на .com, .org или .edu.

import re

address = re.compile('[wd.+-]+@([wd.]+.)+(com|org|edu)', re.UNICODE)

candidates = [
    u'first.last@example.com',
    u'first.last+category@gmail.com',
    u'valid-address@mail.example.com',
    u'not-valid@example.foo',
    ]

for candidate in candidates:
    print
    print 'Candidate:', candidate
    match = address.search(candidate)
    if match:
        print '  Matches'
    else:
        print '  No match'
    

Это регулярное выражение уже сложное. В нём несколько классов символов, групп и повторов.

$ python re_email_compact.py


Candidate: first.last@example.com
  Matches

Candidate: first.last+category@gmail.com
  Matches

Candidate: valid-address@mail.example.com
  Matches

Candidate: not-valid@example.foo
  No match

Преобразование шаблона в многословный формат облегчит его расширение.

import re

address = re.compile(
    '''
    [wd.+-]+       # имя пользователя
    @
    ([wd.]+.)+    # домен второго уровня
    (com|org|edu)    # нужно поддерживать больше доменов верхнего уровня
    ''',
    re.UNICODE | re.VERBOSE)

candidates = [
    u'first.last@example.com',
    u'first.last+category@gmail.com',
    u'valid-address@mail.example.com',
    u'not-valid@example.foo',
    ]

for candidate in candidates:
    print
    print 'Candidate:', candidate
    match = address.search(candidate)
    if match:
        print '  Matches'
    else:
        print '  No match'

Этому регулярному выражению соответствуют те же строки, но в расширенном формате его легче читать. Комментарии также помогают различать части шаблона.

$ python re_email_verbose.py


Candidate: first.last@example.com
  Matches

Candidate: first.last+category@gmail.com
  Matches

Candidate: valid-address@mail.example.com
  Matches

Candidate: not-valid@example.foo
  No match

Данная версия шаблона разбивает строки, включающие в себя имя пользователя и email- адрес так, как они могут отображаться в заголовке письма. Сначала следует имя, и оно отделено от email-адреса в угловых скобках (< и >).

import re

address = re.compile(
    '''

    # Имя состоит из букв и может включать "." в сокращениях 
    # титула или инициалах.
    ((?P<name>
       ([w.,]+s+)*[w.,]+)
       s*
       # Адреса email заключены в угловые скобки: < >
       # но нам нужен только один адрес, если мы нашли имя, поэтому
       # оставляем начальную < в этой группе.
       <
    )? # вся эта группа необязательна

    # Сам адрес: username@domain.tld
    (?P<email>
      [wd.+-]+       # имя пользователя
      @
      ([wd.]+.)+    # домен второго уровня
      (com|org|edu)    # ограничиваем допустимые домены верхнего уровня
    )

    >? # необязательная закрывающая скобка
    ''',
    re.UNICODE | re.VERBOSE)

candidates = [
    u'first.last@example.com',
    u'first.last+category@gmail.com',
    u'valid-address@mail.example.com',
    u'not-valid@example.foo',
    u'First Last <first.last@example.com>',
    u'No Brackets first.last@example.com',
    u'First Last',
    u'First Middle Last <first.last@example.com>',
    u'First M. Last <first.last@example.com>',
    u'<first.last@example.com>',
    ]

for candidate in candidates:
    print
    print 'Candidate:', candidate
    match = address.search(candidate)
    if match:
        print '  Match name :', match.groupdict()['name']
        print '  Match email:', match.groupdict()['email']
    else:
        print '  No match'

Возможность вставлять комментарии в многословное регулярное выражение помогает в поддержке кода. Финальная версия шаблона содержит заметки по его реализации для разработчиков. А также пробелы для отделения групп друг от друга и выделения их иерархии.

$ python re_email_with_name.py


Candidate: first.last@example.com
  Match name : None
  Match email: first.last@example.com

Candidate: first.last+category@gmail.com
  Match name : None
  Match email: first.last+category@gmail.com

Candidate: valid-address@mail.example.com
  Match name : None
  Match email: valid-address@mail.example.com

Candidate: not-valid@example.foo
  No match

Candidate: First Last <first.last@example.com>
  Match name : First Last
  Match email: first.last@example.com

Candidate: No Brackets first.last@example.com
  Match name : None
  Match email: first.last@example.com

Candidate: First Last
  No match

Candidate: First Middle Last <first.last@example.com>
  Match name : First Middle Last
  Match email: first.last@example.com

Candidate: First M. Last <first.last@example.com>
  Match name : First M. Last
  Match email: first.last@example.com

Candidate: <first.last@example.com>
  Match name : None
  Match email: first.last@example.com

Включение флагов в шаблоны

Также можно добавлять флаги в само регулярное выражение. Например, чтобы включить регистронезависимый поиск, добавьте (?i)в начало шаблона.

import re

text = 'This is some text -- with punctuation.'
pattern = r'(?i)bTw+'
regex = re.compile(pattern)

print 'Text      :', text
print 'Pattern   :', pattern
print 'Matches   :', regex.findall(text)

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

$ python re_flags_embedded.py

Text      : This is some text -- with punctuation.
Pattern   : (?i)bTw+
Matches   : ['This', 'text']

Аббревиатуры всех флагов приведены ниже:

Флаг Аббревиатура
IGNORECASE I
MULTILINE M
DOTALL S
UNICODE U
VERBOSE X

Включённые в регулярное выражение флаги можно объединять путём размещения в одной группе. Например, (?imu) включает регистронезависимый поиск по многострочному тексту в кодировке Unicode.

Поиск вперёд или назад

Иногда нужно сопоставить часть шаблона, только если для другой его части также будет найдено совпадение. Например, в регулярном выражении для разбора email-адреса обе угловые скобки были помечены как необязательные. В реальности скобки должны быть парными.

Приведенная ниже версия регулярного выражения использует утверждение положительного поиска вперёд для поиска пары. Синтаксис поиска вперёд следующий:

(?=pattern).
import re

address = re.compile(
    '''
    # Имя состоит из букв и может включать "." в сокращениях 
    # титула или инициалах.
    ((?P<name>
       ([w.,]+s+)*[w.,]+
     )
     s+
    ) # имя больше не является необязательным

    # ПОИСК ВПЕРЁД
    # Адреса email заключены в угловые скобки, но мы хотим искать
    # только парные скобки, либо никаких.
    (?= (<.*>$)       # оставшаяся часть заключена в угловые скобки
        |
        ([^<].*[^>]$) # оставшаяся часть НЕ заключена в угловые скобки
      )

    <? # необязательная открывающая угловая скобка

    # Сам адрес: username@domain.tld
    (?P<email>
      [wd.+-]+       # имя пользователя
      @
      ([wd.]+.)+    # домен второго уровня
      (com|org|edu)    # ограничиваем допустимые домены верхнего уровня
    )

    >? # необязательная закрывающая угловая скобка
    ''',
    re.UNICODE | re.VERBOSE)

candidates = [
    u'First Last <first.last@example.com>',
    u'No Brackets first.last@example.com',
    u'Open Bracket <first.last@example.com',
    u'Close Bracket first.last@example.com>',
    ]

for candidate in candidates:
    print
    print 'Candidate:', candidate
    match = address.search(candidate)
    if match:
        print '  Match name :', match.groupdict()['name']
        print '  Match email:', match.groupdict()['email']
    else:
        print '  No match'

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

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

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

$ python re_look_ahead.py


Candidate: First Last <first.last@example.com>
  Match name : First Last
  Match email: first.last@example.com

Candidate: No Brackets first.last@example.com
  Match name : No Brackets
  Match email: first.last@example.com

Candidate: Open Bracket <first.last@example.com
  No match

Candidate: Close Bracket first.last@example.com>
  No match

Утверждение негативного поиска вперёд ((?!pattern)) говорит, что текст с этого места не должен соответствовать шаблону. Например, шаблон распознавания email-адреса может быть изменён, чтобы игнорировать адреса noreply, которые часто используются автоматическими системами.

import re

address = re.compile(
    '''
    ^

    # Email адрес: username@domain.tld

    # Игнорируем адреса noreply
    (?!noreply@.*$)

    [wd.+-]+       # имя пользователя
    @
    ([wd.]+.)+    # домен второго уровня
    (com|org|edu)    # ограничиваем допустимые домены верхнего уровня

    $
    ''',
    re.UNICODE | re.VERBOSE)

candidates = [
    u'first.last@example.com',
    u'noreply@example.com',
    ]

for candidate in candidates:
    print
    print 'Candidate:', candidate
    match = address.search(candidate)
    if match:
        print '  Match:', candidate[match.start():match.end()]
    else:
        print '  No match'

Адреса, начинающиеся с noreply, не подходят под шаблон, поскольку утверждение поиска вперёд не работает.

$ python re_negative_look_ahead.py


Candidate: first.last@example.com
  Match: first.last@example.com

Candidate: noreply@example.com
  No match

Шаблон также может быть написан с использованием утверждения негативного поиска назад после нахождения совпадения для имени пользователя. Синтаксис: ?<!pattern).

import re

address = re.compile(
    '''
    ^

    # Email адрес: username@domain.tld

    [wd.+-]+       # имя пользователя

    # Игнорируем адреса noreply
    (?<!noreply)

    @
    ([wd.]+.)+    # домен второго уровня
    (com|org|edu)    # ограничиваем допустимые домены верхнего уровня

    $
    ''',
    re.UNICODE | re.VERBOSE)

candidates = [
    u'first.last@example.com',
    u'noreply@example.com',
    ]

for candidate in candidates:
    print
    print 'Candidate:', candidate
    match = address.search(candidate)
    if match:
        print '  Match:', candidate[match.start():match.end()]
    else:
        print '  No match'

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

$ python re_negative_look_behind.py


Candidate: first.last@example.com
  Match: first.last@example.com

Candidate: noreply@example.com
  No match

Утверждение положительного поиска вперёд может быть использовано для поиска текста, следующего за шаблоном. Синтаксис: (?<=pattern). Приведенное ниже регулярное выражение находит упоминания пользователей Twitter.

import re

twitter = re.compile(
    '''
    # Имя пользователя twitter: @username
    (?<=@)
    ([wd_]+)       # Имя пользователя
    ''',
    re.UNICODE | re.VERBOSE)

text = '''This text includes two Twitter handles.
One for @ThePSF, and one for the author, @doughellmann.
'''

print text
for match in twitter.findall(text):
    print 'Handle:', match

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

$ python re_look_behind.py

This text includes two Twitter handles.
One for @ThePSF, and one for the author, @doughellmann.

Handle: ThePSF
Handle: doughellmann

Выражения, ссылающиеся на себя

Совпадения могут быть использованы в последующих частях регулярного выражения. Пример с email-адресами может быть дополнен, чтобы соответствовать только адресам, состоящим из имени и фамилии человека. Для этого используется обратная ссылка на эти группы. Самый простой способ достичь этого – обратиться к одной из предыдущих групп по номеру, используя num.

import re

address = re.compile(
    r'''

    # Обычное имя
    (w+)               # имя
    s+
    (([w.]+)s+)?      # необязательное отчество или инициалы 
    (w+)               # фамилия

    s+

    <

    # Email адрес: first_name.last_name@domain.tld
    (?P<email>
      1               # имя
      .
      4               # фамилия
      @
      ([wd.]+.)+    # домен второго уровня
      (com|org|edu)    # ограничиваем допустимые домены верхнего уровня
    )

    >
    ''',
    re.UNICODE | re.VERBOSE | re.IGNORECASE)

candidates = [
    u'First Last <first.last@example.com>',
    u'Different Name <first.last@example.com>',
    u'First Middle Last <first.last@example.com>',
    u'First M. Last <first.last@example.com>',
    ]

for candidate in candidates:
    print
    print 'Candidate:', candidate
    match = address.search(candidate)
    if match:
        print '  Match name :', match.group(1), match.group(4)
        print '  Match email:', match.group(5)
    else:
        print '  No match'

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

Таким образом могут быть использованы только 99 ссылок. Трехзначный номер будет интерпретирован как восьмеричное представление символа, а не ссылка на группу.

$ python re_refer_to_group.py


Candidate: First Last <first.last@example.com>
  Match name : First Last
  Match email: first.last@example.com

Candidate: Different Name <first.last@example.com>
  No match

Candidate: First Middle Last <first.last@example.com>
  Match name : First Last
  Match email: first.last@example.com

Candidate: First M. Last <first.last@example.com>
  Match name : First Last
  Match email: first.last@example.com

Обработчик регулярных выражений в Python использует синтаксис (?P=name), чтобы обратиться к значению именованной группы, для которой ранее было найдено совпадение.

import re

address = re.compile(
    '''

    # Обычное имя
    (?P<first_name>w+)
    s+
    (([w.]+)s+)?      # необязательное отчество или инициалы
    (?P<last_name>w+)

    s+

    <

    # Email адрес: first_name.last_name@domain.tld
    (?P<email>
      (?P=first_name)
      .
      (?P=last_name)
      @
      ([wd.]+.)+    # домен второго уровня
      (com|org|edu)    # ограничиваем допустимые домены верхнего уровня
    )

    >
    ''',
    re.UNICODE | re.VERBOSE | re.IGNORECASE)

candidates = [
    u'First Last <first.last@example.com>',
    u'Different Name <first.last@example.com>',
    u'First Middle Last <first.last@example.com>',
    u'First M. Last <first.last@example.com>',
    ]

for candidate in candidates:
    print
    print 'Candidate:', candidate
    match = address.search(candidate)
    if match:
        print '  Match name :', match.groupdict()['first_name'], match.groupdict()['last_name']
        print '  Match email:', match.groupdict()['email']
    else:
        print '  No match'

Регулярное выражение email-адреса компилируется с флагом IGNORECASE, поскольку имена начинаются с заглавной буквы, а email- адреса нет.

$ python re_refer_to_named_group.py


Candidate: First Last <first.last@example.com>
  Match name : First Last
  Match email: first.last@example.com

Candidate: Different Name <first.last@example.com>
  No match

Candidate: First Middle Last <first.last@example.com>
  Match name : First Last
  Match email: first.last@example.com

Candidate: First M. Last <first.last@example.com>
  Match name : First Last
  Match email: first.last@example.com

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

Синтаксис проверки группы на соответствие: (?(id)yes-expression|no-expression), где id – имя или номер группы, yes-expression – шаблон, используемый, если группа нашла соответствие, no-expression – шаблон, используемый, если соответствие не найдено.

import re

address = re.compile(
    '''
    ^

    # Имя состоит из букв и может включать "." в сокращениях 
    # титула или инициалах.
    (?P<name>
       ([w.]+s+)*[w.]+
     )?
    s*

    # Адреса email заключены в угловые скобки, но мы хотим искать
    # скобки, если нашли имя.
    (?(name)
      # остаток заключается в угловые скобки, поскольку мы нашли имя
      (?P<brackets>(?=(<.*>$)))
      |
      # остаток не заключается в угловые скобки, если нет имени
      (?=([^<].*[^>]$))
     )

    # Ищем угловую скобку, только если наш поиск вперёд 
    # нашёл обе скобки.
    (?(brackets)<|s*)

    # Сам адрес email: username@domain.tld
    (?P<email>
      [wd.+-]+       # имя пользователя
      @
      ([wd.]+.)+    # домен второго уровня
      (com|org|edu)    # ограничиваем допустимые домены верхнего уровня
     )

    # Ищем угловую скобку, только если наш поиск вперёд 
    # нашёл обе скобки.
    (?(brackets)>|s*)

    $
    ''',
    re.UNICODE | re.VERBOSE)

candidates = [
    u'First Last <first.last@example.com>',
    u'No Brackets first.last@example.com',
    u'Open Bracket <first.last@example.com',
    u'Close Bracket first.last@example.com>',
    u'no.brackets@example.com',
    ]

for candidate in candidates:
    print
    print 'Candidate:', candidate
    match = address.search(candidate)
    if match:
        print '  Match name :', match.groupdict()['name']
        print '  Match email:', match.groupdict()['email']
    else:
        print '  No match'

Эта версия обработчика email- адреса использует два теста. Если группа name находит соответствие, тогда начало поиска вперёд требует наличия обеих угловых скобок и использует группу brackets.

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

$ python re_id.py


Candidate: First Last <first.last@example.com>
  Match name : First Last
  Match email: first.last@example.com

Candidate: No Brackets first.last@example.com
  No match

Candidate: Open Bracket <first.last@example.com
  No match

Candidate: Close Bracket first.last@example.com>
  No match

Candidate: no.brackets@example.com
  Match name : None
  Match email: no.brackets@example.com

Изменение строк с помощью шаблонов

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

import re

bold = re.compile(r'*{2}(.*?)*{2}', re.UNICODE)

text = 'Make this **bold**.  This **too**.'

print 'Text:', text
print 'Bold:', bold.sub(r'<b>1</b>', text)

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

$ python re_sub.py

Text: Make this **bold**.  This **too**.
Bold: Make this <b>bold</b>.  This <b>too</b>.

Чтобы применять именованные группы для замен, используйте синтаксис g<name>.

import re

bold = re.compile(r'*{2}(?P<bold_text>.*?)*{2}', re.UNICODE)

text = 'Make this **bold**.  This **too**.'

print 'Text:', text
print 'Bold:', bold.sub(r'<b>g<bold_text></b>', text)

Синтаксис g<name> также работает с пронумерованными ссылками, и его использование исключает путаницу между номерами групп и окружающими их цифровыми символами.

$ python re_sub_named_groups.py

Text: Make this **bold**.  This **too**.
Bold: Make this <b>bold</b>.  This <b>too</b>.

Передача значения счёта позволяет ограничить количество производимых замен.

import re

bold = re.compile(r'*{2}(.*?)*{2}', re.UNICODE)

text = 'Make this **bold**.  This **too**.'

print 'Text:', text
print 'Bold:', bold.sub(r'<b>1</b>', text, count=1)

Сделана только одна замена, поскольку count равен 1.

$ python re_sub_count.py

Text: Make this **bold**.  This **too**.
Bold: Make this <b>bold</b>.  This **too**.

Метод subn() работает так же, как sub(), за исключением того, что он возвращает как изменённую строку, так и количество произведённых замен.

import re

bold = re.compile(r'*{2}(.*?)*{2}', re.UNICODE)

text = 'Make this **bold**.  This **too**.'

print 'Text:', text
print 'Bold:', bold.subn(r'<b>1</b>', text)

Поиск по шаблону нашёл два соответствия.

$ python re_subn.py

Text: Make this **bold**.  This **too**.
Bold: ('Make this <b>bold</b>.  This <b>too</b>.', 2)

Разбиение строк с помощью шаблонов

str.split() – один из часто используемых методов разбиения строк для их последующего разбора. Но он поддерживает только символьные значения в качестве разделителей. А иногда необходимо регулярное выражение, если исходный текст отформатирован неодинаково. Например, многие языки разметки текста определяют разделители параграфов как два (или более) символа новой строки (n). В этом случае str.split() не может быть использован.

Стратегия определения параграфов с помощью метода findall() использовала бы шаблон вроде (.+?)n{2,}.

import re

text = 'Paragraph onenon two lines.nnParagraph two.nnnParagraph three.'

for num, para in enumerate(re.findall(r'(.+?)n{2,}', text, flags=re.DOTALL)):
    print num, repr(para)
    print

Этот шаблон не смог бы найти параграф в конце текста, как показано в примере: “Paragraph three.” не является частью вывода.

$ python re_paragraphs_findall.py

0 'Paragraph onenon two lines.'

1 'Paragraph two.'

Расширение регулярного выражения так, чтобы он искал параграф с двумя (или более) символами новой строки, решает проблему, но усложняет шаблон. Использование re.split() вместо re.findall() справляется с проблемой разделения параграфов автоматически и позволяет ограничиться простым шаблоном.

import re

text = 'Paragraph onenon two lines.nnParagraph two.nnnParagraph three.'

print 'With findall:'
for num, para in enumerate(re.findall(r'(.+?)(n{2,}|$)', text, flags=re.DOTALL)):
    print num, repr(para)
    print

print
print 'With split:'
for num, para in enumerate(re.split(r'n{2,}', text)):
    print num, repr(para)
    print

Аргумент шаблона в методе split() более точно отражает спецификацию разметки: два (или более) символа новой строки обозначают разделение между параграфами исходного текста.

$ python re_split.py

With findall:
0 ('Paragraph onenon two lines.', 'nn')

1 ('Paragraph two.', 'nnn')

2 ('Paragraph three.', '')


With split:
0 'Paragraph onenon two lines.'

1 'Paragraph two.'

2 'Paragraph three.'

Заключение регулярного выражения в скобки, чтобы определить группу, заставляет метод split() работать как str.partition(). После этого он возвращает значения разделителей вместе с остальными частями строки.

import re

text = 'Paragraph onenon two lines.nnParagraph two.nnnParagraph three.'

print
print 'With split:'
for num, para in enumerate(re.split(r'(n{2,})', text)):
    print num, repr(para)
    print

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

$ python re_split_groups.py


With split:
0 'Paragraph onenon two lines.'

1 'nn'

2 'Paragraph two.'

3 'nnn'

4 'Paragraph three.'

Смотрите также

re – стандартная документация библиотеки модуля.

Regular Expression HOWTO – введение в регулярные выражения для Python-разработчиков от Эндрю Кучлинга.

Kodos – интерактивный инструмент тестирования регулярных выражений от Фила Шварца.

Википедия: регулярные выражения – введение и основные понятия, концепции и техники работы с регулярными выражениями.

locale – модуль для установки конфигурации при работе с текстом в кодировке Unicode.

Данная публикация представляет собой перевод статьи «re – Regular Expressions» , подготовленной дружной командой проекта Интернет-технологии.ру

Меню