В языке Python имеется весьма
полезный встроенный модуль re для обработки
строк с помощью регулярных выражений. Для тех кто совсем не знает что это
такое, приведу следующий простой пример:
import re
text = "Карта map и объект bitmap - это разные вещи"
match = re.findall("map", text)
print(match)
Результатом
работы этой программы будет коллекция из двух найденных подстрок «map»:
['map', 'map']
На первый взгляд
это может показаться совсем банальным. Но, давайте предположим, что от нас
потребовали находить не все подстроки map, а только те, что являются целым
словом. В рамках регулярных выражений это достигается определением
соответствующего шаблона, в частности, такого:
match = re.findall("\\bmap\\b", text)
Здесь записаны
два подряд слеша, чтобы итоговая строка стала такой:
"\bmap\b"
И, так как в
обратные слеши приходится часто записывать в регулярных выражениях, то удобнее
перед строкой поставить бувку ‘r’, которая указывает Python не
преобразовывать строку, а брать такой, какой она записана:
match = re.findall(r"\bmap\b", text)
Запустим
программу и увидим только одно вхождение map, которое
соответствует первому слову. Видите, умея составлять шаблоны для регулярных
выражений, можно с легкостью реализовывать самую разную обработку строк. В этом
их удобство и в рамках языка Python еще и скорость
работы, т.к. сам модуль re написан на языке
Си, что обеспечивает высокую скорость обработки строк.
В практике
программирования регулярные выражения используются для решения следующих задач:
-
проверка
фрагмента текста заданному шаблону (например, формат записи номера телефона);
-
поиск
подстрок по указанному шаблону в тексте;
-
поиск
и замена регулярного выражения на заданную строку;
-
разбиение
строки по найденным шаблонам, записанного в виде регулярного выражения
(например, данные записанные в строке как ключ=значение разбиваются отдельно на
ключи и значения).
Чтобы все это
применять на практике, необходимо знать язык регулярных выражений, который мы
сейчас и начнем рассматривать.
Язык регулярных выражений
Этот язык
является универсальным и не привязан к конкретному языку программирования. То
есть, изучив его, вы сможете использовать регулярные выражения и в Perl и в JavaScript и в Java и т.д. Многие
современные языки программирования поддерживают возможность обработки строк с
помощью таких шаблонов.
Простейшее
регулярное выражение – это обычные литералы символов, такие как a или 5.
Если их просто записать в шаблоне:
text = "еда, беда, победа"
match = re.findall(r"еда", text)
то будут найдены
все вхождения, где встречается данная комбинация символов:
"еда,
беда, победа"
Алгоритм поиска
работает так: находится один символ e, за которым
следует один символ д, за которым следует один символ а. Однако,
не все символы можно записывать в явном виде. Есть специальные символы, которые
относятся к языку регулярных выражений. Они следующие:
\.^$?+*{}[]()|
Например, если
взять слово «еда» в круглые скобки:
match = re.findall(r"(еда)", text)
то увидим абсолютно
тот же самый результат, т.к. круглые скобки здесь воспринимаются не как
отдельные символы, а как часть конструкции языка выражений. И так со всеми
специальными символами. Если нам все же нужно их использовать как отдельные
символы, то перед ними следует записать обратный слеш:
match = re.findall(r"\(еда\)", text)
и тогда будет
искаться подстрока «(еда)», которой у нас нет, поэтому получим пустую
коллекцию. Но вот так:
text = "(еда), беда, победа"
match = re.findall(r"\(еда\)", text)
получим одно
вхождение. Также в пределах языка Python можно
использовать большинство стандартных символов строк:
\n, \t
или же
записывать символы в виде шестнадцатиричных кодов в форматах:
\xHH, \uHHHHи
\UHHHHHHHH
Символьный класс
Часто требуется
найти не одно какое-то конкретное написание слова, а все его множественные
формы, например:
еда, еду, Еда,
Еду
здесь первая
буква может быть малой или заглавной, а последняя или а, или у. Чтобы в нашем
шаблоне охватить все эти варианты используются символьные классы,
которые определяются квадратными скобками:
[набор символов]
В нашем случае
его можно использовать так:
text = "Еда, беду, победа"
match = re.findall(r"[еЕ]д[ау]", text)
Мы здесь в
первых квадратных скобках указываем, что первым символом может быть е
или Е. Далее, должен идти символ д. А в конце стоять символ а
или у. Благодаря использованию символьного класса будут найдены все
комбинации:
['Еда', 'еду', 'еда']
Или, например,
мы хотим определить наличие одной (любой) цифры в нашем тексте. Для этого можно
записать вот такой символьный класс:
text = "Еда, беду, 5 победа"
match = re.findall(r"[0123456789]", text)
и будет найдена
цифра 5. Но, для удобства, в символьном классе можно указывать диапазон
значений:
match = re.findall(r"[0-9]", text)
Результат будет
тот же. Если нужно инвертировать последовательность символов в символьном
классе, то используется символ ^:
match = re.findall(r"[^0-9]", text)
Данный шаблон означает
поиск любого нецифрового символа. Результатом будет последовательность:
['Е', 'д', 'а',
',', ' ', 'б', 'е', 'д', 'у', ',', ' ', ' ', 'п', 'о', 'б', 'е', 'д', 'а']
Причем, обратите
внимание, если символ дефиса ‘-‘ записать первым в символьном классе:
text = "Еда, беду, из-за, победа"
match = re.findall(r"[-0-9]", text)
то он будет
соответствовать просто этому символу.
По аналогии
можно задавать интервал и для символов, например, так:
match = re.findall(r"[а-я]", text)
на выходе
получим найденные малые символы:
['д', 'а', 'б',
'е', 'д', 'у', 'и', 'з', 'з', 'а', 'п', 'о', 'б', 'е', 'д', 'а']
Или записывать
несколько диапазонов:
match = re.findall(r"[а-яА-Я0-9]", text)
Будут находиться
все буквы русского алфавита и цифры. Также внутри символьного класса все
специальные символы (кроме обратного слеша ‘\’) теряют свое значение и
воспринимаются как обычные символы:
text = "(еда), еда, победа"
match = re.findall(r"[(]еда[)]", text)
Здесь будет
искаться строка «(еда)», так как круглые скобки записаны в символьном классе. Но
два символа: ^ и - принимают новое значение, как мы только что видели.
Некоторые наборы
символов, например, [0-9] или [^0-9] и другие довольно часто используются на
практике, поэтому им были назначены специальные краткие формы:
Символ
|
Значение
|
.
|
Соответствует
любому символу, кроме символа переноса строки (‘\n’). Но, если
установлен флаг re.DOTALL, то точка соответствует вообще
любому символу в тексте. Однако, если она записана внутри символьного класса [.],
то воспринимается как символ точки.
|
\d
|
Соответствует
любой цифре, если используется кодировка Юникода. Если же установлен флаг re.ASCII, то диапазону
цифр [0-9].
|
\D
|
Соответствует
любому не цифровому символу для Юникода или символьному классу [^0-9] при
установленном флаге re.ASCII
|
\s
|
Для
Юникода – любой пробельный символ. Для re.ASCII – символьному
классу [ \t\n\r\f\v]
|
\S
|
Для
Юникода – любой не пробельный символ. Для re.ASCII – символьному
классу [^ \t\n\r\f\v]
|
\w
|
Для
Юникода – любой символ слова. При флаге re.ASCII – набору
символов [a-zA-Z0-9_]
|
\W
|
Для
Юникода – любой не символ слова. При флаге re.ASCII – набору
символов [^a-zA-Z0-9_]
|
Например, если
записать вот такой шаблон:
text = "(еда), еда, победа"
match = re.findall(r".", text)
то получим
список из всех символов строки text. Если записать так:
match = re.findall(r"\w", text)
то только
символы слов:
['е', 'д', 'а',
'е', 'д', 'а', 'п', 'о', 'б', 'е', 'д', 'а']
Но, если указать
флаг re.ASCII:
match = re.findall(r"\w", text, re.ASCII)
то получим
пустой список, т.к. символьный класс в этом случае будет иметь вид [a-zA-Z0-9_] и в нем
нет символов русского алфавита. Также специальные наборы можно записывать и
внутри символьных классов, например, так:
text = "0xf, 0xa, 0x5"
match = re.findall(r"0x[\da-fA-F]")
Здесь мы
выделяем шестнадцатиричные числа из строки.
Вот что из себя
в самом простом случае представляют регулярные выражения и символьный класс. На
следующем занятии мы продолжим эту тему и рассмотрим новые конструкции языка регулярных
выражений.