Correspondências de padrões de extração do Python

129

Python 2.7.1 Estou tentando usar a expressão regular do python para extrair palavras dentro de um padrão

Eu tenho uma string que se parece com isso

someline abc
someother line
name my_user_name is valid
some more lines

Eu quero extrair a palavra "my_user_name". Eu faço algo como

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>

Como extraio agora my_user_name?

Kannan Ekanath
fonte

Respostas:

159

Você precisa capturar a partir de regex. searchpara o padrão, se encontrado, recupere a sequência usando group(index). Supondo que sejam realizadas verificações válidas:

>>> 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
fonte
26
Tem certeza de que não é group(0)para a primeira partida?
sharshofski
33
Meio tarde, mas sim e não. group(0)retorna o texto correspondente, não o primeiro grupo de captura. O comentário do código está correto, enquanto você parece estar confundindo grupos de captura e correspondências. group(1)retorna o primeiro grupo de captura.
andrewgu
1
Eu entendoNameError: name '_' is not defined
Ian G
Sua segunda linha, acho que deveria ler _ = p.search(s). Vejo que ele menciona a configuração do resultado, _mas o código não reflete isso. Mudei para _ = p.search(s)para a segunda linha e funciona.
31519 Ian G #
2
@IanG Sinto muito, vou atualizar minha resposta. BTW, com um python padrão REPL, o último resultado é armazenado em uma variável especial chamada _. Não é válido fora de nenhum outro lugar.
UltraInstinct 13/03/19
57

Você pode usar grupos correspondentes:

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

por exemplo

>>> 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']

Aqui eu uso, em re.findallvez de re.searchobter todas as instâncias de my_user_name. Usando re.search, você precisaria obter os dados do grupo no objeto de correspondência:

>>> 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'

Conforme mencionado nos comentários, convém tornar sua regex não gananciosa:

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

para pegar apenas o material entre 'name 'e o próximo ' is valid'(em vez de permitir que o seu regex escolha outro ' is valid'no seu grupo.

mgilson
fonte
2
É possível uma partida não-ganancioso é necessária ... (a menos que um nome de usuário pode ser várias palavras ...)
Jon Clements
@JonClements - Você quer dizer (.*?)? Sim, isso é possível, embora não seja necessário a menos OP-nos usandore.DOTALL
mgilson
sim - re.findall('name (.*) is valid', 'name jon clements is valid is valid is valid')provavelmente não produzirá os resultados desejados ...
Jon Clements
Isso não funciona no Python 2.7.1? Apenas imprime um objeto padrão?
Kannan Ekanath
@CalmStorm - Qual parte não funciona (eu testei em python2.7.3)? A parte onde eu uso .groupé exatamente o mesmo que a resposta que você aceite ...
mgilson
16

Você poderia usar algo como isto:

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')
Apalala
fonte
10

Talvez seja um pouco mais curto e fácil de entender:

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'
John
fonte
9

Você quer um grupo de captura .

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.
Henry Keiter
fonte
9

Você pode usar grupos (indicados com '('e ')') para capturar partes da sequência. O group()método do objeto de correspondência fornece o conteúdo do grupo:

>>> 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'

No Python 3.6+, você também pode indexar em um objeto de correspondência, em vez de usar group():

>>> match[0]  # the entire match 
'name my_user_name is valid'
>>> match[1]  # the first parenthesized subgroup
'my_user_name'
Eugene Yarmash
fonte
6

Aqui está uma maneira de fazer isso sem usar grupos (Python 3.6 ou superior):

>>> re.search('2\d\d\d[01]\d[0-3]\d', 'report_20191207.xml')[0]
'20191207'
wolfovercats
fonte
1
Isso aborda o Python Regex, mas não aborda a questão específica do OP.
Aleister Tanek Javas Mraz 24/01
Além disso, isso basicamente não adiciona nada de novo às respostas existentes, mencionando a sintaxe de indexação 3.6+.
Eugene Yarmash 19/02
3

Você também pode usar um grupo de captura (?P<user>pattern)e acessar o grupo como um dicionário 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
Ryan Stefan
fonte
1

Parece que você está realmente tentando extrair um nome vice simplesmente encontrar uma correspondência. Se for esse o caso, ter índices de span para sua correspondência é útil e eu recomendo o uso re.finditer. Como atalho, você sabe que a nameparte do seu regex tem o comprimento 5 e o is validcomprimento 9, para poder dividir o texto correspondente para extrair o nome.

Nota - No seu exemplo, parece ser suma string com quebras de linha, então é isso que é assumido abaixo.

## 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
fonte