2011-01-08 32 views
24

Czy istnieje sposób użycia dopasowania regex w strumieniu w python? jakPython regex analizować strumień

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

A ja nie chcę, aby to zrobić poprzez uzyskanie wartości całego łańcucha. Chcę wiedzieć, czy istnieje sposób na dopasowanie wyrażeń regularnych do srtream ("w locie").

+0

Jest to sprzeczne z ideą regex. – SilentGhost

+2

@SlientGhost: Niekoniecznie. Możesz chcieć parsować niektóre (nieskończone) strumienie za pomocą wyrażeń regularnych, zawsze dopasowując na bieżącym początku strumienia i zwracając dopasowania jako iterator (i pochłaniając tylko znaki dopasowane ze strumienia). – MartinStettner

+0

@MartinStettner: Cóż, mógłbyś, gdyby był to automatyczny teoretyczny matcher bez backrefs (i kilka innych rzeczy, takich jak ograniczenia z wyprzedzeniem). Tak długo, jak RE może kompilować się do pojedynczego skończonego automatu (NFA lub DFA), może dopasowywać rzeczy w jednym przejściu, więc może obsłużyć plamienia pasujące do nieskończonego strumienia. (Ale Python używa PCRE, który nie jest automatyczny-teoretyczny i który potrzebuje wszystkich bajtów wcześniej). –

Odpowiedz

15

miałem ten sam problem. Pierwszą myślą było zaimplementowanie klasy LazyString, która działa jak ciąg, ale tylko odczytuje tyle danych ze strumienia, ile jest obecnie potrzebne (zrobiłem to przez ponowne wprowadzenie __getitem__ i __iter__, aby pobrać i zabezpieczyć znaki do najwyższej dostępnej pozycji ...).

To nie zadziałało (otrzymałem "TypeError: oczekiwany ciąg lub bufor" z re.match), więc przyjrzałem się nieco implementacji modułu re w standardowej bibliotece.

Niestety używanie wyrażeń regularnych w strumieniu wydaje się niemożliwe. Rdzeń modułu jest zaimplementowany w C, a ta implementacja oczekuje, że cały sygnał wejściowy znajdzie się w pamięci naraz (prawdopodobnie ze względu na wydajność). Wydaje się, że nie ma prostego sposobu, aby to naprawić.

Miałem również spojrzeć na PYL (Python LEX/YACC), ale ich lexer używa wewnętrznie re, więc to nie rozwiąże problemu.

Istnieje możliwość użycia ANTLR, która obsługuje backend Pythona. Konstruuje lexer używając czystego kodu Pythona i wydaje się być zdolny do działania na strumieniach wejściowych. Ponieważ dla mnie problem nie jest aż tak ważny (nie spodziewam się, że mój wkład będzie bardzo duży ...), prawdopodobnie nie będę tego dalej badał, ale warto byłoby rzucić okiem.

+1

Dobrze zbadane, interesujące. Być może http://www.acooke.org/rxpy/ jest rozsądną alternatywą? –

+0

Właśnie znalazłem inne rozwiązanie: pexpect (http://pexpect.readthedocs.org/en/latest/api/pexpect.html) –

-4

Tak - przy użyciu metody getvalue:

import cStringIO 
import re 

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

cóż, to jest to samo, co karmienie ich struną, zastanawiałem się, czy istnieje jakikolwiek sposób analizowania strumienia – nikitautiu

2

To wydaje się być stary problem. Jak już napisałem do a similar question, możesz chcieć podklasować klasę Matchera mojego rozwiązania streamsearch-py i wykonać dopasowanie regex w buforze. Sprawdź plik kmp_example.py dla szablonu. Jeśli okaże się, że klasyczne dopasowanie Knuth-Morris-Pratt jest wszystkim, czego potrzebujesz, wtedy twój problem zostałby rozwiązany właśnie z tą małą biblioteką open source :-)

3

W konkretnym przypadku pliku, jeśli potrafisz zmapuj plik za pomocą mmap, a jeśli pracujesz z bajtów zamiast kodu Unicode, możesz nakarmić plik mapowany w pamięci na re, jakby był testem bytowym, a to zadziała. Ogranicza się to przestrzenią adresową, a nie pamięcią RAM, więc 64-bitowa maszyna z 8 GB pamięci RAM może dobrze odwzorować pamięć na plik o wielkości 32 GB.

Jeśli możesz to zrobić, to naprawdę fajna opcja. Jeśli nie możesz, musisz zwrócić się do opcji Messiera.


The 3rd-Party regex moduł (nie re) oferuje częściowe wsparcie mecz, który może być użyty do budowy strumieniowe wsparcia ... ale to niechlujny i ma wiele zastrzeżeń. Rzeczy takie jak lookbehinds i ^ nie będą działały, dopasowania na szerokość zero będą trudne do uzyskania, i nie wiem, czy będą one poprawnie współpracować z innymi zaawansowanymi funkcjami regex, a oferty re nie. Mimo to wydaje się, że jest to najbardziej zbliżone do kompletnego rozwiązania.

Jeśli zdasz partial=True do regex.match, regex.fullmatch, regex.search lub regex.finditer, wówczas oprócz zgłoszenia kompletne mecze, regex będzie również zgłosić rzeczy, które mogłyby być mecz, jeśli dane zostały przedłużone:

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

It Zgłasza częściowe dopasowanie zamiast pełnego dopasowania, jeśli więcej danych może zmienić wynik meczu, więc na przykład regex.search(r'[\s\S]*', anything, partial=True) zawsze będzie częściowym dopasowaniem.

Dzięki temu można zachować przesuwane okno danych do dopasowania, przedłużając je po naciśnięciu końca okna i odrzucając zużyte dane od samego początku. Niestety, wszystko, co mogłoby zostać zagubione przez znikające dane od początku łańcucha, nie będzie działać, więc lookbehinds, ^, \b i \B są obecnie niedostępne. Dopasowania o zerowej szerokości również wymagają ostrożnej obsługi. Oto dowód koncepcji, która używa przesuwanego okna nad plikiem lub obiektem podobnym do pliku:

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