Coincidencias de patrones de extracto de Python

129

Python 2.7.1 Estoy tratando de usar la expresión regular de Python para extraer palabras dentro de un patrón

Tengo una cuerda que se ve así

someline abc
someother line
name my_user_name is valid
some more lines

Quiero extraer la palabra "my_user_name". Hago 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>

¿Cómo extraigo my_user_name ahora?

Kannan Ekanath
fuente

Respuestas:

159

Necesitas capturar desde regex. searchpara el patrón, si se encuentra, recupere la cadena usando group(index). Asumiendo que se realicen verificaciones 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'
Ultra instinto
fuente
26
¿Estás seguro de que no es group(0)para el primer partido?
sharshofski
33
Un poco tarde, pero tanto sí como no. group(0)devuelve el texto coincidente, no el primer grupo de captura. El comentario del código es correcto, mientras que parece confundir los grupos de captura y las coincidencias. group(1)devuelve el primer grupo de captura.
andrewgu
1
Me saleNameError: name '_' is not defined
Ian G
Tu segunda línea creo que debería leer _ = p.search(s). Veo que menciona establecer el resultado, _pero el código no refleja eso. Cambié a _ = p.search(s)esa segunda línea y funciona.
Ian G
2
@IanG Lo siento, actualizaré mi respuesta. Por cierto, con una REPL de python estándar, el último resultado se almacena en una variable especial llamada _. No es válido fuera de ningún otro lado.
UltraInstinct
57

Puede usar grupos coincidentes:

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

p.ej

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

Aquí uso en re.findalllugar de re.searchobtener todas las instancias de my_user_name. Utilizando re.search, necesitaría obtener los datos del grupo en el objeto de coincidencia:

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

Como se menciona en los comentarios, es posible que desee que su expresión regular no sea codiciosa:

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

para recoger solo las cosas entre 'name 'y el siguiente ' is valid'(en lugar de permitir que su expresión regular recoja otras ' is valid'en su grupo.

mgilson
fuente
2
Es posible que se requiera una coincidencia no codiciosa ... (a menos que un nombre de usuario pueda tener varias palabras ...)
Jon Clements
@ JonClements - ¿Quieres decir (.*?)? Sí, eso es posible, aunque no es necesario a menos que OP nos usere.DOTALL
mgilson
sí, re.findall('name (.*) is valid', 'name jon clements is valid is valid is valid')probablemente no dará los resultados deseados ...
Jon Clements
¿Esto no funciona para Python 2.7.1? ¿Solo imprime un objeto de patrón?
Kannan Ekanath
@CalmStorm - ¿Qué parte no funciona (probé en python2.7.3)? La parte donde uso .groupes exactamente la misma que la respuesta que aceptó ...
mgilson
16

Podrías usar algo como esto:

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
fuente
10

Quizás sea un poco más corto y 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'
Juan
fuente
9

Quieres un 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
fuente
9

Puede usar grupos (indicados con '('y ')') para capturar partes de la cadena. El group()método del objeto de coincidencia le proporciona los contenidos del 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'

En Python 3.6+ también puede indexar en un objeto de coincidencia en lugar 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
fuente
6

Aquí hay una manera de hacerlo sin usar grupos (Python 3.6 o superior):

>>> re.search('2\d\d\d[01]\d[0-3]\d', 'report_20191207.xml')[0]
'20191207'
wolfovercats
fuente
1
Esto aborda Python Regex, pero no aborda la pregunta específica de OP.
Aleister Tanek Javas Mraz
Además, esto básicamente no agrega nada nuevo a las respuestas existentes que mencionan la sintaxis de indexación 3.6+.
Eugene Yarmash
3

También puede usar un grupo de captura (?P<user>pattern)y acceder al grupo como un diccionario 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
fuente
1

Parece que en realidad estás tratando de extraer un vicio de nombre, simplemente encuentra una coincidencia. Si este es el caso, es útil tener índices de extensión para su coincidencia y recomiendo usarlo re.finditer. Como método abreviado, sabe que la nameparte de su expresión regular es longitud 5 y is validlongitud 9, por lo que puede cortar el texto correspondiente para extraer el nombre.

Nota: en su ejemplo, parece que ses una cadena con saltos de línea, así que eso es lo que se supone a continuación.

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