2009-01-15 13 views
12

Una de las mayores molestias que encuentro en Python es la incapacidad del módulo re para guardar su estado sin hacerlo explícitamente en un objeto coincidente. A menudo, uno necesita analizar las líneas y si cumplen una cierta expresión regular extraer los valores de ellos por la misma expresión regular. Me gustaría escribir código como este:Python's re module - ¿estado de guardado?

if re.match('foo (\w+) bar (\d+)', line): 
    # do stuff with .group(1) and .group(2) 
elif re.match('baz whoo_(\d+)', line): 
    # do stuff with .group(1) 
# etc. 

Pero, por desgracia es imposible llegar al objeto coincidente de la llamada anterior a re.match, por lo que este se escribe así:

m = re.match('foo (\w+) bar (\d+)', line) 
if m: 
    # do stuff with m.group(1) and m.group(2) 
else: 
    m = re.match('baz whoo_(\d+)', line) 
    if m: 
    # do stuff with m.group(1) 

que es bastante menos conveniente y se vuelve muy difícil de manejar ya que la lista de elif s crece más.

Una solución hackish sería ajustar el re.match y buscar en mis propios objetos que mantienen el estado en alguna parte. Alguien ha usado esto? ¿Conoce las implementaciones semi estándar (en marcos grandes o algo así)?

¿Qué otras soluciones alternativas puede recomendar? O tal vez, ¿estoy haciendo un mal uso del módulo y podría satisfacer mis necesidades de una manera más limpia?

Gracias de antemano

+0

Ojalá el módulo tuviera un formulario de mantenimiento del estado, también. Pero no considero hackoso crear tu propio objeto que mantenga el estado. Parece una solución limpia, localizada y bien definida para mí. –

Respuesta

4

Probando algunas ideas ...

Parece que lo ideal sería que desee una expresión con efectos secundarios. Si esto estuviera permitido en Python:

if m = re.match('foo (\w+) bar (\d+)', line): 
    # do stuff with m.group(1) and m.group(2) 
elif m = re.match('baz whoo_(\d+)', line): 
    # do stuff with m.group(1) 
elif ... 

... entonces estarías clara y claramente expresando tu intención. Pero no lo es. Si los efectos secundarios fueron permitidos en funciones anidadas, usted podría:

m = None 
def assign_m(x): 
    m = x 
    return x 

if assign_m(re.match('foo (\w+) bar (\d+)', line)): 
    # do stuff with m.group(1) and m.group(2) 
elif assign_m(re.match('baz whoo_(\d+)', line)): 
    # do stuff with m.group(1) 
elif ... 

Ahora, no sólo es que poniendo feo, pero todavía no es válida código Python - la función anidada 'assign_m' no está permitido modificar el variable m en el alcance externo. Lo mejor que puedo llegar a es realmente feo, usando clase anidada que se deja efectos secundarios:

# per Brian's suggestion, a wrapper that is stateful 
class m_(object): 
    def match(self, *args): 
    self.inner_ = re.match(*args) 
    return self.inner_ 
    def group(self, *args): 
    return self.inner_.group(*args) 
m = m_() 

# now 'm' is a stateful regex 
if m.match('foo (\w+) bar (\d+)', line): 
    # do stuff with m.group(1) and m.group(2) 
elif m.match('baz whoo_(\d+)', line): 
    # do stuff with m.group(1) 
elif ... 

Pero eso es claramente un exceso.

Usted migth considerar el uso de una función interna para permitir salidas de ámbito local, lo que le permite eliminar el else anidación:

def find_the_right_match(): 
    # now 'm' is a stateful regex 
    m = re.match('foo (\w+) bar (\d+)', line) 
    if m: 
    # do stuff with m.group(1) and m.group(2) 
    return # <== exit nested function only 
    m = re.match('baz whoo_(\d+)', line) 
    if m: 
    # do stuff with m.group(1) 
    return 

find_the_right_match() 

Esto le permite acoplar anidación = (2 * N-1) a la anidación = 1 , pero es posible que acabe de mover el problema de los efectos secundarios, y es muy probable que las funciones anidadas confundan a la mayoría de los programadores de Python.

Por último, hay maneras de lado libre de efectos de hacer frente a este:

def cond_with(*phrases): 
    """for each 2-tuple, invokes first item. the first pair where 
    the first item returns logical true, result is passed to second 
    function in pair. Like an if-elif-elif.. chain""" 
    for (cond_lambda, then_lambda) in phrases: 
    c = cond_lambda() 
    if c: 
     return then_lambda(c) 
    return None 


cond_with( 
    ((lambda: re.match('foo (\w+) bar (\d+)', line)), 
     (lambda m: 
      ... # do stuff with m.group(1) and m.group(2) 
     )), 
    ((lambda: re.match('baz whoo_(\d+)', line)), 
     (lambda m: 
      ... # do stuff with m.group(1) 
     )), 
    ...) 

y ahora el código apenas incluso ve como Python, y mucho menos comprensible para los programadores de Python (es que Lisp?) .

Creo que la moraleja de esta historia es que Python no está optimizado para este tipo de expresiones idiomáticas. Realmente necesita ser un poco detallado y vivir con un gran factor de anidación de otras condiciones.

+0

LOL y ++ sobre el código Lispy. Bien pensado :-) Pero estaría muy descontento con cualquier programador que escriba código real como este ;-) –

+1

@eliben - heh, gracias. podría haber sido peor ... al menos no intenté usar call/cc! – Aaron

5

te pueden gustar this module que implementa el envoltorio que busca.

+0

Gracias, esto es lo que tenía en mente –

+0

¡Gracias por el puntero! Me gusta el concepto básico de la receta vinculada, pero podría mejorarse si está utilizando una versión más reciente de Python. Soy estrictamente de 2.5+, así que voy a jugar a hacky-hack ahora. –

1

Puede escribir una clase de utilidad para realizar la operación "guardar estado y devolver resultado". No creo que esto sea tan hackish. Es bastante trivial de implementar:

class Var(object): 
    def __init__(self, val=None): self.val = val 

    def set(self, result): 
     self.val = result 
     return result 

y luego usarlo como:

lastMatch = Var() 

if lastMatch.set(re.match('foo (\w+) bar (\d+)', line)): 
    print lastMatch.val.groups() 

elif lastMatch.set(re.match('baz whoo_(\d+)', line)): 
    print lastMatch.val.groups() 
+0

Este es un concepto interesante. Hmm, puede manejar muchos de los casos en los que la inhabilidad de Python de usar la asignación en una expresión duele. –

1
class last(object): 
    def __init__(self, wrapped, initial=None): 
    self.last = initial 
    self.func = wrapped 

    def __call__(self, *args, **kwds): 
    self.last = self.func(*args, **kwds) 
    return self.last 

def test(): 
    """ 
    >>> test() 
    crude, but effective: (oYo) 
    """ 
    import re 
    m = last(re.compile("(oYo)").match) 
    if m("abc"): 
    print("oops") 
    elif m("oYo"): #A 
    print("crude, but effective: (%s)" % m.last.group(1)) #B 
    else: 
    print("mark") 

if __name__ == "__main__": 
    import doctest 
    doctest.testmod() 

last también es adecuado como decorador.

Me di cuenta de que en mi esfuerzo por hacerlo auto-probar y trabajar en 2.5, 2.6 y 3.0, he oscurecido algo la solución real. Las líneas importantes están marcadas con #A y #B arriba, donde usa el mismo objeto para probar (llámelo match o is_somename) y recupere su último valor. Fácil de abusar, pero también es fácil de ajustar y, si no se empuja demasiado, obtiene un código sorprendentemente claro.

1

Basado en las excelentes respuestas a esta pregunta, he preparado el siguiente mecanismo. Parece una forma general de resolver la limitación de "no asignación en condiciones" de Python. El enfoque es la transparencia, implementado por delegación en silencio:

class Var(object): 
    def __init__(self, val=None): 
     self._val = val 

    def __getattr__(self, attr): 
     return getattr(self._val, attr) 

    def __call__(self, arg): 
     self._val = arg 
     return self._val 


if __name__ == "__main__": 
    import re 

    var = Var() 

    line = 'foo kwa bar 12' 

    if var(re.match('foo (\w+) bar (\d+)', line)): 
     print var.group(1), var.group(2) 
    elif var(re.match('baz whoo_(\d+)', line)): 
     print var.group(1) 

En el caso general, esta es una solución segura para los subprocesos, ya que puede crear sus propias instancias de Var. Para una mayor facilidad de uso cuando el enhebrado no es un problema, se puede importar y usar un objeto Var predeterminado. Aquí hay un módulo que sostiene la clase Var:

class Var(object): 
    def __init__(self, val=None): 
     self._val = val 

    def __getattr__(self, attr): 
     return getattr(self._val, attr) 

    def __call__(self, arg): 
     self._val = arg 
     return self._val 

var = Var() 

Y aquí está el código de usuario:

from var import Var, var 
import re 

line = 'foo kwa bar 12' 

if var(re.match('foo (\w+) bar (\d+)', line)): 
    print var.group(1), var.group(2) 
elif var(re.match('baz whoo_(\d+)', line)): 
    print var.group(1) 

Aunque no es seguro para subprocesos, para una gran cantidad de secuencias de comandos simples, esto proporciona un atajo útil.

0

Probablemente la solución más simple es regresar temprano para que pueda volver a crear variables en lugar de tener que hacer una prueba inmediata.

def get_results(line): 
    m = re.match('foo (\w+) bar (\d+)', line) 
    if m: 
     # do stuff with .group(1) and .group(2) 
     return result 
    m = re.match('baz whoo_(\d+)', line) 
    if m: 
     # do stuff with .group(1) 
     return other_result 
    # etc. 

De esta manera se evita el exceso de anidación.