Capturando grupo en una sola línea

Hay un “patrón” conocido para obtener el valor del grupo capturado o una cadena vacía si no hay coincidencia:

match = re.search('regex', 'text') if match: value = match.group(1) else: value = "" 

o:

 match = re.search('regex', 'text') value = match.group(1) if match else '' 

¿Hay una manera simple y pythonica de hacer esto en una línea?

En otras palabras, ¿puedo proporcionar un valor predeterminado para un grupo de captura en caso de que no se encuentre?


Por ejemplo, necesito extraer todos los caracteres alfanuméricos (y _ ) del texto después de la key= cadena:

 >>> import re >>> PATTERN = re.compile('key=(\w+)') >>> def find_text(text): ... match = PATTERN.search(text) ... return match.group(1) if match else '' ... >>> find_text('foo=bar,key=value,beer=pub') 'value' >>> find_text('no match here') '' 

¿Es posible que find_text() sea ​​de una sola línea?

Es solo un ejemplo, estoy buscando un enfoque genérico.

Citando de los documentos de MatchObjects ,

Los objetos coincidentes siempre tienen un valor booleano de True . Como match() y search() devuelven None cuando no hay ninguna coincidencia, puedes probar si hubo una coincidencia con una simple instrucción if:

 match = re.search(pattern, string) if match: process(match) 

Dado que no hay otra opción, y al usar una función, me gustaría presentar esta alternativa

 def find_text(text, matches = lambda x: x.group(1) if x else ''): return matches(PATTERN.search(text)) assert find_text('foo=bar,key=value,beer=pub') == 'value' assert find_text('no match here') == '' 

Es exactamente lo mismo, pero solo la comprobación que necesita hacer está parametrizada por defecto.

Pensando en la solución de @Kevin y las sugerencias de @ devnull en los comentarios, puedes hacer algo como esto

 def find_text(text): return next((item.group(1) for item in PATTERN.finditer(text)), "") 

Esto aprovecha el hecho de que, a next , acepta que el valor predeterminado se devuelva como un argumento. Pero esto tiene la sobrecarga de crear una expresión generadora en cada iteración. Por lo tanto, me quedaría con la primera versión.

Puedes jugar con el patrón, usando una alternativa vacía al final de la cadena en el grupo de captura:

 >>> re.search(r'((?<=key=)\w+|$)', 'foo=bar,key=value').group(1) 'value' >>> re.search(r'((?<=key=)\w+|$)', 'no match here').group(1) '' 

Es posible referirse al resultado de una llamada de función dos veces en una sola línea: crear una expresión lambda y llamar a la función en los argumentos.

 value = (lambda match: match.group(1) if match else '')(re.search(regex,text)) 

Sin embargo, no considero esto especialmente legible. Codifique de forma responsable: si va a escribir un código complicado, ¡deje un comentario descriptivo!

Re: “¿Hay una manera simple y pythonica de hacer esto en una línea?” La respuesta es no . Cualquier forma de hacer que esto funcione en una sola línea (sin definir su propio envoltorio) será más difícil de leer que las formas en las que ya ha presentado. Pero definir su propio envoltorio es perfectamente Pythonic, ya que utiliza dos líneas bastante legibles en lugar de una sola línea difícil de leer.

Versión de una línea:

 if re.findall(pattern,string): pass 

El problema aquí es que desea prepararse para varias coincidencias o asegurarse de que su patrón solo llegue una vez. Versión ampliada:

 # matches is a list matches = re.findall(pattern,string) # condition on the list fails when list is empty if matches: pass 

Así que para su ejemplo “extraiga todos los caracteres alfanuméricos (y _) del texto después de la clave = cadena”:

 # Returns def find_text(text): return re.findall("(?<=key=)[a-zA-Z0-9_]*",text)[0] 

Una línea, una línea … ¿Por qué no puedes escribirlo en 2 líneas?

 getattr(re.search('regex', 'text'), 'group', lambda x: '')(1) 

Su segunda solución si está bien. Haz una función de ella si lo deseas. Mi solución es para propósitos demostrativos y de ninguna manera es pythonica.

Una línea para ti, aunque no del todo python.

 find_text = lambda text: (lambda m: m and m.group(1) or '')(PATTERN.search(text)) 

De hecho, en el lenguaje de progtwigción Scheme, todas las construcciones de variables locales pueden derivarse de las aplicaciones de la función lambda.

Puedes hacerlo como:

 value = re.search('regex', 'text').group(1) if re.search('regex', 'text') else '' 

Aunque no es muy eficiente teniendo en cuenta el hecho de que ejecuta la expresión regular dos veces.

O para ejecutarlo solo una vez como sugirió @Kevin:

value = (lambda match: match.group(1) if match else '')(re.search(regex,text))