2011-01-08 1 views
24

Existe-t-il un moyen d'utiliser regex match sur un flux en python? commeFlux d'analyse Python regex

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

Et je ne veux pas le faire en obtenant la valeur de la chaîne entière. Je veux savoir s'il existe un moyen de faire correspondre regex sur un srtream (à la volée).

+0

est contraire à l'idée de regex. – SilentGhost

+2

@SlientGhost: Pas nécessairement. Vous pouvez analyser un flux (infini) en utilisant des regexes, toujours en correspondance avec le début courant du flux et renvoyer les correspondances en tant qu'élément d'itération (et en ne consommant que les caractères correspondants du flux). – MartinStettner

+0

@MartinStettner: Eh bien, vous pourriez le faire si c'était un matcher automata-théorique sans backrefs (et quelques autres choses aussi, comme les contraintes de lookahead). Tant que le RE peut compiler vers un seul automate fini (NFA ou DFA), il peut correspondre à des choses en une seule passe et peut donc gérer des taches correspondant à un flux infini. (Mais Python utilise PCRE, qui n'est pas automate-théorétique et qui a besoin de tous les octets auparavant.) –

Répondre

15

J'ai eu le même problème. La première pensée était d'implémenter une classe LazyString, qui agit comme une chaîne mais qui ne lit que la quantité de données du flux actuellement nécessaire (je l'ai fait en réimplémentant __getitem__ et __iter__ pour récupérer et mettre en mémoire les caractères jusqu'à la position la plus haute ...). Cela n'a pas fonctionné (j'ai obtenu un "TypeError: chaîne attendue ou tampon" de re.match), donc j'ai regardé un peu dans l'implémentation du module re dans la bibliothèque standard.

Malheureusement, l'utilisation d'expressions rationnelles sur un flux ne semble pas possible. Le cœur du module est implémenté en C et cette implémentation s'attend à ce que toute l'entrée soit en mémoire à la fois (je suppose principalement pour des raisons de performance). Il semble y avoir aucun moyen facile de résoudre ce problème.

J'ai également jeté un oeil à PYL (Python LEX/YACC), mais leur lexeur utilise re en interne, donc cela ne résoudrait pas le problème.

Une possibilité pourrait être d'utiliser ANTLR qui supporte un backend Python. Il construit le lexeur en utilisant du code python pur et semble pouvoir fonctionner sur des flux d'entrée. Puisque pour moi le problème n'est pas si important (je ne m'attends pas à ce que mes commentaires soient très volumineux ...), je ne ferai probablement pas d'enquête plus poussée, mais ça vaut peut-être le coup d'oeil.

+1

Bien documenté, intéressant. Peut-être que http://www.acooke.org/rxpy/ est une alternative raisonnable? –

+0

Je viens de trouver une autre solution: pexpect (http://pexpect.readthedocs.org/en/latest/api/pexpect.html) –

-4

Oui - en utilisant la méthode getvalue:

import cStringIO 
import re 

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

bien que ça soit la même chose que de le nourrir d'une corde, je me demandais s'il y a n'importe quel moyen d'analyser un flux – nikitautiu

2

Cela semble être un vieux problème. Comme je l'ai posté à un a similar question, vous pouvez sous-classer la classe Matcher de ma solution streamsearch-py et effectuer une correspondance regex dans le tampon. Découvrez l'exemple kmp_pour un modèle. S'il s'avère que l'appariement classique de Knuth-Morris-Pratt est tout ce dont vous avez besoin, alors votre problème serait résolu maintenant avec cette petite bibliothèque open source :-)

3

Dans le cas spécifique d'un fichier, si vous pouvez Mappez le fichier avec mmap et si vous travaillez avec des octets au lieu d'Unicode, vous pouvez envoyer un fichier mappé en mémoire à re comme s'il s'agissait d'un octet et il fonctionnera. Ceci est limité par votre espace d'adressage, pas votre RAM, donc une machine 64 bits avec 8 Go de RAM peut très bien mapper un fichier de 32 Go.

Si vous pouvez le faire, c'est une très bonne option. Si vous ne pouvez pas, vous devez vous tourner vers des options plus désordonnées.


La 3ème partie Module regex (non re) offre un support de correspondance partielle, qui peut être utilisé pour construire un soutien continu ... mais il est en désordre et a beaucoup de mises en garde. Des choses comme lookbehinds et ^ ne fonctionnera pas, des allumettes de largeur nulle seraient difficiles à obtenir correctement, et je ne sais pas si cela interagirait correctement avec d'autres fonctionnalités avancées et re et non regex offres. Pourtant, il semble être la chose la plus proche d'une solution complète disponible.

Si vous passez partial=True à regex.match, regex.fullmatch, regex.search ou regex.finditer, puis, en plus de rapports matchs complets, regex rendra également compte des choses qui pourraient être un match si les données a été prolongée:

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

Il signalera une correspondance partielle au lieu d'une correspondance complète si plus de données peuvent modifier le résultat du match. Par exemple, regex.search(r'[\s\S]*', anything, partial=True) sera toujours une correspondance partielle. Avec cette option, vous pouvez conserver une fenêtre de données glissante correspondant, en la prolongeant lorsque vous atteignez la fin de la fenêtre et en supprimant les données consommées depuis le début. Malheureusement, tout ce qui pourrait être perturbé par la disparition des données au début de la chaîne ne fonctionnera pas, donc les fenêtres de recherche, ^, \b, et \B sont désactivées. Les allumettes à largeur nulle devraient également être manipulées avec soin. Voici une preuve de concept qui utilise une fenêtre glissante sur un fichier ou un objet de type fichier:

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