Руководство по регулярным выражениям
- Автор:
-
A.M. Kuchling <amk@amk.ca>
Введение
Регулярные выражения (сокращённо RE или regexes или regex-паттерны) — это по сути небольшой, высокоспециализированный язык программирования, встроенный в Python и доступный через модуль re. Используя этот небольшой язык, вы определяете правила для набора возможных строк, которые вы хотите сопоставить; этот набор может содержать английские предложения, адреса электронной почты, команды TeX или что угодно. Затем вы можете задавать вопросы, например: «Соответствует ли эта строка шаблону?» или «Есть ли совпадение с шаблоном где-либо в этой строке?». Вы также можете использовать RE для изменения строки или разбить её на части различными способами.
Шаблоны регулярных выражений компилируются в последовательность байткодов, которые затем выполняются движком сопоставления, написанным на C. Для продвинутого использования может потребоваться уделить пристальное внимание тому, как движок выполнит данное RE, и написать RE определённым образом, чтобы получить байткоды, выполняющиеся быстрее. В этом документе оптимизация не рассматривается, так как она требует глубокого понимания внутренних механизмов движка сопоставления.
Язык регулярных выражений относительно небольшой и ограниченный, поэтому не все возможные задачи по обработке строк можно решить с помощью регулярных выражений. Также есть задачи, которые можно решить с помощью регулярных выражений, но выражения при этом получаются очень сложными. В таких случаях вам может быть лучше написать код Python для обработки; хотя код Python будет медленнее, чем сложное регулярное выражение, он, вероятно, будет более понятен.
Простые шаблоны
Начнём с изучения самых простых регулярных выражений. Так как регулярные выражения используются для работы со строками, начнём с самой распространённой задачи: сопоставления символов.
Для подробного объяснения компьютерных основ регулярных выражений (детерминированных и недетерминированных конечных автоматов) вы можете обратиться к любой книге по написанию компиляторов.
Сопоставление символов
Большинство букв и символов просто сопоставляются сами с собой. Например, регулярное выражение test точно сопоставит строку test. (Вы можете включить режим, не чувствительный к регистру, который позволит этому RE сопоставить также Test или TEST; об этом позже.)
Есть исключения из этого правила; некоторые символы являются специальными метасимволами и не сопоставляются сами по себе. Вместо этого они сигнализируют о том, что должно быть сопоставлено нечто необычное, или они влияют на другие части RE, повторяя их или изменяя их значение. Большая часть этого документа посвящена обсуждению различных метасимволов и их функций.
Вот полный список метасимволов; их значения будут обсуждаться в остальной части этого руководства.
. ^ $ * + ? { } [ ] \ | ( )
Первые метасимволы, которые мы рассмотрим, — это [ и ]. Они используются для указания класса символов, который представляет собой набор символов, которые вы хотите сопоставить. Символы могут быть перечислены по отдельности или задан диапазон символов путём указания двух символов и разделения их '-'. Например, [abc] сопоставит любой из символов a, b, или c; это то же самое, что и [a-c], которое использует диапазон для выражения того же набора символов. Если вы хотите сопоставить только строчные буквы, ваше RE будет [a-z].
Метасимволы (кроме \ ) не активны внутри классов. Например, [akm$] сопоставит любой из символов 'a', 'k', 'm', или '$'; '$' обычно является метасимволом, но внутри класса символов он теряет своё специальное свойство.
Вы можете сопоставить символы, не указанные в классе, путём дополнения набора. Это указывается путём включения '^' в качестве первого символа класса. Например, [^5] сопоставит любой символ, кроме '5'. Если символ возвышения встречается где-либо ещё в классе символов, он не имеет специального значения. Например: [5^] сопоставит либо '5' , либо '^'.
Возможно, самым важным метасимволом является обратный слэш, \. Как и в строковых литералах Python, обратный слэш может быть последован различными символами для обозначения различных специальных последовательностей. Он также используется для экранирования всех метасимволов, чтобы вы могли всё ещё сопоставлять их в шаблонах; например, если вам нужно сопоставить [ или \, вы можете предварять их обратным слэшем, чтобы удалить их специальное значение: \[ или \\.
Некоторые специальные последовательности, начинающиеся с '\' , представляют предопределённые наборы символов, которые часто полезны, такие как набор цифр, набор букв или набор всех символов, не являющихся пробелами.
Приведём пример: \w сопоставляет любой буквенно-цифровой символ. Если шаблон регулярного выражения выражен в байтах, это эквивалентно классу [a-zA-Z0-9_]. Если шаблон регулярного выражения является строкой, \w сопоставит все символы, отмеченные как буквы в базе данных Unicode, предоставляемой модулем unicodedata. Вы можете использовать более ограниченное определение \w в строковом шаблоне, передавая флаг re.ASCII при компиляции регулярного выражения.
Следующий список специальных последовательностей неполный. Для получения полного списка последовательностей и расширенных определений классов для шаблонов строк Unicode см. последнюю часть Синтаксис регулярных выражений в справочнике стандартной библиотеки. В общем случае версии 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), где он будет сопоставлять даже символ новой строки. . часто используется, когда вам нужно сопоставить «любой символ».
Повторяющиеся элементы
Возможность сопоставления различных наборов символов — это первое, что могут делать регулярные выражения, чего нельзя сделать с методами строк. Однако, если бы это была единственная дополнительная возможность регулярных выражений, они не были бы значительным улучшением. Другая возможность заключается в том, что вы можете указать, что части RE должны повторяться определённое количество раз.
Первый метасимвол для повторения, который мы рассмотрим, это *. * не соответствует буквально символу '*'; вместо этого он указывает, что предыдущий символ может совпадать ноль или более раз вместо одного.
Например, ca*t будет соответствовать 'ct' (0 'a' символа), 'cat' (1 'a'), 'caaat' (3 'a' символа) и так далее.
Повторения, такие как *, являются жадными; при повторении RE движок сопоставления будет пытаться повторить его как можно большее количество раз. Если последующие части шаблона не совпадают, движок сопоставления откатится назад и попробует снова с меньшим количеством повторений.
Пример пошагового выполнения прояснит это. Рассмотрим выражение a[bcd]*b. Оно соответствует букве 'a', нулю или более буквам из класса [bcd] и, наконец, заканчивается на 'b'. Теперь представьте себе сопоставление этого RE со строкой 'abcbd'.
Шаг | Совпадение | Объяснение |
|---|---|---|
1 |
|
|
2 |
| Движок сопоставляет |
3 | Ошибка | Движок пытается сопоставить |
4 |
| Откатывается, чтобы |
5 | Ошибка | Повторно пытается сопоставить |
6 |
| Снова откатывается, так что |
6 |
| Повторно пытается сопоставить |
Конец RE достигнут, и он сопоставился с 'abcb'. Это демонстрирует, как движок сопоставления сначала достигает конца, а если совпадение не найдено, то последовательно откатывается и пытается снова и снова повторить остальную часть RE. Он откатывается до тех пор, пока не попробует нулевое количество совпадений для [bcd]*, а если и это не удастся, движок заключит, что строка вообще не соответствует RE.
Ещё один метасимвол повторения — +, который соответствует одному или более разам. Обратите особое внимание на разницу между * и +; * соответствует нулю или более раз, поэтому то, что повторяется, может вообще отсутствовать, в то время как + требует по крайней мере одного вхождения. Используя аналогичный пример, ca+t будет соответствовать 'cat' (1 'a'), 'caaat' (3 'a'), но не '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 предоставляет интерфейс к движку регулярных выражений, позволяющий компилировать RE в объекты и затем выполнять сопоставления с ними.
Компиляция регулярных выражений
Регулярные выражения компилируются в объекты шаблонов, которые имеют методы для различных операций, таких как поиск совпадений шаблона или выполнение подстановок строк.
>>> import re
>>> p = re.compile('ab*')
>>> p
re.compile('ab*')
re.compile() также принимает необязательный аргумент flags, используемый для включения различных специальных функций и вариантов синтаксиса. Мы рассмотрим доступные настройки позже, но сейчас достаточно одного примера:
>>> p = re.compile('ab*', re.IGNORECASE)
RE передается в re.compile() в виде строки. RE обрабатываются как строки, потому что регулярные выражения не являются частью языка Python, и для их выражения не создавался специальный синтаксис. (Существуют приложения, которым вообще не нужны RE, поэтому нет необходимости усложнять спецификацию языка, включая их). Вместо этого модуль re — это просто модуль расширения на C, включённый в Python, как и модули socket или zlib.
Нахождение RE в строках упрощает язык Python, но имеет один недостаток, который является темой следующего раздела.
Проблема обратного слеша
Как уже говорилось ранее, регулярные выражения используют символ обратного слеша ('\') для обозначения специальных форм или для использования специальных символов без вызова их специального значения. Это противоречит использованию Python того же символа для той же цели в строковых литералах.
Допустим, вы хотите написать RE, который соответствует строке \section, которая может встречаться в файле LaTeX. Чтобы понять, что написать в коде программы, начните с желаемой строки для сопоставления. Затем необходимо экранировать все обратные слэши и другие метасимволы, предваряя их обратным слэшем, что приводит к строке \\section. Результирующая строка, которая должна быть передана в re.compile(), должна быть \\section. Однако, чтобы выразить это как строковый литерал Python, оба обратных слэша должны быть экранированы ещё раз.
Символы | Этап |
|---|---|
| Строка текста для сопоставления |
| Экранированный обратный слэш для |
| Экранированные обратные слэши для строкового литерала |
Короче говоря, чтобы сопоставить буквальный обратный слэш, необходимо написать '\\\\' в качестве строки RE, так как регулярное выражение должно быть \\, а каждый обратный слэш должен быть выражен как \\ внутри обычного строкового литерала Python. В RE, которые содержат обратные слэши многократно, это приводит к большому количеству повторяющихся обратных слэшей и затрудняет понимание результирующих строк.
Решение заключается в использовании синтаксиса сырых строк Python для регулярных выражений; обратные слэши не обрабатываются каким-либо специальным образом в строковом литерале, предваряемом префиксом 'r', поэтому r"\n" — это строка из двух символов, содержащая '\' и 'n', в то время как "\n" — это строка из одного символа, содержащая новую строку. Регулярные выражения часто записываются в коде Python с использованием этого синтаксиса сырых строк.
Кроме того, специальные последовательности эскейпов, которые допустимы в регулярных выражениях, но не допустимы в строковых литералах Python, теперь вызывают DeprecationWarning и в конечном итоге станут SyntaxError, что означает, что эти последовательности будут недопустимы, если не используется синтаксис сырых строк или экранирование обратных слэшей.
Обычная строка | Строка без обработки |
|---|---|
|
|
|
|
|
|
Выполнение соответствий
После создания объекта, представляющего скомпилированное регулярное выражение, что с ним делать? Объекты шаблонов имеют несколько методов и атрибутов. Здесь будут рассмотрены только самые важные; для полного списка обратитесь к документации re.
Метод/Атрибут | Назначение |
|---|---|
| Определяет, соответствует ли регулярное выражение началу строки. |
| Просматривает строку, ищет все места, где это регулярное выражение соответствует. |
| Находит все подстроки, где соответствует регулярное выражение, и возвращает их как список. |
| Находит все подстроки, где соответствует регулярное выражение, и возвращает их как итератор. |
match() и search() возвращают None в случае отсутствия совпадения. В случае успеха возвращается экземпляр объекта совпадения, содержащий информацию о совпадении: его начало и конец, сопоставленную подстроку и многое другое.
Вы можете узнать об этом, экспериментируя с модулем re интерактивно.
Этот HOWTO использует стандартный интерпретатор Python для примеров. Сначала запустите интерпретатор Python, импортируйте модуль re и скомпилируйте регулярное выражение:
>>> import re
>>> p = re.compile('[a-z]+')
>>> p
re.compile('[a-z]+')
Теперь вы можете попытаться сопоставить различные строки с регулярным выражением [a-z]+. Пустая строка не должна соответствовать вообще, так как + означает «одна или несколько повторений». match() должен вернуть None в этом случае, что приведет к тому, что интерпретатор ничего не напечатает. Вы можете явно напечатать результат match() для большей ясности.
>>> p.match("")
>>> print(p.match(""))
None
Теперь попробуем его на строке, которая должна соответствовать, например, на tempo. В этом случае match() вернёт объект совпадения, поэтому нужно сохранить результат в переменной для последующего использования.
>>> m = p.match('tempo')
>>> m
<re.Match object; span=(0, 5), match='tempo'>
Теперь вы можете запросить у объекта совпадения информацию о совпадающей строке. Экземпляры объекта совпадения также имеют несколько методов и атрибутов; наиболее важные из них:
Метод/Атрибут | Назначение |
|---|---|
| Возвращает строку, соответствующую регулярному выражению |
| Возвращает начальную позицию совпадения |
| Возвращает конечную позицию совпадения |
| Возвращает кортеж, содержащий начальную и конечную позиции совпадения |
Опыт работы с этими методами поможет быстрее понять их значение:
>>> m.group() 'tempo' >>> m.start(), m.end() (0, 5) >>> m.span() (0, 5)
group() возвращает подстроку, соответствующую регулярному выражению. start() и end() возвращают начальный и конечный индекс совпадения. span() возвращает оба индекса в одном кортеже. Поскольку метод match() проверяет только, соответствует ли регулярное выражение началу строки, 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)
В реальных программах наиболее распространённый стиль заключается в сохранении объекта совпадения в переменной и проверке, был ли он None. Это обычно выглядит так:
p = re.compile( ... )
m = p.match( 'string goes here' )
if m:
print('Match found: ', m.group())
else:
print('No match')
Два метода шаблона возвращают все совпадения для шаблона. findall() возвращает список соответствующих строк:
>>> p = re.compile(r'\d+')
>>> p.findall('12 drummers drumming, 11 pipers piping, 10 lords a-leaping')
['12', '11', '10']
Префикс r, делающий литерал сырой строкой, необходим в этом примере, потому что последовательности обратного слэша в обычном «сыром» литерале строки, не распознаваемые Python, в отличие от регулярных выражений, теперь вызывают DeprecationWarning и в конечном итоге станут SyntaxError. См. Проблема с обратными слэшами.
findall() должен создать весь список, прежде чем его можно будет вернуть в качестве результата. Метод finditer() возвращает последовательность экземпляров объектов совпадения как итератор:
>>> 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)
Функции уровня модуля
Вам не нужно создавать объект шаблона и вызывать его методы; модуль re также предоставляет функции верхнего уровня, называемые match(), search(), findall(), sub() и т. д. Эти функции принимают те же аргументы, что и соответствующие методы шаблона, с добавлением строки регулярного выражения в качестве первого аргумента, и по-прежнему возвращают либо None , либо экземпляр объекта совпадения.
>>> 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 '>
Под капотом эти функции просто создают для вас объект шаблона и вызывают соответствующий метод. Они также сохраняют скомпилированный объект в кэше, поэтому будущие вызовы с использованием того же регулярного выражения не будут повторно анализировать шаблон снова и снова.
Следует ли использовать эти функции уровня модуля или самостоятельно получать шаблон и вызывать его методы? Если вы обращаетесь к регулярному выражению в цикле, предварительная компиляция сэкономит несколько вызовов функций. Вне циклов разницы практически нет благодаря внутреннему кэшу.
Флаги компиляции
Флаги компиляции позволяют изменять некоторые аспекты работы регулярных выражений. Флаги доступны в модуле 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: ‘İ’ (U+0130, прописная буква I с точкой сверху), ‘ı’ (U+0131, строчная буква i без точки), ‘ſ’ (U+017F, строчная буква s с длинной чертой) и ‘K’ (U+212A, символ Кельвина).Spamбудет соответствовать'Spam','spam','spAM', или'ſpam'(последнее соответствует только в режиме Юникода). Это приведение к нижнему регистру не учитывает текущую локаль; это будет учитываться, если также установлен флагLOCALE.
- re.L
- re.LOCALE
-
Заставляет
\w,\W,\b,\Bи соответствие без учета регистра зависеть от текущей локали вместо базы данных Юникода.Локали — это функция библиотеки C, предназначенная для написания программ, учитывающих различия языков. Например, если вы обрабатываете закодированный французский текст, вам нужно будет использовать
\w+для сопоставления слов, но\wсоответствует только символьному классу[A-Za-z]в шаблонах байтов; он не будет соответствовать байтам, соответствующимéилиç. Если ваша система настроена должным образом и выбрана французская локаль, некоторые функции C скажут программе, что байт, соответствующийé, также должен рассматриваться как буква. Установка флагаLOCALEпри компиляции регулярного выражения заставит полученный скомпилированный объект использовать эти функции C для\w; это медленнее, но также позволяет\w+соответствовать французским словам так, как ожидается. Использование этого флага в Python 3 не рекомендуется, так как механизм локали очень ненадежен, он обрабатывает только одну «культуру» за раз и работает только с 8-битными локалями. Соответствие по Юникоду уже включено по умолчанию в Python 3 для шаблонов Юникода (str), и он может обрабатывать разные локали/языки.
- re.M
- re.MULTILINE
-
(
^и$еще не объяснены; они будут рассмотрены в разделе Дополнительные метасимволы.)Обычно
^соответствует только в начале строки, а$соответствует только в конце строки и непосредственно перед символом новой строки (если он есть) в конце строки. При указании этого флага^соответствует в начале строки и в начале каждой строки в строке, сразу после каждой новой строки. Аналогично, метасимвол$соответствует либо в конце строки, либо в конце каждой строки (непосредственно перед каждой новой строкой).
- re.S
- re.DOTALL
-
Заставляет специальный символ
'.'соответствовать любому символу, включая символ новой строки; без этого флага'.'будет соответствовать чему-угодно, *кроме* символа новой строки.
- re.A
- re.ASCII
-
Заставляет
\w,\W,\b,\B,\sи\Sвыполнять соответствие только по ASCII, а не по полному Юникоду. Это имеет смысл только для шаблонов Юникода и игнорируется для шаблонов байтов.
- 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)Без режима verbose RE выглядел бы так:
charref = re.compile("&#(0[0-7]+" "|[0-9]+" "|x[0-9a-fA-F]+);")В приведенном выше примере использована автоматическая конкатенация строковых литералов Python для разделения RE на более мелкие части, но все еще сложнее понять, чем версия, использующая
re.VERBOSE.
Больше возможностей шаблонов
До сих пор мы рассмотрели только часть возможностей регулярных выражений. В этом разделе мы рассмотрим новые метасимволы и как использовать группы для извлечения частей текста, который был сопоставлен.
Дополнительные метасимволы
Есть некоторые метасимволы, которые мы еще не изучали. Большинство из них будет рассмотрено в этом разделе.
Некоторые из оставшихся метасимволов, которые будут обсуждаться, являются утверждениями нулевой ширины. Они не заставляют движок продвигаться по строке; вместо этого они вообще не потребляют символы и просто увенчиваются успехом или неудачей. Например, \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в обратный пробел, и ваше регулярное выражение не будет соответствовать ожидаемому результату. Следующий пример выглядит так же, как наше предыдущее регулярное выражение, но опускает'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'>Во-вторых, внутри набора символов, где это утверждение не используется,
\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 всегда присутствует; это всё регулярное выражение, поэтому все методы объекта соответствия имеют группу 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 также используют обратную косую черту, за которой следуют цифры, чтобы разрешить включение произвольных символов в строку, поэтому обязательно используйте сырую строку при включении обратных ссылок в регулярное выражение.
Например, следующее регулярное выражение обнаруживает удвоенные слова в строке.
>>> p = re.compile(r'\b(\w+)\s+\1\b')
>>> p.search('Paris in the the spring').group()
'the the'
Обратные ссылки такого рода не очень полезны только для поиска в строке — существует мало текстовых форматов, которые повторяют данные таким образом — но вы скоро поймете, что они очень полезны при выполнении подстановок строк.
Незахватывающие и именованные группы
Сложные регулярные выражения могут использовать много групп, как для захвата подстрок, представляющих интерес, так и для группировки и структурирования самого регулярного выражения. В сложных регулярных выражениях становится трудно отслеживать номера групп. Есть две функции, которые помогают решить эту проблему. Обе они используют общий синтаксис расширений регулярных выражений, поэтому сначала рассмотрим его.
Perl 5 известен своими мощными дополнениями к стандартным регулярным выражениям. Для этих новых функций разработчики Perl не могли выбрать новые символы-метасимволы с одиночным нажатием клавиши или новые специальные последовательности, начинающиеся с \ , не сделав регулярные выражения Perl непонятными по сравнению со стандартными регулярными выражениями. Если бы они выбрали & в качестве нового метасимвола, например, старые выражения предполагали бы, что & является обычным символом и не экранировали бы его, написав \& или [&].
Выбранное разработчиками 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 — это, очевидно, имя группы. Именованные группы ведут себя точно так же, как захватывающие группы, и дополнительно связывают имя с группой. Методы объекта совпадения, связанные с захватывающими группами, все принимают либо целые числа, которые ссылаются на группу по номеру, либо строки, содержащие имя желаемой группы.
>>> 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$)[^.]*$
Изменение строк
До этого момента мы просто выполняли поиск в статической строке. Регулярные выражения также часто используются для изменения строк различными способами, используя следующие методы шаблонов:
Метод/Атрибут | Назначение |
|---|---|
| Разделить строку на список, разделяя ее там, где совпадает RE |
| Найти все подстроки, где совпадает RE, и заменить их другой строкой |
| Делает то же, что и |
Разделение строк
Метод split() шаблона разделяет строку там, где совпадает RE, возвращая список частей. Он похож на метод split() строк, но обеспечивает гораздо большую общность разделителей, по которым можно разделять; строка split() поддерживает только разделение по пробелам или по фиксированной строке. Как и ожидалось, существует также функция уровня модуля re.split().
- .split(string[, maxsplit=0])
-
Разделить string по совпадениям с регулярным выражением. Если в RE используются группирующие скобки, то их содержимое также будет возвращено как часть результирующего списка. Если 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().']
Иногда вы не только заинтересованы в том, что находится между разделителями, но также вам нужно знать, что представлял собой разделитель. Если в RE используются группирующие скобки, то их значения также возвращаются как часть списка. Сравните следующие вызовы:
>>> 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 в качестве первого аргумента, но в остальном она такая же.
>>> 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 — в символ возврата каретки и так далее. Неизвестные escapes, такие как \& оставляются без изменений. Ссылки на обратные части, такие как \6, заменяются подстрокой, соответствующей группе в RE. Это позволяет включить части исходного текста в результирующую строку замены.
Этот пример находит слово 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}'
replacement также может быть функцией, что дает вам еще больший контроль. Если replacement — функция, функция вызывается для каждого неперекрывающегося вхождения pattern. При каждом вызове функция получает аргумент объекта совпадения объекта совпадения для совпадения и может использовать эту информацию для вычисления желаемой строки замены и возвращения её.
В следующем примере функция замены переводит десятичные числа в шестнадцатеричные:
>>> 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, то вся мощь регулярных выражений может быть не нужна. Строки имеют несколько методов для выполнения операций с фиксированными строками, и они обычно намного быстрее, потому что их реализация — это единственный небольшой цикл C, оптимизированный для этой цели, а не большая, более обобщённая система регулярных выражений.
Один пример — это замена одной фиксированной строки другой; например, вы можете заменить word на deed. Функция re.sub() кажется подходящей для этого, но рассмотрите метод replace(). Обратите внимание, что replace() также заменит word внутри слов, превратив swordfish в sdeedfish, но и наивное регулярное выражение word тоже это сделает. (Чтобы избежать замены внутри слов, шаблон должен быть \bword\b, чтобы потребовать, чтобы word имело границу слова с обеих сторон. Это выходит за рамки возможностей replace().)
Другая распространённая задача — удаление каждого вхождения одного символа из строки или его замена другим символом. Вы можете сделать это с помощью чего-то вроде re.sub('\n', ' ', S), но translate() способен выполнить обе задачи и будет быстрее, чем любая операция с регулярным выражением.
Короче говоря, прежде чем обращаться к модулю re, подумайте, можно ли решить вашу проблему с помощью более быстрого и простого метода для строк.
match() против search()
Функция match() проверяет только, соответствует ли регулярное выражение началу строки, а 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() вместо этого. Компилятор регулярных выражений выполняет некоторый анализ регулярных выражений, чтобы ускорить процесс поиска совпадения. Один из таких анализов определяет, каким должен быть первый символ совпадения; например, шаблон, начинающийся с 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>', и .* потребляет остальную часть строки. В регулярном выражении всё ещё есть что-то другое, и > не может соответствовать в конце строки, поэтому движок регулярных выражений должен возвращаться символ за символом до тех пор, пока он не найдёт совпадение для > . Конечное совпадение простирается от '<' в '<html>' до '>' в '</title>', что не нужно.
В этом случае решение заключается в использовании нежадных квантификаторов *?, +?, ??, или {m,n}?, которые соответствуют как можно меньше текста. В приведённом выше примере '>' проверяется сразу после того, как '<' находит соответствие, а когда это не срабатывает, движок переходит к следующему символу, повторно проверяя '>' на каждой итерации. Это приводит к нужному результату:
>>> print(re.match('<.*?>', s).group())
<html>
(Обратите внимание, что разбор HTML или XML с помощью регулярных выражений сложная задача. Быстрые и грязные шаблоны справятся с общими случаями, но HTML и XML имеют особые случаи, которые нарушат очевидные регулярные выражения; к тому времени, когда вы напишете регулярное выражение, которое обрабатывает все возможные случаи, шаблоны станут очень сложными. Используйте модуль разбора HTML или XML для таких задач.)
Использование re.VERBOSE
К этому моменту вы, вероятно, заметили, что регулярные выражения — это очень компактная запись, но они не очень читабельны. Регулярные выражения средней сложности могут превратиться в длинные последовательности обратных слэшей, скобок и метасимволов, что затрудняет их чтение и понимание.
Для таких регулярных выражений установка флага re.VERBOSE при компиляции регулярного выражения может быть полезной, поскольку позволяет более чётко сформатировать регулярное выражение.
Флаг re.VERBOSE имеет несколько эффектов. Пробелы в регулярном выражении, которые не находятся внутри класса символов, игнорируются. Это означает, что такое выражение, как dog | cat , эквивалентно менее читабельному dog|cat, но [a b] по-прежнему будет соответствовать символам 'a', 'b' или пробелу. Кроме того, вы также можете помещать комментарии внутри регулярного выражения; комментарии начинаются с символа # и заканчиваются новой строкой. При использовании строк с тройными кавычками это позволяет более аккуратно форматировать регулярные выражения:
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, который теперь удалён, что не сильно поможет вам.) Подумайте о том, чтобы взять его в библиотеке.
© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.13/howto/regex.html