Соответствие шаблону извлечения Python

129

Python 2.7.1 Я пытаюсь использовать регулярное выражение Python для извлечения слов внутри шаблона

У меня есть строка, которая выглядит так

someline abc
someother line
name my_user_name is valid
some more lines

Я хочу извлечь слово «my_user_name». Я делаю что-то вроде

import re
s = #that big string
p = re.compile("name .* is valid", re.flags)
p.match(s) #this gives me <_sre.SRE_Match object at 0x026B6838>

Как мне теперь извлечь my_user_name?

Каннан Эканатх
источник

Ответы:

159

Вам нужно захватить из регулярного выражения. searchдля шаблона, если он найден, получить строку с помощью group(index). Предполагая, что выполняются действительные проверки:

>>> p = re.compile("name (.*) is valid")
>>> result = p.search(s)
>>> result
<_sre.SRE_Match object at 0x10555e738>
>>> result.group(1)     # group(1) will return the 1st capture.
                        # group(0) will returned the entire matched text.
'my_user_name'
UltraInstinct
источник
26
Вы уверены, что это не group(0)первый матч?
sharshofski
33
Вроде поздно, но и да, и нет. group(0)возвращает совпавший текст, а не первую группу захвата. Комментарий кода правильный, хотя вы, кажется, путаете группы захвата и совпадения. group(1)возвращает первую группу захвата.
andrewgu 07
1
Я получаюNameError: name '_' is not defined
Ian G
Я думаю, вашу вторую строчку следует прочитать _ = p.search(s). Я вижу, что упоминается установка результата, _но код этого не отражает. Я изменил _ = p.search(s)на эту вторую строку, и она работает.
Ian G
2
@IanG Извините, я обновлю свой ответ. Кстати, со стандартным REPL Python последний результат сохраняется в специальной переменной с именем _. Он не действует за пределами другого места.
UltraInstinct
57

Вы можете использовать соответствующие группы:

p = re.compile('name (.*) is valid')

например

>>> import re
>>> p = re.compile('name (.*) is valid')
>>> s = """
... someline abc
... someother line
... name my_user_name is valid
... some more lines"""
>>> p.findall(s)
['my_user_name']

Здесь я использую, re.findallа не re.searchполучаю все экземпляры my_user_name. Используя re.search, вам нужно будет получить данные из группы по объекту соответствия:

>>> p.search(s)   #gives a match object or None if no match is found
<_sre.SRE_Match object at 0xf5c60>
>>> p.search(s).group() #entire string that matched
'name my_user_name is valid'
>>> p.search(s).group(1) #first group that match in the string that matched
'my_user_name'

Как упоминалось в комментариях, вы можете сделать свое регулярное выражение не жадным:

p = re.compile('name (.*?) is valid')

только подбирать материал между 'name 'и следующим ' is valid'(вместо того, чтобы позволить вашему регулярному выражению подбирать другое ' is valid'в вашей группе.

mgilson
источник
2
Возможно, потребуется нежадное сопоставление ... (если имя пользователя не может состоять из нескольких слов ...)
Джон Клементс
@JonClements - Вы имеете в виду (.*?)? Да, это возможно, хотя и не обязательно, если мы не используем OPre.DOTALL
mgilson
да - re.findall('name (.*) is valid', 'name jon clements is valid is valid is valid')вероятно, не даст желаемых результатов ...
Джон Клементс
Это не работает для Python 2.7.1? Он просто печатает объект шаблона?
Каннан Эканат
@CalmStorm - Какая часть не работает (я тестировал на python2.7.3)? Часть, в которой я использую .group, точно такая же, как и в ответе, который вы приняли ...
mgilson
16

Вы можете использовать что-то вроде этого:

import re
s = #that big string
# the parenthesis create a group with what was matched
# and '\w' matches only alphanumeric charactes
p = re.compile("name +(\w+) +is valid", re.flags)
# use search(), so the match doesn't have to happen 
# at the beginning of "big string"
m = p.search(s)
# search() returns a Match object with information about what was matched
if m:
    name = m.group(1)
else:
    raise Exception('name not found')
апалала
источник
10

Может быть, это немного короче и проще для понимания:

import re
text = '... someline abc... someother line... name my_user_name is valid.. some more lines'
>>> re.search('name (.*) is valid', text).group(1)
'my_user_name'
Джон
источник
9

Вам нужна группа захвата .

p = re.compile("name (.*) is valid", re.flags) # parentheses for capture groups
print p.match(s).groups() # This gives you a tuple of your matches.
Генри Кейтер
источник
9

Вы можете использовать группы (обозначенные значком '('и ')') для захвата частей строки. Затем group()метод объекта match выдает вам содержимое группы:

>>> import re
>>> s = 'name my_user_name is valid'
>>> match = re.search('name (.*) is valid', s)
>>> match.group(0)  # the entire match
'name my_user_name is valid'
>>> match.group(1)  # the first parenthesized subgroup
'my_user_name'

В Python 3.6+ вы также можете индексировать объект соответствия вместо использования group():

>>> match[0]  # the entire match 
'name my_user_name is valid'
>>> match[1]  # the first parenthesized subgroup
'my_user_name'
Евгений Ярмаш
источник
6

Вот способ сделать это без использования групп (Python 3.6 или выше):

>>> re.search('2\d\d\d[01]\d[0-3]\d', 'report_20191207.xml')[0]
'20191207'
wolfovercats
источник
1
Это касается Python Regex, но не касается конкретного вопроса OP.
Алистер Танек Джавас Мраз,
Кроме того, это в основном не добавляет ничего нового к существующим ответам, в которых упоминается синтаксис индексации 3.6+.
Евгений Ярмаш
3

Вы также можете использовать группу захвата (?P<user>pattern)и обращаться к ней как к словарю match['user'].

string = '''someline abc\n
            someother line\n
            name my_user_name is valid\n
            some more lines\n'''

pattern = r'name (?P<user>.*) is valid'
matches = re.search(pattern, str(string), re.DOTALL)
print(matches['user'])

# my_user_name
Райан Стефан
источник
1

Похоже, вы на самом деле пытаетесь извлечь название, просто найдите совпадение. В этом случае полезно иметь индексы диапазона для вашего соответствия, и я бы рекомендовал использовать re.finditer. В качестве ярлыка вы знаете, что nameчасть вашего регулярного выражения имеет длину 5 и is validдлину 9, поэтому вы можете нарезать соответствующий текст, чтобы извлечь имя.

Примечание. В вашем примере это выглядит как sстрока с разрывами строки, что и предполагается ниже.

## covert s to list of strings separated by line:
s2 = s.splitlines()

## find matches by line: 
for i, j in enumerate(s2):
    matches = re.finditer("name (.*) is valid", j)
    ## ignore lines without a match
    if matches:
        ## loop through match group elements
        for k in matches:
            ## get text
            match_txt = k.group(0)
            ## get line span
            match_span = k.span(0)
            ## extract username
            my_user_name = match_txt[5:-9]
            ## compare with original text
            print(f'Extracted Username: {my_user_name} - found on line {i}')
            print('Match Text:', match_txt)
chiceman
источник