Earlier today, I told a work collease that re.finditer () on Pythonissa yleensä ensisijainen tapa tokenisoida tulo, sen sijaan että lekseri vieritettäisiin käsin iteroitavasta merkistöstä. Hänen mukaansa se vaatii koko tiedoston lukemista muistissa. Paikan päällä sanoin lammasmaisesti, että voit varmaan lukea sen rivi riviltä ja soittaa re: lle.finditer () toistuvasti, jos token regexp ei ylitä viivan rajaa.
tuo on totta, mutta ei suuri vastaus. On käynyt ilmi, että rakentaa ketjutettu re.finditer () on iteroitavasta merkkijonosta mahdollista, joskaan ei aivan suoraviivaista.
ensimmäinen yritys tässä osoittaa pääidean: since re.finditerin () ottelut eivät ole päällekkäisiä, kun kaikki ottelut on käytetty loppuun, seuraava uudelleen.finditerin () tulisi jatkaa jäljellä olevasta verrattomasta merkkijonosta.
def finditer_chained_bad(pattern, strings, flags=0): last = '' for s in strings: m = None for m in re.finditer(pattern, last + s, flags): yield m if not m: last = '' ##1 continue # m is the last match object. last = s
kommentti ##1
voi olla kiistakysymys. If re.finditer () ei ollut tuottanut yhtään ottelua, täytäntöönpanopäätös tässä on hylätä s täysin olettaen, että mikään siinä ei koskaan täsmäisi, mutta voidaan väittää, että mahdollinen ottelu voisi olla syöttömerkkijonon rajan ylittäminen. Jos tämä vaihdettaisiin arvoon last += s
, niin se toimisi, jos potentiaalinen ottelu ylittää tulorajan rajoittamattoman muistin kasvun kustannuksella, jos pitkä tulojakso ei koskaan tuottanut yhtäkään osumaa. Rajaton kasvu on tarpeen, koska se tarkoittaa, että mahdollinen ottelu on suurempi kuin syöttölohkon koko, joten saadaksemme täyden ottelun, meidän täytyy dynaamisesti kasvattaa palan kokoa, mikä on mitä teemme täällä.
toinen ongelma on, että viimeinen ottelu voi olla osaottelu, joka jatkuu seuraavaan jonoon. Jos annamme periksi naiivisti, soittaja saa osittaisen osuman. Tämän havainnollistamiseksi oletetaan, että syöte chunkataan 8-merkkisiksi merkkijonoiksi:
def main(): for m in finditer_chained_bad(r'\w+', ): print m.group(0)
se tulostaa:
helloworld
sana ”maailma” hajoaa merkkijonorajan takia vahingossa kahteen osumaan. Korjataksemme tämän, meidän on vain lykättävä viimeistä ottelua ja käynnistettävä seuraava uudelleen.finditer () viimeisen ottelun alusta. Täysi työkoodi tulee:
def finditer_chained(pattern, strings, flags=0): last_s = '' for s in strings: last_m = None last_s += s for m in re.finditer(pattern, last_s, flags): if last_m: yield last_m last_m = m if not last_m: continue assert last_m is m last_s = s if last_m: yield last_m
ja se tulostaa oikein:
helloworld
tuloksena oleva finditer_chained () hyväksyy tiedostomaisen objektin, koska tiedosto on iteroituva riveistä, ikään kuin re.finditeriä () käytetään koko tiedostossa. Ja se toimii lukematta koko tiedostoa muistiin.