Страницы

Поиск по вопросам

среда, 18 декабря 2019 г.

Для чего в Python несколько функций для поиска в регулярных выражениях?

#python #регулярные_выражения


Выражение re.match("regexp", str) полностью идентично re.search("^regexp", str).
И есть еще re.findall(). Зачем плодить сущности без надобности?

Как же насчет "There should be one — and preferably only one — obvious way to do it."?
    


Ответы

Ответ 1



Эти функции несут в себе различную семантику. То же самое можно сказать и про 2 + 2 + 2 vs 2 * 3 или (с натяжкой) про синонимы. Можно заменить 2*3 через 2 + 2 + 2, равно как и re.findall написать через re.search, но это потребует определенных телодвижений. re.match Сопоставляет строку с образцом. Сопоставление происходит с начала текста. За счет этого, функция re.match потенциально быстрее, потому что она точно знает, где начать поиск. In [31]: re.match('a', 'aa') Out[31]: <_sre.SRE_Match object; span=(0, 1), match='a'> In [32]: re.match('a', 'ba') # Returns None In [33]: re.match('a$', 'aa') # Returns None In [34]: re.match('aa$', 'aa') Out[34]: <_sre.SRE_Match object; span=(0, 2), match='aa'> In [35]: re.match('b', 'a\nb\nc', re.MULTILINE) # Returns None In [36]: re.match('ab', 'a\nb\nc', re.MULTILINE) # Returns None In [37]: re.match('a\nb\n', 'a\nb\nc', re.MULTILINE) Out[37]: <_sre.SRE_Match object; span=(0, 4), match='a\nb\n'> re.search Ищет первое вхождение образца в строку. Если флаг re.MULTILINE не включен, символы ^ и $ определяют соответственно начало и конец текста, в противном случае - начало и конец строки. Пример: In [12]: re.search('^b', 'a\nb\nc\n') # Returns None In [13]: re.search('b$', 'a\nb\nc\n') # Returns None In [14]: re.search('^b', 'a\nb\nc\n', re.MULTILINE) Out[14]: <_sre.SRE_Match object; span=(2, 3), match='b'> In [15]: re.search('b$', 'a\nb\nc\n', re.MULTILINE) Out[15]: <_sre.SRE_Match object; span=(2, 3), match='b'> re.findall Находит все неперекрывающие соответствия (возвращает список объектов str): In [24]: re.findall('aa', 'aaa') Out[24]: ['aa'] In [25]: re.findall('aa', 'aaaa') Out[25]: ['aa', 'aa'] По мотивам этого ответа на SO Еще одно важное различие, касающееся только функций re.RegexObject.match и re.RegexObject.search. Обе сигнатуры позволяют передать опциональный параметр pos, начиная с которого будет производиться поиск. Стоит отметить, что это не совсем то же самое, что и срез строки, поскольку символ ^ по-прежнему будет определять настоящее начало строки. Пример различного поведения: In [38]: s Out[38]: 'a ab abc abcd' In [39]: re.compile('a').match(s, pos=2) Out[39]: <_sre.SRE_Match object; span=(2, 3), match='a'> In [40]: re.compile('^a').match(s, pos=2) # None Это может быть полезно, если мы пишем парсер, который не должен пропускать неизвестные символы: In [41]: def tokenize_match(s, patt): ....: at = 0 ....: while at < len(s): ....: m = patt.match(s, pos=at) ....: if not m: ....: raise ValueError("Did not expect character at location {}".format(at)) ....: at = m.end() ....: yield m ....: Функция tokenize_search определена аналогичным образом, за исключением того, что используется patt.search а не patt.match: In [42]: list(tokenize_search('ab421"cdef', re.compile('(ab)|\d|\w'))) Out[42]: [<_sre.SRE_Match object; span=(0, 2), match='ab'>, <_sre.SRE_Match object; span=(2, 3), match='4'>, <_sre.SRE_Match object; span=(3, 4), match='2'>, <_sre.SRE_Match object; span=(4, 5), match='1'>, <_sre.SRE_Match object; span=(6, 7), match='c'>, <_sre.SRE_Match object; span=(7, 8), match='d'>, <_sre.SRE_Match object; span=(8, 9), match='e'>, <_sre.SRE_Match object; span=(9, 10), match='f'>] In [43]: list(tokenize_match('ab421"cdef', re.compile('(ab)|\d|\w'))) --------------------------------------------------------------------------- ValueError Traceback (most recent call last) in () ----> 1 list(tokenize_match('ab421"cdef', re.compile('(ab)|\d|\w'))) in tokenize_match(s, patt) 4 m = patt.match(s, pos=at) 5 if not m: ----> 6 raise ValueError("Did not expect character at location {}".format(at)) 7 at = m.end() 8 yield m ValueError: Did not expect character at location 5 Видим, что функция tokenize_search пропустила незнакомый элемент, в отличие от tokenize_match.

Ответ 2



более краткая версия @soon ответа: Существенной разницы между функциями re.search и re.match нет. Легко можно одну выразить через другую. Кроме удобства, вероятно они существуют потому что метод SRE_Pattern.search в отличии от метода SRE_Pattern.match не позволяет выразить: "искать с текущей позиции": \A ищет с начала строки ^ ищет с начала строки и с начала каждой новой строки (последнее только c включенным re.MULTILINE флагом) Текущая позиция не обязаны быть в начале строки или начале новой строки, поэтому match и search ведут себя по разному: >>> import re >>> search = re.compile(r'\Ax').search >>> match = re.compile(r'x').match >>> text = 'xxx' >>> search(text, pos=2) # not found >>> match(text, pos=2) # found <_sre.SRE_Match object; span=(2, 3), match='x'> Другие функции такие как re.findall() существуют для удобства, так же как удобней написать 2**3 вместо 2*2*2. "There should be one — and preferably only one — obvious way to do it." Три различных ситуации -- три функции, которые являются очевидным решением в каждом случае: re.match удобно использовать, если с начала строки хочется искать (неявный \A): аналог text.startwith(prefix) re.search рекомендуется использовать для поиска по всей строке: аналог substr in text re.fullmatch когда строка целиком должна соответствовать шаблону (неявные \A, \Z): аналог text == another. re.fullmatch() функция добавлена в Python 3.4 -- частный случай считается достаточно распространённым (и разница между $ и \Z достаточно тонкой для новичков), чтобы отдельную функцию завести.

Комментариев нет:

Отправить комментарий