В этой статье мы представляем исчерпывающую статью о регулярных выражениях на языке Python.
1. Что такое регулярное выражение
Если вы когда-либо искали файл, который точно не помнили его имени, то вы могли использовать один или несколько специальных (или подстановочных ) символов для определения символов, которые вы не могли запомнить. Например, чтобы найти все файлы .txt
, начинающиеся с b
вы, возможно, набрали « b*.txt
в приложении файлового менеджера (Windows Explorer, или Nautilus, или Finder), или в оболочке Linux или в окне DOS. Вышеупомянутое выражение ( b*.txt
) является примером сопоставления с шаблоном , которое называется более конкретно « глобализация» , и оно соответствует именам b.txt ba.txt bla.txt
Распространенные персонажи:
-
?
соответствует любому отдельному символу -
*
соответствует любому количеству символов -
[...]
соответствует любому отдельному символу в наборе, например,[ac]
соответствует только символамa, b
иc
.
Давайте посмотрим некоторые определения из Википедии:
- Сопоставление с образцом — это проверка заданной последовательности токенов на наличие составляющих некоторого образца . Совпадение обычно должно быть точным: «оно будет или не будет совпадением».
- Регулярное выражение , регулярное выражение или регулярное выражение — это последовательность символов, которые определяют шаблон поиска .
- Шаблоны глобуса определяют наборы имен файлов с подстановочными знаками .
Итак, в приведенном выше примере с поиском файлов b*.txt
является шаблоном поиска, который применяется, например, к именам b.txt ba.txt bla.txt
и соответствует именам файлов b.txt ba.txt bla.txt
.
В этой статье мы узнаем о шаблонах поиска или регулярных выражениях, которые поддерживаются Python, а также о том, какие команды использовать для использования регулярных выражений в ваших программах на Python.
2. Введение в регулярные выражения в Python
Представьте, что у вас есть задача поиска электронных писем в тексте, например
1
2
|
>>> text = 'You may reach us at this email address: |
Как вы будете искать адрес электронной почты в тексте выше?
1
|
index = text.find( '@' ) |
чтобы найти индекс @
а затем вам нужно найти другие части адреса электронной почты (оставьте читателю в качестве упражнения; пожалуйста, не используйте регулярные выражения). Используя регулярные выражения, вы наберете что-то вроде:
1
2
|
>>> import re >>> ans = re.search( '@' , text) |
Намного проще, не правда ли? Далее мы увидим, как мы можем уточнить нашу строку регулярного выражения внутри метода search()
, чтобы иметь возможность правильно идентифицировать адрес электронной почты.
В качестве другого примера, давайте посмотрим, как мы можем искать шаблон b*.txt
в нашей текущей папке:
1
2
3
|
>>> import glob >>> print (glob.glob( 'b*.txt' )) [ 'b.txt' 'ba.txt' 'bla.txt' ] |
Не так ли сложно, не так ли?
2.1 Методы регулярных выражений Python
Модуль re предоставляет ряд функций сопоставления с образцом:
метод | объяснение |
match(regex, string) |
находит regex в начале string и возвращает объект Match с методами start() и end() для получения индексов или None |
search(regex, string) |
находит regex любом месте string и возвращает объект Match с методами start() и end() чтобы получить индексы или None |
fullmatch(regex, string) |
возвращает объект Match если regex соответствует string , в противном случае возвращается None |
findall(regex, string) |
находит все вхождения regex в string и возвращает список совпадающих строк |
finditer(regex, string) |
возвращает итератор для цикла по совпадениям с regex в string , например, for m in finditer(regex, string ), где m имеет тип Match |
sub(regex, replacement, string) |
заменяет все совпадения regex в string с replacement и возвращает новую строку с заменами |
split(regex, string) |
возвращает список строк, который содержит части string между всеми совпадениями regex в string |
compile(regex) |
компилирует регулярное выражение в объект регулярного выражения |
Аналогично, модуль glob предоставляет функцию сопоставления с образцом:
-
glob(pattern)
находит файлы, которые удовлетворяют (globbing)pattern
В оставшейся части этой статьи мы сосредоточимся на регулярных выражениях (а не на глобализации).
Вы можете протестировать регулярные выражения в этой статье либо в своей среде Python (например, irb
), либо на одном из следующих сайтов онлайн-регулярных выражений (список не является исчерпывающим, и вы можете найти лучший сайт для своих нужд или вкуса):
- https://regex101.com/
- https://pythex.org/
- http://www.pyregex.com/
- https://www.debuggex.com/
- https://www.regextester.com/
- http://www.regexplanet.com/advanced/python/index.html
Самое простое регулярное выражение — это просто текстовая строка без каких-либо специальных символов, то есть литерала . Литералы — это самая простая форма сопоставления с образцом в регулярных выражениях. Они будут просто успешны всякий раз, когда этот литерал будет найден в тексте, в котором вы ищите. Например, давайте найдем шаблон email
в text
определенном выше, с помощью API Python:
1
2
3
4
5
6
|
>>> regex = 'email' >>> re.match(regex, text) >>> re.search(regex, text) <_sre.SRE_Match object ; span = ( 25 , 30 ), match = 'email' > >>> re.findall(regex, text) [ 'email' ] |
Вы понимаете результаты? re.match(
) пытается найти шаблон в начале text
но text
не начинается со слова email
. re.search()
пытается найти шаблон в любом месте text
и возвращает экземпляр match
говоря, что шаблон был найден, начиная с позиции 25 и заканчивая позицией 30 text
(нумерация начинается с 0
). Наконец, re.findall()
возвращает список совпадающих строк, поэтому 'email
» была найдена только один раз. После вышеприведенного объяснения следующий результат должен быть самоочевидным:
1
2
|
>>> re.findall( '@' , text) [ '@' , '@' , '@' ] |
Регулярные выражения могут использоваться для замены части строки:
1
2
3
4
|
>>> regex = '@' >>> replacement = '#' >>> re.sub(regex, replacement, text) 'You may reach us at this email address: java.info#javacodegeeks.com. Opening hours: #9am-#17pm' |
Обратите внимание, что text
не изменяется.
Чтобы повысить производительность, например, когда мы повторно используем регулярное выражение в ряде совпадений, мы можем скомпилировать регулярное выражение в объект регулярного выражения:
1
2
3
|
>>> regex = re. compile ( '@' ) >>> regex.findall(text) [ '@' , '@' , '@' ] |
2.2 Объект Match
Матчобъект содержит ряд методов:
-
group()
возвращает часть строки, совпадающую со всем регулярным выражением -
start()
возвращает смещение в строке начала матча (начинается с 0) -
end()
возвращает смещение символа после окончания матча -
span()
возвращает 2 кортежаstart()
иend()
1
2
3
4
5
6
7
8
9
|
>>> match = re.search(regex, text) >>> match.group() 'email' >>> match.start() 25 >>> match.end() 30 >>> match.span() ( 25 , 30 ) |
Распространенная ошибка заключается в следующем:
1
2
3
4
5
|
>>> match = re.search( "python" , text) >>> match.group() Traceback (most recent call last): File "<stdin>" , line 1 , in <module> AttributeError: 'NoneType' object has no attribute 'group' |
По этой причине создайте такой полезный метод, как этот:
1
2
3
4
5
6
|
def findMatch(regex, text): match = re.search(regex, text) if match: print (match.group()) else : print ( "Pattern not found!" ) |
Более эффективным, чем re.findall(regex, text)
является re.finditer(regex, text)
. Он возвращает итератор, который позволяет циклически повторять совпадения с регулярным выражением в тексте:
1
2
3
4
|
>>> for m in re.finditer(regex, text): ... print ( '%02d-%02d: %s' % (m.start(), m.end(), m.group( 0 ))) ... 25 - 30 : email |
Переменная цикла for m
— это объект Match
со сведениями о текущем совпадении.
3. Мета-символы
Следующие символы имеют специальные значения в регулярных выражениях:
Мета-символ | Смысл |
. |
Любой отдельный персонаж |
[ ], [^ ] |
Любой отдельный символ в наборе (символов) или нет ( ^ ) в наборе (порядок не имеет значения) |
? |
Квантор: необязательный, т. Е. Ноль или одно из предшествующих регулярных выражений |
* |
Quantifier: ноль или более предыдущего регулярного выражения |
+ |
Квантификатор: одно или несколько из предыдущего регулярного выражения |
| |
Или же |
^ |
Привязать шаблон к началу строки |
$ |
Шаблон привязки к концу строки |
( ) |
Группа персонажей |
{ } |
Quantifier: количество времени предыдущего регулярного выражения. {n} означает ровно n раз {n, m} или {nm} означает n и m раз (включительно) {n,} или {,m} означает не менее n или не более m раз |
\ | Избегает метасимвола, то есть это означает, что следующий за ним символ не является метасимволом. |
Поэтому, если мы хотим выполнить поиск вышеуказанных метасимволов, нам нужно экранировать их, используя метасимвол escape ( \
). В следующей таблице приведены примеры метасимволов escape.
Непечатный символ | Смысл |
\n |
Новая линия |
\r |
Возврат каретки |
\e |
Побег |
\t |
табуляция |
\v |
Вертикальная вкладка |
\f |
Форма подачи |
\uXXXX |
Символы Юникода, например, \u20AC представляет € |
Давайте посмотрим несколько примеров:
Точка ( .
) Соответствует одному символу, кроме символов разрыва строки.
1
2
3
4
|
>>> regex = "gr.y" >>> text = "gr gry grey gray gryy grrrr graaaay gr%y" >>> re.findall(regex, text) [ 'grey' , 'gray' , 'gryy' , 'gr%y' ] |
Если мы хотим сопоставить слова только с 3 символами, которые заканчиваются на y
:
01
02
03
04
05
06
07
08
09
10
11
12
|
>>> regex = "...y" >>> re.findall(regex, text) [ ' gry' , 'grey' , 'gray' , ' gry' , 'aaay' , 'gr%y' ] >>> regex = "gr.*y" >>> re.findall(regex, text) [ 'gr gry grey gray gryy grrrr graaaay gr%y' ] >>> regex = "gr.+y" >>> re.findall(regex, text) [ 'gr gry grey gray gryy grrrr graaaay gr%y' ] >>> regex = "gr.?y" >>> re.findall(regex, text) [ 'gry' , 'grey' , 'gray' , 'gryy' , 'gr%y' ] |
.*
означает любой отдельный символ ноль или более раз .+
означает любой отдельный символ один или несколько раз и .
? означает любой отдельный символ ноль или один раз. Вы можете быть удивлены результатами "gr.*y"
и "gr.+y"
но они верны; они просто соответствуют всему text
потому что он действительно начинается с gr
и заканчивается y
(словом gr%y
), содержащим все остальные символы между ними.
Эти модификаторы называются жадными , потому что они пытаются найти максимально возможное совпадение, чтобы получить максимально возможный результат совпадения. Вы можете преобразовать их в не жадные , добавив дополнительный знак вопроса в квантификатор; например, ??
*?
или +?
, Квантификатор, помеченный как не жадный или неохотный, пытается получить наименьшее возможное соответствие
1
2
3
|
>>> regex = "gr.*?y" >>> re.findall(regex, text) [ 'gr gry' , 'grey' , 'gray' , 'gry' , 'grrrr graaaay' , 'gr%y' ] |
Если нам нужно сопоставить один или несколько метасимволов в нашем text
, то метасимвол escape показывает его использование:
1
2
3
4
|
>>> text = "How is life?" >>> regex = "life\?" >>> findMatch(regex, text) life? |
Вы также можете разбить строку:
1
2
3
4
|
>>> text = "gr, gry, grey, gray, gryy, grrrr, graaaay, gr%y" >>> regex = ", " >>> re.split(regex, text) [ 'gr' , 'gry' , 'grey' , 'gray' , 'gryy' , 'grrrr' , 'graaaay' , 'gr%y' ] |
4. Наборы символов или классы
Но что, если мы хотим найти только правильно написанные слова, то есть только grey
и gray
? Наборы символов (или классы символов) для спасения:
1
2
3
|
>>> regex = "gr[ae]y" >>> re.findall(regex, text) [ 'grey' , 'gray' ] |
Набор символов или класс соответствует только одному из нескольких символов. Порядок символов внутри класса символов не имеет значения. Дефис ( -
) внутри класса символов указывает диапазон символов. Например, [0-9]
соответствует одной цифре от 0 до 9. Вы можете использовать более одного диапазона, например, [A-Za-z]
соответствует одному символу, без учета регистра.
Как бы вы соответствовали адресу электронной почты в первом тексте (повторяется здесь)?
1
|
|
Использование классов персонажей не должно быть таким сложным. Адрес электронной почты состоит из заглавных или строчных букв, например, [A-Za-z]
и, возможно, точки ( .
) В нашем примере, например, [A-Za-z.]
(Не нужно экранировать точку внутри набора символов). , Но это соответствует только одному символу в наборе. Чтобы соответствовать любому количеству этих символов, нам нужно (как вы уже догадались) [A-Za-z.]
+. Таким образом, вы в конечном итоге:
1
2
3
|
Элемент | объяснение |
[A-Za-z.] |
Соответствует любой латинской букве и точке |
+ |
Один или несколько раз |
@ |
соответствует символу @ |
[A-Za-z] |
соответствует любой латинской букве |
+ |
Один или несколько раз |
\. |
Соответствует точке |
[A-Za-z] |
соответствует любой латинской букве |
{2,3} |
2 или 3 раза |
Поздравляем! Вы написали свое первое фактическое регулярное выражение. Обратите внимание, что вам нужно экранировать точку вне класса символов. Чтобы ограничить определенное количество символов, используйте { }
. {2, 3}
означает максимум 2 или 3 символа, так как последняя часть письма обычно состоит из 2 или 3 символов, например, eu
или com
. (Конечно, в настоящее время есть много других доменов, например, info
или ac.u
k, но это оставлено читателю в качестве упражнения).
Мы сопоставили точку в домене, избежав ее:
1
|
>>> regex = "[A-Za-z.]+@[A-Za-z]+\.[A-Za-z]{2,3}" |
Если бы мы этого не сделали, это все равно сработало бы:
1
2
3
|
но
1
2
3
|
>>> email = "java.info@javacodegeeks~com" >>> re.findall(regex, email) [ 'java.info@javacodegeeks~com' ] |
потому что точка (.) соответствует любому символу.
Если вам нужно, чтобы символы не входили в набор символов, используйте символ ^
как в [^A-Za-z]
что означает любой символ, который не является буквой.
Поскольку определенные классы символов используются часто, доступны серии сокращенных классов символов:
стенография | Набор символов | Спички |
\d (\D) |
[0-9] ([^0-9]) |
цифра (не цифра) |
\w (\W) |
[A-Za-z0-9_] ([^A-Za-z0-9_]) |
слово (не слово) |
\s (\S) |
[ \t\r\n\f] ([^ \t\r\n\f]) |
пробелы (без пробелов) |
\A |
Начало строки | |
\Z |
Конец строки | |
\b (\B) |
Граница слова, например, пробелы, запятые, двоеточия, дефисы и т. Д. (Без границы слова) |
Итак, предыдущее регулярное выражение также может быть записано как:
1
2
3
|
и чтобы избежать совпадения "[email protected]"
:
1
2
3
4
|
>>> regex = "\w[\w.]+@[\w]+\.[\w]{2,3}" >>> re.findall(regex, email) [] |
Чтобы найти первое и последнее слово:
1
2
3
4
5
6
|
>>> regex = "^\w+" >>> findMatch(regex, text) You >>> regex = "\w+$" >>> findMatch(regex, text) 17pm |
Давайте посмотрим на другой пример:
1
2
3
4
|
>>> text = "Hello do you want to play Othello?" >>> regex = "[Hh]ello" >>> re.findall(regex, text) [ 'Hello' , 'hello' ] |
Откуда приходит второе 'hello'
? От 'Ot hello '
. Как мы можем сказать Python, что хотим соответствовать только целым словам?
1
2
3
|
>>> regex = r "\b[Hh]ello\b" >>> re.findall(regex, text) [ 'Hello' ] |
Обратите внимание, что мы определяем регулярное выражение как необработанную строку, в противном случае мы должны будем ввести:
1
|
>>> regex = "\\b[Hh]ello\\b" |
В Python 3.4 добавлена новая re.fullmatch()
которая возвращает объект Match
только если регулярное выражение соответствует всей строке, в противном случае возвращается None
. re.fullmatch(regex, text)
аналогичен re.search("\Aregex\Z", text)
. Если text
— пустая строка, тогда fullmatch()
оценивается как True
для любого регулярного выражения, которое может найти совпадение нулевой длины.
Будьте осторожны при использовании отрицательных сокращений внутри квадратных скобок. Например, [\D\S]
не совпадает с [^\d\s]
. Последний соответствует любому символу, который не является ни цифрой, ни пробелом. Первый, однако, соответствует любому символу, который не является цифрой или не является пробелом. Поскольку все цифры не являются пробелами, а все символы пробелов не являются цифрами, [\D\S]
соответствует любому символу; цифра, пробел или другое.
5. Группировка
Представьте, что мы хотим сопоставить только несколько TLD (домен верхнего уровня) с адресами электронной почты:
1
2
3
4
|
>>> regex = "\w[\w.]+@[\w]+\.com|net|org|edu" >>> re.findall(regex, email) [ 'net' ] |
Видимо, |
(или) метасимвол здесь не работает. Нам нужно сгруппировать TLD:
1
2
3
|
Это также может быть полезно, если мы хотим сопоставить имя и домен адреса электронной почты, например
01
02
03
04
05
06
07
08
09
10
11
12
|
>>> regex = "(\w[\w.]+)@([\w]+\.[\w]{2,3})" >>> match = re.search(regex, email) >>> match >>> match.group() >>> match.groups() ( 'java.info' , 'javacodegeeks.net' ) >>> match.group( 1 ) 'java.info' >>> match.group( 2 ) 'javacodegeeks.net' |
match.group()
или match.group(0)
возвращает все совпадение. Чтобы продвинуть пример немного дальше (вложенные группы):
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
>>> regex = "(\w[\w.]+)@([\w]+\.(com|net|org|edu))" >>> match = re.search(regex, email) >>> match >>> match.group() >>> match.groups() ( 'java.info' , 'javacodegeeks.net' , 'net' ) >>> match.group( 1 ) 'java.info' >>> match.group( 2 ) 'javacodegeeks.net' >>> match.group( 3 ) 'net' |
Если вы обратите внимание на группы скобок, вы увидите 3 группы:
1
2
3
|
(\w[\w.] + ) ([\w] + \.(com|net|org|edu)) (com|net|org|edu) |
Результаты match.group()
теперь должны быть очевидны.
Если вам не нужна группа для захвата совпадения, используйте группу без захвата с синтаксисом (?:regex)
. Например, если мы не хотим включать TLD в наш матч:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
>>> regex = "(\w[\w.]+)@([\w]+\.(?:com|net|org|edu))" >>> match = re.search(regex, email) >>> match >>> match.group() >>> match.groups() ( 'java.info' , 'javacodegeeks.net' ) >>> match.group( 1 ) 'java.info' >>> match.group( 2 ) 'javacodegeeks.net' >>> match.group( 3 ) Traceback (most recent call last): File "", line 1 , in IndexError: no such group |
Python был первым языком программирования, который ввел именованные группы захвата. Синтаксис (?P<name>regex)
фиксирует совпадение regex
в имени обратной ссылки. name
должно быть буквенно-цифровой последовательностью, начинающейся с буквы. Вы можете ссылаться на содержимое группы с помощью именованной обратной ссылки \g<name>
.
1
2
3
4
|
>>> regex = "(\w[\w.]+)@([\w]+\.(?P<TLD>com|net|org|edu))" >>> match = re.search(regex, email) >>> match.group( "TLD" ) 'net' |
Python не позволяет нескольким группам использовать одно и то же имя. Это даст ошибку компиляции регулярного выражения.
В качестве упражнения напишите регулярное выражение адреса в Нидерландах, например:
1
|
text = 'George Maduroplein 1, 2584 RZ, The Hague, The Netherlands' |
Убедитесь, что вы можете вернуть независимые совпадения улицы и номера дома, почтового индекса, города или страны.
5.1 Обратные ссылки
Обратные ссылки соответствуют тому же тексту, что и ранее сопоставляемая группой захвата. Возможно, самый известный пример — это регулярное выражение для поиска дублированных слов.
1
2
3
4
|
>>> text = "hello hello world" >>> regex = r "(\w+) \1" >>> re.findall(regex, text) [ 'hello' ] |
В приведенном выше примере мы собираем группу, состоящую из одного или нескольких буквенно-цифровых символов, после чего шаблон пытается найти пробел, и, наконец, у нас есть обратная ссылка \1
, что означает, что он должен совпадать с первым группа (\w+)
. Также обратите внимание на использование необработанных строк, чтобы избежать ввода
1
|
>>> regex = "(\w+) \\1" |
Обратные ссылки можно использовать с первыми 99 группами. Именованные группы, которые мы видели ранее, могут помочь уменьшить сложность в случае многих групп в регулярном выражении. Для обратной ссылки на именованную группу используйте синтаксис (?P=name)
:
1
2
3
|
>>> regex = r "(?P<word>\w+) (?P=word)" >>> re.findall(regex, text) [ 'hello' ] |
6. Согласование режимов
search(regex, string, modes)
и match(regex, string, modes)
принимают третий параметр, называемый режимами соответствия .
Режим соответствия | Группировка письма | объяснение |
re.I или re.IGNORECASE |
i |
Игнорирует дело |
re.S или re.DOTALL |
s |
делает точку (.) совпадением с новой строкой |
re.M или re.MULTILINE |
m |
делает совпадение ^ и $ после и до переноса строки |
re.L или re.LOCALE |
L |
заставляет \w соответствовать всем символам, которые считаются буквами с учетом текущих настроек локали |
re.U или re.UNICODE |
u |
обрабатывает все буквы из всех сценариев как символы слова |
1
2
3
4
|
>>> text = "gry Grey grey gray gryy grrrr graaaay" >>> regex = "gr[ae]y" >>> re.search(regex, text, re.I) <_sre.SRE_Match object ; span = ( 4 , 8 ), match = 'Grey' > |
Используйте |
метасимвол для указания нескольких подходящих режимов.
Или вы можете использовать букву группировки, упомянутую в таблице выше:
1
2
3
4
|
>>> text = "gry Grey grey gray gryy grrrr graaaay" >>> regex = r "(?i)gr[ae]y" >>> re.search(regex, text) <_sre.SRE_Match object ; span = ( 4 , 8 ), match = 'Grey' > |
7. Юникод
Начиная с версии 3.3, Python обеспечивает хорошую поддержку сопоставления с регулярным выражением Unicode. Как упоминалось выше, должен использоваться синтаксис \uFFFF
. Например, чтобы соответствовать одной или нескольким цифрам, заканчивающимся €
:
1
2
3
4
|
>>> text = 'This item costs 33€.' >>> regex = "\d+\u20AC" >>> re.findall(regex, text) [ '33€' ] |
8. Резюме
В этом руководстве мы предоставили обзор регулярных выражений и увидели, как мы можем выполнять регулярные выражения в Python. Python предоставляет модуль re
для этой работы. Мы видели множество примеров для использования в ваших реальных проектах. Я надеюсь, что после этого урока вы будете меньше бояться регулярных выражений. Эта статья ни в коем случае не является исчерпывающей. Заинтересованный читатель должен посмотреть ссылки для более глубокого знания регулярных выражений. Чтобы проверить себя, в чем разница между символами []
, ()
и {}
в регулярных выражениях?
10. Ссылки
- https://www.regular-expressions.info/
- Фридл ДЖЕФ (2006), Освоение регулярных выражений , 3-е изд., О’Рейли.
- Краснов А. (2017), «Учебник по регулярным выражениям Python» , WebCodeGeeks.
- Лопес Ф. и Ромеро В. (2014), Освоение регулярных выражений Python , Packt.