2011-01-08 26 views
24

¿Hay alguna forma de utilizar la coincidencia de expresión regular en una secuencia en python? comoPython regex parse stream

reg = re.compile(r'\w+') 
reg.match(StringIO.StringIO('aa aaa aa')) 

Y no quiero hacer esto consiguiendo el valor de toda la cadena. Quiero saber si hay alguna forma de unir regex en un srtream (sobre la marcha).

+0

eso va en contra de la idea de regex. – SilentGhost

+2

@SlientGhost: No necesariamente. Es posible que desee analizar un flujo (infinito) utilizando expresiones regulares, siempre coincidiendo con el comienzo actual de la secuencia y devolver las coincidencias como un iterador (y consumir solo los caracteres combinados de la secuencia). – MartinStettner

+0

@MartinStettner: Bueno, podría hacerlo si se tratara de un equilibrador de autómatas sin resúmenes (y algunas otras cosas también, como las limitaciones de anticipación). Siempre que el RE pueda compilarse a un único autómata finito (ya sea NFA o DFA), puede hacer coincidir las cosas en una pasada y, por lo tanto, puede manejar las coincidencias que coincidan con una secuencia infinita. (Pero Python usa PCRE, que no es una teoría de autómatas y necesita todos los bytes allí antes). –

Respuesta

15

que tenían el mismo problema. Lo primero que se pensó fue implementar una clase LazyString, que actúa como una cadena pero que solo lee tantos datos de la ruta como se necesita actualmente (lo hice volviendo a implementar __getitem__ y __iter__ para recuperar y almacenar caracteres hasta la posición más alta a la que se accedió ...)

Esto no funcionó (recibí un "TypeError: string o buffer esperado" de re.match), así que analicé un poco la implementación del módulo re en la biblioteca estándar.

Desafortunadamente no parece posible usar expresiones regulares en una secuencia. El núcleo del módulo se implementa en C y esta implementación espera que toda la entrada esté en la memoria a la vez (supongo que principalmente por razones de rendimiento). Parece que no hay una manera fácil de arreglar esto.

También eché un vistazo a PYL (Python LEX/YACC), pero su lexer usa re internamente, por lo que esto no resolvería el problema.

Una posibilidad podría ser utilizar ANTLR que admita un back-end de Python. Construye el lexer usando el código python puro y parece ser capaz de operar en flujos de entrada. Ya que para mí el problema no es tan importante (no espero que mi entrada sea extensamente grande ...), probablemente no investigue más, pero podría valer la pena verla.

+1

Bien investigado, interesante. Tal vez http://www.acooke.org/rxpy/ es una alternativa razonable? –

+0

Acabo de encontrar otra solución: pexpect (http://pexpect.readthedocs.org/en/latest/api/pexpect.html) –

-4

Sí - usando el método getvalue:

import cStringIO 
import re 

data = cStringIO.StringIO("some text") 
regex = re.compile(r"\w+") 
regex.match(data.getvalue()) 
+3

bueno, eso es lo mismo que darle una cadena, me preguntaba si hay cualquier forma de analizar una secuencia – nikitautiu

2

Esto parece ser un viejo problema. Como he publicado en un a similar question, es posible que desee hacer una subclase de la clase Matcher de mi solución streamsearch-py y realizar la coincidencia de expresiones regulares en el búfer. Consulte kmp_example.py para obtener una plantilla. Si resulta que todo lo que necesita es una combinación clásica de Knuth-Morris-Pratt, entonces su problema se resolvería ahora mismo con esta pequeña biblioteca de fuente abierta :-)

3

En el caso específico de un archivo, si puede memoria- mapee el archivo con mmap y si está trabajando con cadenas de bytes en lugar de Unicode, puede alimentar un archivo mapeado en memoria al re como si fuera una cadena de bytes y simplemente funcionará. Esto está limitado por su espacio de direcciones, no por su RAM, por lo que una máquina de 64 bits con 8 GB de RAM puede mapear en memoria un archivo de 32 GB sin problemas.

Si puede hacerlo, es una buena opción. Si no puede, tiene que recurrir a opciones más complicadas.


El regex módulo 3 ª parte (no re) ofrece apoyo coincidencia parcial, que puede ser utilizado para construir el soporte de streaming ... pero es complicado y tiene un montón de advertencias. Cosas como lookbehinds y ^ no funcionarán, las coincidencias de ancho cero serían difíciles de hacer, y no sé si interactuarían correctamente con otras características avanzadas regex ofertas y re no. Aún así, parece ser lo más parecido a una solución completa disponible.

Si pasa partial=True a regex.match, regex.fullmatch, regex.search, o regex.finditer, a continuación, además de informar partidos completos, regex también informará cosas que podrían ser un partido si los datos se amplió:

In [10]: regex.search(r'1234', '12', partial=True) 
Out[10]: <regex.Match object; span=(0, 2), match='12', partial=True> 

Se Informará una coincidencia parcial en lugar de una coincidencia completa si más datos pueden cambiar el resultado del partido, por lo que, por ejemplo, regex.search(r'[\s\S]*', anything, partial=True) siempre será una coincidencia parcial.

Con esto, puede mantener una ventana deslizante de datos para que coincida, ampliándola al llegar al final de la ventana y descartando los datos consumidos desde el principio. Desafortunadamente, cualquier cosa que se confunda por la desaparición de los datos desde el inicio de la cadena no funcionará, por lo tanto, mira hacia atrás, ^, \b, y \B están fuera. Las coincidencias de ancho cero también necesitarían un manejo cuidadoso. Aquí hay una prueba de concepto que utiliza una ventana deslizante sobre un archivo o un objeto similar a un archivo:

import regex 

def findall_over_file_with_caveats(pattern, file): 
    # Caveats: 
    # - doesn't support^or backreferences, and might not play well with 
    # advanced features I'm not aware of that regex provides and re doesn't. 
    # - Doesn't do the careful handling that zero-width matches would need, 
    # so consider behavior undefined in case of zero-width matches. 
    # - I have not bothered to implement findall's behavior of returning groups 
    # when the pattern has groups. 
    # Unlike findall, produces an iterator instead of a list. 

    # bytes window for bytes pattern, unicode window for unicode pattern 
    # We assume the file provides data of the same type. 
    window = pattern[:0] 
    chunksize = 8192 
    sentinel = object() 

    last_chunk = False 

    while not last_chunk: 
     chunk = file.read(chunksize) 
     if not chunk: 
      last_chunk = True 
     window += chunk 

     match = sentinel 
     for match in regex.finditer(pattern, window, partial=not last_chunk): 
      if not match.partial: 
       yield match.group() 

     if match is sentinel or not match.partial: 
      # No partial match at the end (maybe even no matches at all). 
      # Discard the window. We don't need that data. 
      # The only cases I can find where we do this are if the pattern 
      # uses unsupported features or if we're on the last chunk, but 
      # there might be some important case I haven't thought of. 
      window = window[:0] 
     else: 
      # Partial match at the end. 
      # Discard all data not involved in the match. 
      window = window[match.start():] 
      if match.start() == 0: 
       # Our chunks are too small. Make them bigger. 
       chunksize *= 2