[PYTHON] Lerne Pyparsing

Was ist Pyparsing?

The pyparsing module is an alternative approach to creating and executing simple grammars, vs. the traditional lex/yacc approach, or the use of regular expressions. The pyparsing module provides a library of classes that client code uses to construct the grammar directly in Python code. pyparsing - home

Korrekt. Es gibt auch ein Buch von O'Reilly, also denke ich, dass die Reichen es kaufen sollten. Ich habe danach gesucht und es geschrieben, weil es auf Japanisch keine Informationen zu geben scheint. Ich versuche es in einer Python 2.7-Umgebung, daher ist es möglicherweise nicht modern. Die verwendete Version von Pyparsing war 2.0.2.

Wie benutzt man

das ist alles.

Machen Sie einen Parser

Es gibt zwei Möglichkeiten, einen Parser zu erstellen. Eine besteht darin, eine Instanz einer von "ParserElement" abgeleiteten Klasse zu erstellen, und die andere darin, ein geeignetes "ParserElement" unter Verwendung einer Generatorfunktion aus einem Modul zu erstellen.

import pyparsing as pp

word = pp.Word(pp.alphanums)
comma = pp.Literal(',')
csv = word + pp.ZeroOrMore(comma + word)
# >>> csv.parseString("1,2,3,a,5")
(['1', ',', '2', ',', '3', ',', 'a', ',', '5'], {})

Einige abgeleitete Klassen verwenden Zeichenfolgen und reguläre Ausdrücke als Argumente für Instanzen, andere verwenden die Pyparsing-Klasse selbst als Argument.

Einige Operatoren werden für diejenigen bereitgestellt, die (eine Liste von) Klassen belegen. Es gibt keine entsprechende Klasse, aber Sie können die Wiederholung mit * ausdrücken. Die zu multiplizierende Zahl und die zu multiplizierende Zahl können vertauscht werden, aber eine muss "int" von 0 oder mehr sein. (Long wird nicht akzeptiert)

pp.And([p1, p2])        #== p1 + p2        ;Bestellte Verbindung
pp.Each([p1, p2])       #== p1 & p2        ;Ungeordnete Verbindung
pp.MatchFirst([p1, p2]) #== p1 | p2        ;Prioritätsübereinstimmung
pp.Or([p1, p2])         #== p1 ^ p2        ;Längste Übereinstimmung
pp.NotAny(p)            #== ~ p            ;Verweigerung
p + p + p               #== p * 3 or 3 * p ;Abkürzung für Bindung

Es ist vielleicht keine sehr clevere Nomenklatur, aber man muss sich daran gewöhnen. Auch wenn Each nicht in der richtigen Reihenfolge ist, wird es nicht zurückverfolgt und von Anfang an in der richtigen Reihenfolge versucht. Wenn also ein Teil jedes Element überlappt, kann die Eingabe nicht gegessen werden und es wird eine Ausnahme ausgegeben. Wenn Sie den Summensatz wirklich verwenden möchten, verwenden Sie "Match First" mäßig.

Die Generatorfunktion war ursprünglich auf einen bestimmten Zweck spezialisiert und bestand intern aus regulären Ausdrücken. In diesem Fall sollten Sie der Meinung sein, dass die Entwurfsrichtlinie des Parsers falsch ist. Ich werde es hier weglassen.

Beispiel

Versuchen Sie, die $ GPGSV-Zeile der vom GPS-Empfänger ausgegebenen NMEA 0183 zu analysieren. $ GPGSV hat die folgende Struktur. $GPGSV,3,2,12,16,02,229,,22,21,224,16,24,02,095,,25,52,039,35*73\r\n

Es beginnt mit dem Startzeichen von "$" und endet mit der Endsequenz von "\ r \ n". Vor der Endsequenz wird eine Prüfsumme im Format "*% 2h" eingefügt. Der Rest enthält 7 bis 16 durch Kommas getrennte Werte (möglicherweise leer). Die Gesamtzahl kann nicht aus der Nachricht ermittelt werden. Die Nachricht wird geteilt und gesendet, und die Menge der zu übertragenden Informationen wird beschrieben, so dass sie durch Berechnung bestimmt werden kann, diesmal jedoch nicht ausgeführt wird. (Das zweite Feld gibt die Gesamtzahl der Nachrichten an, das dritte die aktuelle Nachrichtennummer und das vierte die Informationsmenge (Anzahl der Satelliten).)

buf = '$GPGSV,3,2,12,16,02,229,,22,21,224,16,24,02,095,,25,52,039,35*73\r\n'
# checksum (for validation)
ck = "{:02X}".format(reduce(lambda a,b: a^b, [ord(_) for _ in buf[1+buf.find("$"):buf.rfind("*")]]))
# simple parser unit
toint  = lambda a: int(a[0])
c      = pp.Literal(',').suppress()
nemo   = pp.Literal('$GPGSV')
numMsg = pp.Word(pp.nums).setParseAction(toint)
msgNum = pp.Word(pp.nums).setParseAction(toint).setResultsName("msgNum")
numSV  = pp.Word(pp.nums).setParseAction(toint).setResultsName("numSV")
sv     = pp.Word(pp.nums).setParseAction(toint)
# combinated parser unit
toint_maybe = lambda a: int(a[0]) if a[0] else -1
elv    = pp.Combine(pp.Word(pp.nums) ^ pp.Empty()).setParseAction(toint_maybe)
az     = pp.Combine(pp.Word(pp.nums) ^ pp.Empty()).setParseAction(toint_maybe)
cno    = pp.Combine(pp.Word(pp.nums) ^ pp.Empty()).setParseAction(toint_maybe)
cs     = pp.Combine(pp.Literal('*') + pp.Literal(ck)).suppress()
block  = pp.Group(c + sv + c + elv + c + az + c + cno)
blocks = pp.OneOrMore(block)
parser = nemo + c + numMsg + c + msgNum + c + numSV + blocks + cs
# result
ret = parser.parseString(buf)

Das einzige, woran Sie sich erinnern sollten, ist "Word (" ab ... ") == Each ([Literal ('a'), Literal ('b'), ...])". Da "Literal" Zeichen mit einer Länge von 1 oder mehr enthalten kann, wird empfohlen, feste Zeichenfolgen damit einzugeben und numerische Werte mit "Word" zu verwenden.

Das auf "ret" analysierte Ergebnis wird zurückgegeben. Derzeit wird diejenige, die in eine Ganzzahl konvertiert werden kann, mit "setParseAction" in eine Ganzzahl konvertiert. Andernfalls wird einfach eine Liste von Zeichenfolgen zurückgegeben. Wenn Sie den Puffer essen möchten, aber nicht im Ergebnis verbleiben möchten, können Sie ihn entfernen, indem Sie die Methode .suppress () verwenden oder das Element mit pp.Suppress umschließen. Es wird nicht effektiv verwendet, kann aber mit der Methode ".setResultsName" benannt werden.

Als bequemer oder süchtig machender Ort

Der durch die endgültige "parseString" -Methode erhaltene Wert ist eine Liste, aber die Unterbrechung dieses Elements ist jedes ParserElement oder die Einheit, die explizit nach "Combine" gruppiert ist. Wenn Sie also einfach "Und" kombinieren, ist die Entsprechung zwischen der Formel zum Zusammenstellen des endgültigen Parsers und dem Ergebnis seltsam. Sie sollten "Kombinieren" mäßig tun.

Es hängt mit der Kombination von "Kombinieren" zusammen, aber wenn nichts unternommen wird, wird das Ergebnis eine Liste von Zeichenfolgen sein. Es ist nicht verschachtelt, sodass Sie das Ergebnis an einem schlechten Tag wie "ZeroOrMore" nicht kennen. Verwenden Sie "Gruppe", wenn Sie verschachteln möchten. Wenn Sie über die Verwendung von "setParseAction" nachdenken, werden Sie es auf jeden Fall verwenden.

Sie können es also nicht deklarativ schreiben, sondern stattdessen eine Methodenkette erstellen.

Gemäß dem Dokument werden einige Prototypen festgelegt, aber für jeden Prototyp werden Argumente festgelegt und ausgelöst. Wenn dies fehlschlägt, versuchen Sie es mit dem nächsten. Wenn es sich um einen Fehler bis zum Ende handelt, wird der letzte Fehler als gesamter Fehler zurückgegeben, sodass er schlecht intern ist Wenn eine Ausnahme auftritt, ist das Debuggen äußerst schwierig. Selbst wenn die Argumentverarbeitung falsch ist, ist der letzte Fehler ein Prototypfehler mit unzureichenden Argumenten, sodass Sie nicht wissen, was Sie sagen.

das ist alles.

Recommended Posts

Lerne Pyparsing
Lernen Sie Data Science
Lerne Python-Gesten