Руководство по использованию регулярных выражений¶
- Автор:
А. М. Кюхлинг <amk@amk.ca>
Вступление¶
Регулярные выражения (называемые REs, или регулярными выражениями, или шаблонами регулярных выражений) - это, по сути, крошечный, узкоспециализированный язык программирования, встроенный в Python и доступный через модуль re
. Используя этот небольшой язык, вы задаете правила для набора возможных строк, которым вы хотите соответствовать; этот набор может содержать английские предложения, или адреса электронной почты, или команды TeX, или все, что вам нравится. Затем вы можете задать такие вопросы, как «Соответствует ли эта строка шаблону?» или «Есть ли совпадение с шаблоном где-нибудь в этой строке?». Вы также можете использовать REs для изменения строки или разделения ее на части различными способами.
Шаблоны регулярных выражений компилируются в серию байт-кодов, которые затем выполняются соответствующим механизмом, написанным на C. Для расширенного использования может потребоваться обратить пристальное внимание на то, как движок будет выполнять данный RE, и записать RE определенным образом, чтобы создать байт-код, который выполняется быстрее. Оптимизация не рассматривается в этом документе, поскольку она требует, чтобы вы хорошо разбирались во внутреннем устройстве соответствующего движка.
Язык регулярных выражений относительно невелик и ограничен, поэтому не все возможные задачи по обработке строк могут быть выполнены с помощью регулярных выражений. Есть также задачи, которые можно выполнить с помощью регулярных выражений, но выражения оказываются очень сложными. В этих случаях вам, возможно, лучше написать код на Python для выполнения обработки; хотя код на Python будет работать медленнее, чем сложное регулярное выражение, он также, вероятно, будет более понятным.
Простые узоры¶
Мы начнем с изучения простейших из возможных регулярных выражений. Поскольку регулярные выражения используются для работы со строками, мы начнем с наиболее распространенной задачи: сопоставления символов.
Для подробного объяснения основ информатики, лежащих в основе регулярных выражений (детерминированных и недетерминированных конечных автоматов), вы можете обратиться практически к любому учебнику по написанию компиляторов.
Совпадающие символы¶
Большинство букв и символов будут просто совпадать друг с другом. Например, регулярное выражение test
будет точно соответствовать строке test
. (Вы также можете включить режим без учета регистра, который позволит этому значению соответствовать Test
или TEST
; подробнее об этом позже.)
Из этого правила есть исключения; некоторые символы являются особыми metacharacters и не совпадают сами по себе. Вместо этого они сигнализируют о том, что следует сопоставить некоторые необычные элементы, или влияют на другие части текста, повторяясь или меняя их значение. Большая часть этого документа посвящена обсуждению различных метасимволов и того, что они делают.
Вот полный список метасимволов; их значения будут рассмотрены далее в этом руководстве.
. ^ $ * + ? { } [ ] \ | ( )
Первыми метасимволами, которые мы рассмотрим, являются [
и ]
. Они используются для указания класса символов, который представляет собой набор символов, которым вы хотите соответствовать. Символы могут быть перечислены по отдельности или можно указать диапазон символов, указав два символа и разделив их символом '-'
. Например, [abc]
будет соответствовать любому из символов a
, b
, или c
; это то же самое, что и [a-c]
, который использует диапазон для выражения того же набора символов. Если бы вы хотели использовать только строчные буквы, ваше значение было бы [a-z]
.
Метасимволы (кроме \
) не активны внутри классов. Например, [akm$]
будет соответствовать любому из символов 'a'
, 'k'
, 'm'
, или '$'
; '$'
обычно это метасимвол, но внутри класса символов он лишен своей особой природы.
Вы можете сопоставить символы, не перечисленные в классе, с помощью complementing набора. Для этого в качестве первого символа класса указывается '^'
. Например, [^5]
будет соответствовать любому символу, кроме '5'
. Если курсор появляется в другом месте класса символов, это не имеет особого значения. Например: [5^]
будет соответствовать либо '5'
, либо '^'
.
Возможно, наиболее важным метасимволом является обратная косая черта \
. Как и в строковых литералах Python, за обратной косой чертой могут следовать различные символы, обозначающие различные специальные последовательности. Он также используется для экранирования всех метасимволов, чтобы вы все еще могли сопоставлять их в шаблонах; например, если вам нужно сопоставить [
или \
, вы можете поставить перед ними обратную косую черту, чтобы убрать их особое значение: \[
или \\
.
Некоторые из специальных последовательностей, начинающихся с '\'
, представляют собой предопределенные наборы символов, которые часто бывают полезны, такие как набор цифр, набор букв или набор всего, что не является пробелом.
Давайте рассмотрим пример: \w
соответствует любому буквенно-цифровому символу. Если шаблон регулярного выражения выражен в байтах, это эквивалентно классу [a-zA-Z0-9_]
. Если шаблон регулярного выражения является строкой, \w
будет соответствовать всем символам, помеченным как буквы в базе данных Unicode, предоставляемой модулем unicodedata
. Вы можете использовать более ограниченное определение \w
в строковом шаблоне, указав флаг re.ASCII
при компиляции регулярного выражения.
Следующий список специальных последовательностей неполон. Полный список последовательностей и расширенные определения классов для строковых шаблонов Unicode приведены в последней части Regular Expression Syntax в справочнике по стандартной библиотеке. Как правило, версии Unicode соответствуют любому символу, который находится в соответствующей категории в базе данных Unicode.
\d
Соответствует любой десятичной цифре; это эквивалентно классу
[0-9]
.\D
Соответствует любому нецифровому символу; это эквивалентно классу
[^0-9]
.\s
Соответствует любому символу пробела; это эквивалентно классу
[ \t\n\r\f\v]
.\S
Соответствует любому символу, не содержащему пробелов; это эквивалентно классу
[^ \t\n\r\f\v]
.\w
Соответствует любому буквенно-цифровому символу; это эквивалентно классу
[a-zA-Z0-9_]
.\W
Соответствует любому неалфавитно-цифровому символу; это эквивалентно классу
[^a-zA-Z0-9_]
.
Эти последовательности могут быть включены в символьный класс. Например, [\s,.]
- это символьный класс, который будет соответствовать любому символу пробела, или ','
, или '.'
.
Последний метасимвол в этом разделе - .
. Он соответствует всему, кроме символа новой строки, и есть альтернативный режим (re.DOTALL
), где он будет соответствовать даже символу новой строки. .
часто используется там, где вы хотите сопоставить «любой символ».
Повторяющиеся вещи¶
Возможность сопоставления различных наборов символов - это первое, что могут делать регулярные выражения, что уже невозможно с помощью методов, доступных для строк. Однако, если бы это была единственная дополнительная возможность регулярных выражений, это не было бы большим достижением. Другая возможность заключается в том, что вы можете указать, что части текста должны повторяться определенное количество раз.
Первый метасимвол для повторения, который мы рассмотрим, - это *
. *
не соответствует буквальному символу '*'
; вместо этого он указывает, что предыдущему символу можно соответствовать ноль или более раз, а не ровно один раз.
Например, ca*t
будет соответствовать 'ct'
(0 'a'
символам), 'cat'
(1 'a'
), 'caaat'
(3 'a'
символов) и так далее.
Такие повторы, как *
, являются greedy; при повторении aRE механизм сопоставления попытается повторить это как можно больше раз. Если последующие части шаблона не совпадают, поисковый механизм выполнит резервное копирование и повторную попытку с меньшим количеством повторений.
Пошаговый пример сделает это более очевидным. Давайте рассмотрим выражение a[bcd]*b
. Это соответствует букве 'a'
, нулю или более буквам из класса [bcd]
и, наконец, заканчивается на 'b'
. Теперь представьте, что это число соответствует строке 'abcbd'
.
Шаг |
Соответствие |
Объяснение |
---|---|---|
1 |
|
|
2 |
|
Движок сопоставляет |
3 |
Неудача |
Движок пытается найти соответствие |
4 |
|
Сделайте резервную копию, чтобы |
5 |
Неудача |
Попробуйте |
6 |
|
Создайте резервную копию еще раз, чтобы |
6 |
|
Попробуйте еще раз |
Теперь достигнут конец запроса, и он соответствует 'abcb'
. Это показывает, как сначала поисковый механизм выполняет все возможные действия, а если совпадений не найдено, он постепенно выполняет резервное копирование и повторяет оставшуюся часть запроса снова и снова. Он будет выполнять резервное копирование до тех пор, пока не найдет нулевые совпадения для [bcd]*
, и если впоследствии это не удастся, движок сделает вывод, что строка вообще не соответствует RE.
Другим повторяющимся метасимволом является +
, который совпадает один или несколько раз. Обратите особое внимание на разницу между *
и +
; *
совпадает с нулем или более раз, поэтому все, что повторяется, может вообще отсутствовать, в то время как для +
требуется по крайней мере одно* повторение. Чтобы использовать аналогичный пример, ca+t
будет соответствовать 'cat'
(1 'a'
), 'caaat'
(3 'a'
s), но не будет соответствовать 'ct'
.
Есть еще два повторяющихся оператора или квантора. Символ вопросительного знака ?
совпадает либо один раз, либо ноль раз; вы можете считать, что он помечает что-то как необязательное. Например, home-?brew
соответствует либо 'homebrew'
, либо 'home-brew'
.
Наиболее сложным квантором является {m,n}
, где m и n - десятичные целые числа. Этот квантор означает, что должно быть не менее m повторений и не более n. Например, a/{1,3}b
будет соответствовать 'a/b'
, 'a//b'
, и 'a///b'
. Он не будет соответствовать 'ab'
, у которого нет косых черт, или 'a////b'
, у которого их четыре.
Вы можете опустить либо m, либо n; в этом случае для пропущенного значения принимается разумное значение. Пропуск m интерпретируется как нижний предел, равный 0, в то время как пропуск n приводит к верхней границе, равной бесконечности.
Простейший случай {m}
соответствует предыдущему элементу ровно m раз. Например, a/{2}b
будет соответствовать только 'a//b'
.
Читатели, склонные к редукционизму, могут заметить, что все три других квантора могут быть выражены с помощью этого обозначения. {0,}
совпадает с *
, {1,}
эквивалентно +
, а {0,1}
совпадает с ?
. По возможности лучше использовать *
, +
, или ?
просто потому, что они короче и их легче читать.
Использование регулярных выражений¶
Теперь, когда мы рассмотрели несколько простых регулярных выражений, как мы на самом деле используем их в Python? Модуль re
предоставляет интерфейс для движка регулярных выражений, позволяющий вам компилировать REs в объекты и затем выполнять сопоставления с ними.
Компиляция регулярных выражений¶
Регулярные выражения компилируются в объекты-шаблоны, которые содержат методы для различных операций, таких как поиск совпадений с шаблонами или выполнение подстановок строк.
>>> import re
>>> p = re.compile('ab*')
>>> p
re.compile('ab*')
re.compile()
также принимает необязательный аргумент flags, используемый для включения различных специальных функций и вариаций синтаксиса. Мы рассмотрим доступные настройки позже, а пока достаточно одного примера:
>>> p = re.compile('ab*', re.IGNORECASE)
Значение re.compile()
передается в виде строки. Значения обрабатываются как строки, поскольку регулярные выражения не являются частью основного языка Python, и для их выражения не был создан специальный синтаксис. (Есть приложения, которые вообще не нуждаются в REs, поэтому нет необходимости раздувать спецификацию языка, включая их.) Вместо этого модуль re
- это просто модуль расширения C, включенный в Python, точно так же, как socket
или zlib
модули.
Использование строк REsin упрощает язык Python, но имеет один недостаток, который будет рассмотрен в следующем разделе.
Эпидемия обратной косой черты¶
Как указывалось ранее, в регулярных выражениях используется символ обратной косой черты ('\'
) для обозначения специальных форм или для того, чтобы разрешить использование специальных символов без указания их специального значения. Это противоречит использованию того же символа в Python для тех же целей в строковых литералах.
Допустим, вы хотите написать RE, который соответствует строке \section
, которая может быть найдена в файле LaTeX. Чтобы понять, что писать в программном коде, начните с нужной строки, которую нужно сопоставить. Затем вы должны экранировать все обратные косые черты и другие метасимволы, добавляя перед ними обратную косую черту, в результате чего получится строка \\section
. Результирующая строка, которая должна быть передана в re.compile()
, должна быть \\section
. Однако, чтобы выразить это в виде строкового литерала Python, обе обратные косые черты должны быть экранированы * еще раз*.
Персонажи |
Этап |
---|---|
|
Текстовая строка, которая должна быть сопоставлена |
|
Экранированная обратная косая черта для |
|
Экранированные обратные косые черты для строкового литерала |
Короче говоря, чтобы соответствовать буквальному обратному слешу, нужно записать '\\\\'
в качестве строки RE, потому что регулярное выражение должно быть \\
, и каждый обратный слеш должен быть выражен как \\
внутри обычного строкового литерала Python. В RES, в которых многократно используются обратные косые черты, это приводит к большому количеству повторяющихся обратных косых черт и затрудняет понимание результирующих строк.
Решение состоит в том, чтобы использовать необработанную строковую нотацию Python для регулярных выражений; обратные косые черты не обрабатываются каким-либо особым образом в строковом литерале с префиксом 'r'
, поэтому r"\n"
- это двухсимвольная строка, содержащая '\'
и 'n'
, в то время как "\n"
- это односимвольная строка, содержащая символ новой строки. Регулярные выражения часто записываются в коде Python с использованием этой необработанной строковой нотации.
Кроме того, специальные управляющие последовательности, которые допустимы в регулярных выражениях, но недопустимы в качестве строковых литералов Python, теперь приводят к DeprecationWarning
и в конечном итоге станут SyntaxError
, что означает, что последовательности будут недействительными, если использовать необработанную строковую запись или экранировать обратную косую черту не используется.
Обычная строка |
Необработанная строка |
---|---|
|
|
|
|
|
|
Проведение матчей¶
Как только у вас появляется объект, представляющий скомпилированное регулярное выражение, что вы с ним делаете? Объекты-шаблоны имеют несколько методов и атрибутов. Здесь будут рассмотрены только наиболее важные из них; полный список приведен в документации re
.
Метод/атрибут |
Цель |
---|---|
|
Определите, совпадают ли они в начале строки. |
|
Сканируйте строку в поисках любого места, где это значение совпадает. |
|
Находит все подстроки, которые там совпадают, и возвращает их в виде списка. |
|
Найдите все подстроки, где они совпадают, и верните их в виде iterator. |
match()
и search()
возвращают None
, если совпадение не найдено. Если они завершаются успешно, возвращается экземпляр match object, содержащий информацию о совпадении: где оно начинается и заканчивается, подстрока, которой оно соответствует, и многое другое.
Вы можете узнать об этом, поэкспериментировав в интерактивном режиме с модулем re
. Если у вас есть tkinter
, вы также можете ознакомиться с Tools/demo/redemo.py, демонстрационной программой, входящей в дистрибутив Python. Он позволяет вводить Rest и строки и отображает, соответствует ли RE или нет. redemo.py
может быть весьма полезен при попытке отладки сложного RE.
В этом руководстве для примеров используется стандартный интерпретатор Python. Сначала запустите интерпретатор Python, импортируйте модуль re
и скомпилируйте повторный:
>>> import re
>>> p = re.compile('[a-z]+')
>>> p
re.compile('[a-z]+')
Теперь вы можете попробовать сопоставить различные строки с RE [a-z]+
. Пустая строка вообще не должна совпадать, поскольку +
означает «одно или несколько повторений». В этом случае match()
должен возвращать None
, что приведет к тому, что интерпретатор не выведет никаких выходных данных. Вы можете явно вывести результат match()
, чтобы сделать это понятным.
>>> p.match("")
>>> print(p.match(""))
None
Теперь давайте попробуем применить это к строке, которой оно должно соответствовать, например, к tempo
. В этом случае match()
вернет match object, поэтому вам следует сохранить результат в переменной для последующего использования.
>>> m = p.match('tempo')
>>> m
<re.Match object; span=(0, 5), match='tempo'>
Теперь вы можете запросить у match object информацию о соответствующей строке. Экземпляры объектов Match также имеют несколько методов и атрибутов; наиболее важными из них являются:
Метод/атрибут |
Цель |
---|---|
|
Возвращает строку, соответствующую параметру RE |
|
Верните исходное положение для матча |
|
Возвращает конечную позицию совпадения |
|
Возвращает кортеж, содержащий (начальную, конечную) позиции совпадения |
Попробовав эти методы, вы вскоре проясните их значение:
>>> m.group()
'tempo'
>>> m.start(), m.end()
(0, 5)
>>> m.span()
(0, 5)
group()
возвращает подстроку, с которой был сопоставлен RE. start()
и end()
возвращают начальный и конечный индексы соответствия. span()
возвращает как начальный, так и конечный индексы в одном кортеже. Поскольку метод match()
проверяет только соответствие RE в начале строки, start()
всегда будет равно нулю. Однако метод шаблонов search()
сканирует строку, поэтому в этом случае совпадение может начинаться не с нуля.
>>> print(p.match('::: message'))
None
>>> m = p.search('::: message'); print(m)
<re.Match object; span=(4, 11), match='message'>
>>> m.group()
'message'
>>> m.span()
(4, 11)
В реальных программах наиболее распространенным способом является сохранение match object в переменной, а затем проверка, было ли это None
. Обычно это выглядит так:
p = re.compile( ... )
m = p.match( 'string goes here' )
if m:
print('Match found: ', m.group())
else:
print('No match')
Два метода pattern возвращают все совпадения для шаблона. findall()
возвращает список совпадающих строк:
>>> p = re.compile(r'\d+')
>>> p.findall('12 drummers drumming, 11 pipers piping, 10 lords a-leaping')
['12', '11', '10']
Префикс r
, делающий литерал необработанным строковым литералом, необходим в этом примере, потому что escape-последовательности в обычном «обработанном» строковом литерале, которые не распознаются Python, в отличие от регулярных выражений, теперь приводят к DeprecationWarning
и будут в конце концов, станьте SyntaxError
. См. Эпидемия обратной косой черты.
findall()
должен быть создан весь список, прежде чем он может быть возвращен в качестве результата. Метод finditer()
возвращает последовательность из match object экземпляров в виде iterator:
>>> iterator = p.finditer('12 drummers drumming, 11 ... 10 ...')
>>> iterator
<callable_iterator object at 0x...>
>>> for match in iterator:
... print(match.span())
...
(0, 2)
(22, 24)
(29, 31)
Функции на уровне модуля¶
Вам не нужно создавать объект pattern и вызывать его методы; модуль re
также предоставляет функции верхнего уровня, называемые match()
, search()
, findall()
, sub()
, и так далее. Эти функции принимают те же аргументы, что и соответствующий метод pattern, с добавлением строки RE в качестве первого аргумента, и по-прежнему возвращают либо None
, либо экземпляр match object.
>>> print(re.match(r'From\s+', 'Fromage amk'))
None
>>> re.match(r'From\s+', 'From amk Thu May 14 19:12:10 1998')
<re.Match object; span=(0, 5), match='From '>
По сути, эти функции просто создают для вас объект pattern и вызывают для него соответствующий метод. Они также сохраняют скомпилированный объект в кэше, поэтому будущим вызовам с использованием того же RE не нужно будет разбирать шаблон снова и снова.
Следует ли вам использовать эти функции на уровне модуля или вам следует получить шаблон и вызывать его методы самостоятельно? Если вы обращаетесь к регулярному выражению в цикле, его предварительная компиляция сэкономит несколько вызовов функций. За пределами циклов, благодаря внутреннему кэшу, разница невелика.
Флаги компиляции¶
Флаги компиляции позволяют изменять некоторые аспекты работы регулярных выражений. Флаги доступны в модуле re
под двумя именами: длинным, например IGNORECASE
, и коротким, состоящим из одной буквы, например I
. (Если вы знакомы с модификаторами шаблонов Perl, то в однобуквенных формах используются одни и те же буквы; например, краткая форма re.VERBOSE
- это re.X
.) Можно задать несколько флагов путем их побитового преобразования; re.I | re.M
устанавливает, например, флаги I
и M
.
Вот таблица доступных флагов, за которой следует более подробное описание каждого из них.
Флаг |
Значение |
---|---|
Создает несколько экранирующих символов типа |
|
Сделайте так, чтобы |
|
Выполняйте сопоставления без учета регистра. |
|
Выполните сопоставление с учетом локали. |
|
Многострочное сопоставление, затрагивающее |
|
Включите подробные инструкции, которые могут быть организованы более четко и понятнее. |
- re.I
- re.IGNORECASE
Выполняйте сопоставление без учета регистра; символьный класс и литеральные строки будут соответствовать буквам, игнорируя регистр. Например,
[A-Z]
также будет соответствовать строчным буквам. Полное соответствие Юникоду также работает, если только не используется флагASCII
для отключения совпадений, отличных от ASCII. Когда шаблоны Юникода[a-z]
или[A-Z]
используются в сочетании с флагомIGNORECASE
, они будут соответствовать 52 буквам ASCII и 4 дополнительным буквам, отличным от ASCII: „I“ (U+0130, латинская заглавная буква I с точкой вверху), «i» (U+0131, латинская строчная буква без точки i), «ſ» (U+017F, латинская строчная буква без точки s) и «K» (U+212A, знак Кельвина).Spam
будет соответствовать'Spam'
,'spam'
,'spAM'
, или'ſpam'
(последнее соответствует только в режиме Unicode). Этот нижний регистр не учитывает текущую локаль; он будет учитываться, если вы также установите флагLOCALE
.
- re.L
- re.LOCALE
Сделайте соответствие
\w
,\W
,\b
,\B
и без учета регистра зависимым от текущего языкового стандарта, а не от базы данных Unicode.Локали - это функция библиотеки C, предназначенная для облегчения написания программ, учитывающих языковые различия. Например, если вы обрабатываете закодированный французский текст, вы хотели бы иметь возможность писать
\w+
, чтобы соответствовать словам, но\w
соответствует только классу символов[A-Za-z]
в шаблонах байтов; он не будет соответствовать соответствующим байтам. кé
илиç
. Если ваша система настроена правильно и выбран французский язык, определенные функции языка Си сообщат программе, что байт, соответствующийé
, также следует считать буквой. Установка флагаLOCALE
при компиляции регулярного выражения приведет к тому, что результирующий скомпилированный объект будет использовать эти функции C для\w
; это медленнее, но также позволяет\w+
сопоставлять французские слова, как вы и ожидали. Использование этого флага в Python 3 не рекомендуется, поскольку механизм определения языка очень ненадежен, он обрабатывает только одну «культуру» за раз и работает только с 8-разрядными языками. Сопоставление с Юникодом уже включено по умолчанию в Python 3 для шаблонов Unicode (str), и оно способно обрабатывать различные локали / языки.
- re.M
- re.MULTILINE
(
^
и$
еще не были объяснены; они будут представлены в разделе Больше метасимволов.)Обычно
^
совпадает только в начале строки, а$
совпадает только в конце строки и непосредственно перед новой строкой (если таковая имеется) в конце строки. Когда этот флаг установлен,^
совпадает в начале строки и в начале каждой строки внутри строки, непосредственно после каждой новой строки. Аналогично, метасимвол$
встречается либо в конце строки, либо в конце каждой строки (непосредственно перед каждой новой строкой).
- re.S
- re.DOTALL
Позволяет специальному символу
'.'
соответствовать любому символу вообще, включая перевод строки; без этого флага'.'
будет соответствовать чему угодно, кроме перевода строки.
- re.A
- re.ASCII
Сделайте так, чтобы
\w
,\W
,\b
,\B
,\s
и\S
выполняли сопоставление только с ASCII вместо полного сопоставления с Unicode. Это имеет смысл только для шаблонов Unicode и игнорируется для байтовых шаблонов.
- re.X
- re.VERBOSE
Этот флаг позволяет вам писать регулярные выражения, которые более удобочитаемы, предоставляя вам большую гибкость в их форматировании. Когда этот флаг установлен, пробелы в строке RE игнорируются, за исключением случаев, когда они относятся к классу символов или перед ними стоит неэкранированная обратная косая черта; это позволяет более четко организовать RE и сделать отступы. Этот флаг также позволяет помещать комментарии в RE, которые будут проигнорированы движком; комментарии помечаются символом
'#'
, который не относится ни к классу символов, ни перед которым не ставится обратная косая черта.Например, вот RE, который использует
re.VERBOSE
; видите, насколько легче его читать?charref = re.compile(r""" &[#] # Start of a numeric entity reference ( 0[0-7]+ # Octal form | [0-9]+ # Decimal form | x[0-9a-fA-F]+ # Hexadecimal form ) ; # Trailing semicolon """, re.VERBOSE)
Без подробной настройки это выглядело бы примерно так:
charref = re.compile("&#(0[0-7]+" "|[0-9]+" "|x[0-9a-fA-F]+);")
В приведенном выше примере автоматическая конкатенация строковых литералов в Python была использована для разбиения RE на более мелкие фрагменты, но это все равно сложнее для понимания, чем версия, использующая
re.VERBOSE
.
Больше силы паттерна¶
Пока что мы рассмотрели только часть возможностей регулярных выражений. В этом разделе мы рассмотрим некоторые новые метасимволы и то, как использовать группы для извлечения фрагментов текста, которые были сопоставлены.
Больше метасимволов¶
Есть несколько метасимволов, которые мы еще не рассмотрели. Большинство из них будут рассмотрены в этом разделе.
Некоторые из оставшихся метасимволов, которые следует обсудить, - это zero-width assertions. Они не заставляют движок продвигаться по строке; вместо этого они вообще не используют символы и просто завершаются успешно или с ошибкой. Например, \b
- это утверждение о том, что текущая позиция находится на границе слова; позиция вообще не изменяется с помощью \b
. Это означает, что утверждения нулевой ширины никогда не должны повторяться, потому что, если они совпадают один раз в заданном месте, очевидно, что они могут совпадать бесконечное количество раз.
|
Чередование или оператор «или». Если A и B являются регулярными выражениями,
A|B
будет соответствовать любой строке, которая соответствует либо A, либо B.|
имеет очень низкий приоритет, чтобы обеспечить разумную работу при чередовании многосимвольных строк.Crow|Servo
будет соответствовать либо'Crow'
, либо'Servo'
, но не'Cro'
, а'w'
или'S'
и'ervo'
.Чтобы сопоставить литерал
'|'
, используйте\|
или заключите его в символьный класс, как в[|]
.^
Совпадения в начале строк. Если не установлен флаг
MULTILINE
, это значение будет совпадать только в начале строки. В режимеMULTILINE
это значение также совпадает сразу после каждой новой строки в строке.Например, если вы хотите сопоставить слово
From
только в начале строки, то следует использовать^From
.>>> print(re.search('^From', 'From Here to Eternity')) <re.Match object; span=(0, 4), match='From'> >>> print(re.search('^From', 'Reciting From Memory')) None
Чтобы сопоставить литерал
'^'
, используйте\^
.$
Совпадает с концом строки, который определяется либо как конец строки, либо как любое место, за которым следует символ новой строки.
>>> print(re.search('}$', '{block}')) <re.Match object; span=(6, 7), match='}'> >>> print(re.search('}$', '{block} ')) None >>> print(re.search('}$', '{block}\n')) <re.Match object; span=(6, 7), match='}'>
Чтобы сопоставить литерал
'$'
, используйте\$
или заключите его в символьный класс, как в[$]
.\A
Совпадает только в начале строки. Когда не используется режим
MULTILINE
,\A
и^
фактически совпадают. В режимеMULTILINE
они отличаются:\A
по-прежнему совпадает только в начале строки, но^
может совпадать в любом месте внутри строки, которое следует за символом новой строки.\Z
Совпадает только в конце строки.
\b
Граница слова. Это утверждение нулевой ширины, которое совпадает только в начале или конце слова. Слово определяется как последовательность буквенно-цифровых символов, поэтому конец слова обозначается пробелом или не буквенно-цифровым символом.
Следующий пример соответствует
class
только тогда, когда это полное слово; оно не будет соответствовать, если содержится внутри другого слова.>>> p = re.compile(r'\bclass\b') >>> print(p.search('no class at all')) <re.Match object; span=(3, 8), match='class'> >>> print(p.search('the declassified algorithm')) None >>> print(p.search('one subclass is')) None
При использовании этой специальной последовательности вам следует помнить о двух тонкостях. Во-первых, это наихудшая коллизия между строковыми литералами Python и последовательностями регулярных выражений. В строковых литералах Python
\b
- это символ обратного пробела, значение ASCII 8. Если вы не используете необработанные строки, то Python преобразует\b
в обратное пространство, и ваш RE не будет соответствовать ожидаемому. Следующий пример выглядит так же, как и наш предыдущий, но в нем отсутствует'r'
перед восстановлением.>>> p = re.compile('\bclass\b') >>> print(p.search('no class at all')) None >>> print(p.search('\b' + 'class' + '\b')) <re.Match object; span=(0, 7), match='\x08class\x08'>
Во-вторых, внутри класса character, где это утверждение бесполезно,
\b
представляет символ обратного пробела для совместимости со строковыми литералами Python.\B
Другое утверждение нулевой ширины, противоположное
\b
, совпадает только тогда, когда текущая позиция не находится на границе слова.
Группировка¶
Часто вам нужно получить больше информации, чем просто о том, совпадают они или нет. Регулярные выражения часто используются для разбиения строк на несколько подгрупп, которые соответствуют различным интересующим компонентам. Например, строка заголовка RFC-822 делится на имя заголовка и значение, разделенные символом ':'
, вот так:
From: author@example.com
User-Agent: Thunderbird 1.5.0.9 (X11/20061227)
MIME-Version: 1.0
To: editor@example.com
Это можно решить, написав регулярное выражение, которое соответствует всей строке заголовка и содержит одну группу, соответствующую имени заголовка, и другую группу, соответствующую значению заголовка.
Группы обозначаются метасимволами '('
, ')'
. '('
и ')'
имеют практически то же значение, что и в математических выражениях; они группируют содержащиеся в них выражения, и вы можете может повторять содержимое группы с помощью квантификатора, такого как *
, +
, ?
, или {m,n}
. Например, (ab)*
будет соответствовать нулю или более повторений ab
.
>>> p = re.compile('(ab)*')
>>> print(p.match('ababababab').span())
(0, 10)
Группы, обозначенные символом '('
, ')'
, также содержат начальный и конечный индекс текста, которому они соответствуют; это можно получить, передав аргумент в виде group()
, start()
, end()
и span()
. Группы нумеруются, начиная с 0. Группа 0 присутствует всегда; это все методы RE, поэтому все методы match object имеют группу 0 в качестве аргумента по умолчанию. Позже мы увидим, как выражать группы, которые не отображают объем текста, которому они соответствуют.
>>> p = re.compile('(a)b')
>>> m = p.match('ab')
>>> m.group()
'ab'
>>> m.group(0)
'ab'
Подгруппы нумеруются слева направо, начиная от 1 и выше. Группы могут быть вложенными; чтобы определить их количество, просто подсчитайте количество символов в круглых скобках, начиная слева направо.
>>> p = re.compile('(a(b)c)d')
>>> m = p.match('abcd')
>>> m.group(0)
'abcd'
>>> m.group(1)
'abc'
>>> m.group(2)
'b'
group()
может быть передано несколько номеров групп одновременно, и в этом случае он вернет кортеж, содержащий соответствующие значения для этих групп.
>>> m.group(2,1,2)
('b', 'abc', 'b')
Метод groups()
возвращает кортеж, содержащий строки для всех подгрупп, от 1 до любого их количества.
>>> m.groups()
('abc', 'b')
Обратные ссылки в шаблоне позволяют указать, что содержимое более ранней группы захвата также должно быть найдено в текущем местоположении в строке. Например, \1
будет выполнено успешно, если в текущей позиции можно найти точное содержимое группы 1, и завершится неудачей в противном случае. Помните, что строковые литералы Python также используют обратную косую черту, за которой следуют цифры, чтобы разрешить включение произвольных символов в строку, поэтому обязательно используйте необработанную строку при включении обратных ссылок в RE.
Например, следующий метод повторно обнаруживает удвоенные слова в строке.
>>> p = re.compile(r'\b(\w+)\s+\1\b')
>>> p.search('Paris in the the spring').group()
'the the'
Обратные ссылки, подобные этой, не часто полезны для простого поиска по строке - существует несколько текстовых форматов, которые повторяют данные таким образом, - но вы скоро обнаружите, что они очень полезны при выполнении подстановки строк.
Не захватываемые и именованные группы¶
В сложных RES может использоваться множество групп, как для захвата интересующих подстрок, так и для их группировки и структурирования. В сложных REs становится трудно отслеживать номера групп. Есть две особенности, которые помогают решить эту проблему. Оба они используют общий синтаксис для расширений регулярных выражений, поэтому сначала мы рассмотрим его.
Perl 5 хорошо известен своими мощными дополнениями к стандартным регулярным выражениям. Для этих новых функций разработчики Perl не могли выбрать новые метасимволы для однократного нажатия клавиши или новые специальные последовательности, начинающиеся с \
, без того, чтобы регулярные выражения Perl не отличались от стандартных REs до степени смешения. Например, если бы они выбрали &
в качестве нового метасимвола, старые выражения предполагали бы, что &
является обычным символом и не экранировали бы его, записывая \&
или [&]
.
Решением, выбранным разработчиками Perl, было использование (?...)
в качестве синтаксиса расширения. ?
сразу после круглой скобки была синтаксическая ошибка, потому что ?
нечего было бы повторять, так что это не создавало никаких проблем с совместимостью. Символы, следующие сразу за ?
, указывают, какое расширение используется, поэтому (?=foo)
- это одно (положительное предварительное утверждение), а (?:foo)
- это что-то другое (группа, не включающая в себя подвыражение foo
).
Python поддерживает несколько расширений Perl и добавляет синтаксис расширений к синтаксису расширений Perl. Если первым символом после знака вопроса является P
, вы знаете, что это расширение, специфичное для Python.
Теперь, когда мы рассмотрели общий синтаксис расширения, мы можем вернуться к функциям, которые упрощают работу с группами в сложных исследованиях.
Иногда вы захотите использовать группу для обозначения части регулярного выражения, но не заинтересованы в получении содержимого группы. Вы можете сделать этот факт явным, используя группу без захвата: (?:...)
, где вы можете заменить ...
любым другим регулярным выражением.
>>> m = re.match("([abc])+", "abc")
>>> m.groups()
('c',)
>>> m = re.match("(?:[abc])+", "abc")
>>> m.groups()
()
За исключением того факта, что вы не можете получить содержимое того, что соответствует группе, группа без захвата ведет себя точно так же, как группа захвата; вы можете поместить в нее что угодно, повторить это с помощью метасимвола повторения, такого как *
, и вложить это в другие группы. (захватывать или не захватывать). (?:...)
особенно полезен при изменении существующего шаблона, поскольку вы можете добавлять новые группы, не меняя порядок нумерации всех остальных групп. Следует отметить, что нет разницы в производительности поиска между группами, которые захватывают и не захватывают данные.; ни одна из форм не работает быстрее другой.
Более важной особенностью являются именованные группы: вместо того, чтобы ссылаться на них по номерам, на группы можно ссылаться по имени.
Синтаксис именованной группы является одним из специфичных для Python расширений: (?P<name>...)
. name - это, очевидно, название группы. Именованные группы ведут себя точно так же, как группы захвата, и дополнительно связывают имя с группой. Все методы match object, которые работают с получением групп, принимают либо целые числа, которые ссылаются на группу по номеру, либо строки, содержащие название нужной группы. Именованным группам по-прежнему присваиваются номера, поэтому вы можете получить информацию о группе двумя способами:
>>> p = re.compile(r'(?P<word>\b\w+\b)')
>>> m = p.search( '(((( Lots of punctuation )))' )
>>> m.group('word')
'Lots'
>>> m.group(1)
'Lots'
Кроме того, вы можете извлекать именованные группы в виде словаря с помощью groupdict()
:
>>> m = re.match(r'(?P<first>\w+) (?P<last>\w+)', 'Jane Doe')
>>> m.groupdict()
{'first': 'Jane', 'last': 'Doe'}
Именованные группы удобны тем, что позволяют использовать легко запоминающиеся имена вместо того, чтобы запоминать номера. Вот пример из модуля imaplib
:
InternalDate = re.compile(r'INTERNALDATE "'
r'(?P<day>[ 123][0-9])-(?P<mon>[A-Z][a-z][a-z])-'
r'(?P<year>[0-9][0-9][0-9][0-9])'
r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
r'"')
Очевидно, что гораздо проще получить m.group('zonem')
, вместо того чтобы помнить о необходимости получения группы 9.
Синтаксис обратных ссылок в таком выражении, как (...)\1
, указывает на номер группы. Естественно, существует вариант, в котором вместо номера используется название группы. Это еще одно расширение Python: (?P=name)
указывает, что содержимое группы с именем name должно быть снова сопоставлено в текущей точке. Регулярное выражение для поиска удвоенных слов \b(\w+)\s+\1\b
также может быть записано как \b(?P<word>\w+)\s+(?P=word)\b
:
>>> p = re.compile(r'\b(?P<word>\w+)\s+(?P=word)\b')
>>> p.search('Paris in the the spring').group()
'the the'
Предварительные утверждения¶
Другим утверждением нулевой ширины является предварительное утверждение. Предварительные утверждения доступны как в положительной, так и в отрицательной форме и выглядят следующим образом:
(?=...)
Положительное предварительное утверждение. Это выполняется успешно, если содержащееся в нем регулярное выражение, представленное здесь как
...
, успешно соответствует текущему местоположению, и в противном случае завершается ошибкой. Но после того, как было опробовано содержащееся в нем выражение, механизм сопоставления вообще не продвигается вперед; остальная часть шаблона опробована прямо с того места, где начиналось утверждение.(?!...)
Отрицательное предварительное утверждение. Это противоположно положительному утверждению; оно выполняется успешно, если содержащееся в нем выражение *не соответствует текущей позиции в строке.
Чтобы конкретизировать это, давайте рассмотрим случай, когда полезно предварительное описание. Рассмотрим простой шаблон для сопоставления имени файла и разделим его на базовое имя и расширение, разделенные символом .
. Например, в news.rc
, news
- это базовое имя, а rc
- расширение имени файла.
Схема, соответствующая этому, довольно проста:
.*[.].*$
Обратите внимание, что .
требует особой обработки, поскольку это метасимвол, поэтому он находится внутри класса символов и соответствует только этому конкретному символу. Также обратите внимание на завершающий символ $
; это добавлено для того, чтобы гарантировать, что вся остальная часть строки должна быть включена в расширение. Это регулярное выражение соответствует foo.bar
и autoexec.bat
, а также sendmail.cf
и printers.conf
.
Теперь давайте немного усложним задачу; что, если вы хотите сопоставить имена файлов, расширение которых отличается от bat
? Несколько неверных попыток:
.*[.][^b].*$
В приведенной выше первой попытке делается попытка исключить bat
, требуя, чтобы первый символ расширения не был b
. Это неверно, поскольку шаблон также не соответствует foo.bar
.
.*[.]([^b]..|.[^a].|..[^t])$
Выражение становится еще более запутанным, когда вы пытаетесь исправить первое решение, требуя соответствия одному из следующих вариантов: первый символ расширения не является b
; второй символ не является a
; или третий символ не является t
. Это принимает foo.bar
и отклоняет autoexec.bat
, но требует трехбуквенного расширения и не принимает имя файла с двухбуквенным расширением, таким как sendmail.cf
. Мы снова усложним схему, пытаясь ее исправить.
.*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$
При третьей попытке вторая и третья буквы становятся необязательными, чтобы разрешить сопоставление расширений короче трех символов, таких как sendmail.cf
.
Шаблон становится все более сложным, что затрудняет его чтение и понимание. Что еще хуже, если проблема изменится и вы захотите исключить как bat
, так и exe
в качестве расширений, шаблон станет еще более сложным и запутанным.
Негативный взгляд на вещи преодолевает всю эту неразбериху:
.*[.](?!bat$)[^.]*$
Отрицательный предварительный просмотр означает: если выражение bat
не соответствует на данном этапе, попробуйте остальную часть шаблона; если bat$
соответствует, весь шаблон не будет выполнен. Завершающий символ $
необходим для обеспечения того, чтобы было разрешено что-то вроде sample.batch
, где расширение начинается только с bat
. [^.]*
гарантирует, что шаблон работает при наличии нескольких точек в имени файла.
Исключить другое расширение имени файла теперь легко; просто добавьте его в качестве альтернативы в утверждение. Следующий шаблон исключает имена файлов, которые заканчиваются либо на bat
, либо на exe
:
.*[.](?!bat$|exe$)[^.]*$
Изменение строк¶
До этого момента мы просто выполняли поиск по статической строке. Регулярные выражения также широко используются для изменения строк различными способами, используя следующие шаблонные методы:
Метод/атрибут |
Цель |
---|---|
|
Разделите строку на список, разделив его везде, где есть совпадения |
|
Найдите все подстроки, которые совпадают, и замените их другой строкой |
|
Выполняет то же самое, что и |
Разделение строк¶
Метод split()
шаблона разбивает строку на части там, где они совпадают, возвращая список фрагментов. Он похож на метод split()
для strings, но обеспечивает гораздо большую общность в разделителях, которые вы можете использовать для разделения; string split()
поддерживает разделение только пробелами или фиксированной строкой. Как и следовало ожидать, здесь также есть функция re.split()
на уровне модуля.
- .split(string[, maxsplit=0])
Разделите string на совпадения с регулярным выражением. Если в нем используются фиксирующие круглые скобки, то их содержимое также будет возвращено как часть результирующего списка. Если значение maxsplit не равно нулю, выполняется не более maxsplit разбиений.
Вы можете ограничить количество выполненных разбиений, передав значение для maxsplit. Когда значение maxsplit отличное от нуля, будет выполнено не более maxsplit разбиений, а оставшаяся часть строки будет возвращена в качестве последнего элемента списка. В следующем примере разделителем является любая последовательность не буквенно-цифровых символов.
>>> p = re.compile(r'\W+')
>>> p.split('This is a test, short and sweet, of split().')
['This', 'is', 'a', 'test', 'short', 'and', 'sweet', 'of', 'split', '']
>>> p.split('This is a test, short and sweet, of split().', 3)
['This', 'is', 'a', 'test, short and sweet, of split().']
Иногда вам не только интересно, что за текст находится между разделителями, но и нужно знать, что это за разделитель. Если там используются круглые скобки, то их значения также возвращаются как часть списка. Сравните следующие вызовы:
>>> p = re.compile(r'\W+')
>>> p2 = re.compile(r'(\W+)')
>>> p.split('This... is a test.')
['This', 'is', 'a', 'test', '']
>>> p2.split('This... is a test.')
['This', '... ', 'is', ' ', 'a', ' ', 'test', '.', '']
Функция уровня модуля re.split()
добавляет их для использования в качестве первого аргумента, но в остальном остается такой же.
>>> re.split(r'[\W]+', 'Words, words, words.')
['Words', 'words', 'words', '']
>>> re.split(r'([\W]+)', 'Words, words, words.')
['Words', ', ', 'words', ', ', 'words', '.', '']
>>> re.split(r'[\W]+', 'Words, words, words.', 1)
['Words', 'words, words.']
Поиск и замена¶
Другая распространенная задача - найти все совпадения с шаблоном и заменить их другой строкой. Метод sub()
принимает значение для замены, которое может быть либо строкой, либо функцией, и строку, которая должна быть обработана.
- .sub(replacement, string[, count=0])
Возвращает строку, полученную путем замены крайних левых непересекающихся вхождений RE в string на замену replacement. Если шаблон не найден, string возвращается без изменений.
Необязательный аргумент count указывает максимальное количество вхождений шаблона, которые необходимо заменить; count должно быть неотрицательным целым числом. Значение по умолчанию 0 означает замену всех вхождений.
Вот простой пример использования метода sub()
. В нем названия цветов заменяются словом colour
:
>>> p = re.compile('(blue|white|red)')
>>> p.sub('colour', 'blue socks and red shoes')
'colour socks and colour shoes'
>>> p.sub('colour', 'blue socks and red shoes', count=1)
'colour socks and red shoes'
Метод subn()
выполняет ту же работу, но возвращает кортеж из 2 элементов, содержащий новое строковое значение и количество выполненных замен:
>>> p = re.compile('(blue|white|red)')
>>> p.subn('colour', 'blue socks and red shoes')
('colour socks and colour shoes', 2)
>>> p.subn('colour', 'no colours at all')
('no colours at all', 0)
Пустые совпадения заменяются только в том случае, если они не находятся рядом с предыдущим пустым совпадением.
>>> p = re.compile('x*')
>>> p.sub('-', 'abxd')
'-a-b--d-'
Если replacement является строкой, то обрабатываются все обратные косые черты в ней. То есть \n
преобразуется в один символ новой строки, \r
преобразуется в символ возврата каретки и так далее. Неизвестные экранирующие элементы, такие как \&
, не используются. Обратные ссылки, такие как \6
, заменяются подстрокой, соответствующей их группе. Это позволяет включить фрагменты исходного текста в результирующую строку замены.
В этом примере соответствует слову section
, за которым следует строка, заключенная в {
, }
,, и изменяется section
на subsection
:
>>> p = re.compile('section{ ( [^}]* ) }', re.VERBOSE)
>>> p.sub(r'subsection{\1}','section{First} section{second}')
'subsection{First} subsection{second}'
Существует также синтаксис для обращения к именованным группам, определенный синтаксисом (?P<name>...)
. \g<name>
будет использовать подстроку, соответствующую группе с именем name
, а \g<number>
использует соответствующий номер группы. Таким образом, \g<2>
эквивалентно \2
, но не является неоднозначность в заменяющей строке, такой как \g<2>0
. (\20
, будет интерпретироваться как ссылка на группу 20, а не на группу 2, за которой следует буквальный символ '0'
.) Все приведенные ниже замены эквивалентны, но используются все три варианта строки замены.
>>> p = re.compile('section{ (?P<name> [^}]* ) }', re.VERBOSE)
>>> p.sub(r'subsection{\1}','section{First}')
'subsection{First}'
>>> p.sub(r'subsection{\g<1>}','section{First}')
'subsection{First}'
>>> p.sub(r'subsection{\g<name>}','section{First}')
'subsection{First}'
замена также может быть функцией, что дает вам еще больший контроль. Если замена является функцией, то функция вызывается при каждом непересекающемся появлении шаблона. При каждом вызове функции передается match object аргумент для соответствия, и она может использовать эту информацию для вычисления желаемой строки замены и возврата ее.
В следующем примере функция замены преобразует десятичные числа в шестнадцатеричные:
>>> def hexrepl(match):
... "Return the hex string for a decimal number"
... value = int(match.group())
... return hex(value)
...
>>> p = re.compile(r'\d+')
>>> p.sub(hexrepl, 'Call 65490 for printing, 49152 for user code.')
'Call 0xffd2 for printing, 0xc000 for user code.'
При использовании функции уровня модуля re.sub()
шаблон передается в качестве первого аргумента. Шаблон может быть представлен в виде объекта или строки; если вам нужно указать флаги регулярных выражений, вы должны либо использовать объект шаблона в качестве первого параметра, либо использовать встроенные модификаторы в строке шаблона, например, sub("(?i)b+", "x", "bbbb BBBB")
возвращает 'x x'
.
Общие проблемы¶
Регулярные выражения являются мощным инструментом для некоторых приложений, но в некоторых отношениях их поведение не интуитивно понятно, и иногда они ведут себя не так, как вы ожидаете. В этом разделе будут указаны некоторые из наиболее распространенных ошибок.
Используйте строковые методы¶
Иногда использование модуля re
является ошибкой. Если вы сопоставляете фиксированную строку или класс с одним символом и не используете какие-либо функции re
, такие как флаг IGNORECASE
, то полная мощность регулярных выражений может и не потребоваться. У Strings есть несколько методов для выполнения операций с фиксированными строками, и обычно они намного быстрее, потому что реализация представляет собой один небольшой цикл на языке Си, который был оптимизирован для этой цели, вместо большого, более обобщенного механизма регулярных выражений.
Одним из примеров может быть замена одной фиксированной строки на другую; например, вы могли бы заменить word
на deed
. re.sub()
кажется, что для этого следует использовать функцию, но рассмотрите метод replace()
. Обратите внимание, что replace()
также заменит word
внутри слов, превратив swordfish
в sdeedfish
, но наивный RE word
сделал бы то же самое. (Чтобы избежать выполнения замены частей слов, шаблон должен быть \bword\b
, чтобы требовать, чтобы word
имели границу слова с обеих сторон. Это выводит работу за рамки возможностей replace()
.)
Другой распространенной задачей является удаление каждого вхождения отдельного символа из строки или замена его другим отдельным символом. Вы могли бы сделать это с помощью чего-то вроде re.sub('\n', ' ', S)
, но translate()
способен выполнять обе задачи и будет работать быстрее, чем любая операция с регулярным выражением.
Короче говоря, прежде чем обращаться к модулю re
, подумайте, можно ли решить вашу проблему с помощью более быстрого и простого строкового метода.
сопоставление() с поиском()¶
Функция match()
проверяет только соответствие RE в начале строки, в то время как функция search()
просматривает строку в поисках совпадения. Важно помнить об этом различии. Помните, что match()
сообщит только об успешном совпадении, которое начнется с 0; если совпадение не начнется с нуля, match()
не сообщит об этом.
>>> print(re.match('super', 'superstition').span())
(0, 5)
>>> print(re.match('super', 'insuperable'))
None
С другой стороны, search()
будет сканировать строку вперед, сообщая о первом найденном совпадении.
>>> print(re.search('super', 'superstition').span())
(0, 5)
>>> print(re.search('super', 'insuperable').span())
(2, 7)
Иногда у вас возникает соблазн продолжить использовать re.match()
и просто добавить .*
в начале текста. Не поддавайтесь этому искушению и используйте вместо этого re.search()
. Компилятор регулярных выражений выполняет некоторый анализ REs, чтобы ускорить процесс поиска совпадения. Один из таких анализов позволяет определить, каким должен быть первый символ совпадения; например, шаблон, начинающийся с Crow
, должен совпадать с 'C'
. Анализ позволяет движку быстро просканировать строку в поисках начального символа, проверяя полное соответствие только в том случае, если найдено 'C'
.
Добавление .*
нарушает эту оптимизацию, требуя сканирования до конца строки и последующего возврата к поиску соответствия для остальной части строки. Вместо этого используйте re.search()
.
Жадный против нежадного¶
При повторении регулярного выражения, как в случае с a*
, результирующим действием является использование как можно большей части шаблона. Этот факт часто поражает вас, когда вы пытаетесь сопоставить пару сбалансированных разделителей, таких как угловые скобки, окружающие HTML-тег. Наивный шаблон для сопоставления с одним HTML-тегом не работает из-за жадной природы .*
.
>>> s = '<html><head><title>Title</title>'
>>> len(s)
32
>>> print(re.match('<.*>', s).span())
(0, 32)
>>> print(re.match('<.*>', s).group())
<html><head><title>Title</title>
Там соответствует '<'
в '<html>'
, а .*
занимает остальную часть строки. Однако в RE осталось еще больше, и >
не может совпадать в конце строки, поэтому обработчику регулярных выражений приходится выполнять обратный поиск по символам, пока он не найдет соответствие для >
. Финальное совпадение простирается от '<'
в '<html>'
до '>'
в '</title>'
, а это не то, что вам нужно.
В этом случае решением является использование нежадных кванторов *?
, +?
, ??
, или {m,n}?
, которые соответствуют как можно меньшему количеству текста. В приведенном выше примере '>'
пробуется сразу после первого совпадения с '<'
, и когда это не удается, движок продвигает символ по очереди, повторяя '>'
на каждом шаге. Это приводит как раз к нужному результату:
>>> print(re.match('<.*?>', s).group())
<html>
(Обратите внимание, что разбор HTML или XML с помощью регулярных выражений является болезненным процессом. Простые шаблоны будут работать с обычными случаями, но в HTML и XML есть особые случаи, которые нарушают очевидное регулярное выражение; к тому времени, когда вы напишете регулярное выражение, которое обрабатывает все возможные случаи, шаблоны будут очень сложными. Для таких задач используйте модуль синтаксического анализа HTML или XML.)
Используя огонь.многословный¶
Вы, наверное, уже заметили, что регулярные выражения - это очень компактная запись, но они не очень удобочитаемы. Области средней сложности могут состоять из длинных наборов обратных косых черт, круглых скобок и метасимволов, что затрудняет их чтение и понимание.
Для таких случаев может быть полезно указать флаг re.VERBOSE
при компиляции регулярного выражения, поскольку это позволяет более четко отформатировать регулярное выражение.
Флаг re.VERBOSE
имеет несколько эффектов. Пробелы в регулярном выражении, которые не находятся внутри символьного класса, игнорируются. Это означает, что такое выражение, как dog | cat
, эквивалентно менее удобочитаемому dog|cat
, но [a b]
все равно будет соответствовать символам 'a'
, 'b'
, или пробелу. Кроме того, вы также можете поместить комментарии внутри aRE; комментарии начинаются с символа #
и заканчиваются следующей новой строкой. При использовании со строками, заключенными в тройные кавычки, это позволяет более аккуратно форматировать REs:
pat = re.compile(r"""
\s* # Skip leading whitespace
(?P<header>[^:]+) # Header name
\s* : # Whitespace, and a colon
(?P<value>.*?) # The header's value -- *? used to
# lose the following trailing whitespace
\s*$ # Trailing whitespace to end-of-line
""", re.VERBOSE)
Это гораздо более читабельно, чем:
pat = re.compile(r"\s*(?P<header>[^:]+)\s*:(?P<value>.*?)\s*$")
Обратная связь¶
Регулярные выражения - сложная тема. Помог ли вам этот документ разобраться в них? Были ли какие-то неясные части или проблемы, с которыми вы столкнулись и которые не были рассмотрены здесь? Если да, пожалуйста, отправьте автору предложения по улучшению.
Самой полной книгой о регулярных выражениях, безусловно, является «Освоение регулярных выражений» Джеффри Фридла, опубликованная издательством O’Reilly. К сожалению, он посвящен исключительно вариантам регулярных выражений Perl и Java и вообще не содержит никаких материалов по Python, поэтому он не будет полезен в качестве справочного материала для программирования на Python. (В первом издании описывался удаленный модуль Python regex
, который вам не очень поможет.) Попробуйте воспользоваться им в своей библиотеке.