[PYTHON] Apprendre Pyparsing

Qu'est-ce que 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

C'est vrai. Il y a aussi un livre d'O'Reilly, donc je pense que les riches devraient l'acheter. Je l'ai recherché et écrit parce qu'il ne semble y avoir aucune information en japonais. Je l'essaie dans un environnement Python 2.7, donc ce n'est peut-être pas moderne. La version de pyparsing utilisée était la 2.0.2.

Comment utiliser

c'est tout.

Faire un analyseur

Il existe deux façons de créer un analyseur. L'une consiste à créer une instance d'une classe dérivée «ParserElement», et l'autre est de créer un «ParserElement» approprié en utilisant une fonction de générateur à partir d'un module.

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'], {})

Certaines classes dérivées prennent des chaînes et des expressions régulières comme arguments pour les instances, et d'autres prennent la classe pyparsing elle-même comme argument.

Certains opérateurs sont fournis pour ceux qui prennent (une liste de) classes. Il n'y a pas de classe correspondante, mais vous pouvez exprimer la répétition avec *. Le nombre à multiplier et le nombre à multiplier peuvent être interchangés, mais l'un doit être «int »de 0 ou plus. ("Long" n'est pas accepté)

pp.And([p1, p2])        #== p1 + p2        ;Jointure ordonnée
pp.Each([p1, p2])       #== p1 & p2        ;Jointure non ordonnée
pp.MatchFirst([p1, p2]) #== p1 | p2        ;Match prioritaire
pp.Or([p1, p2])         #== p1 ^ p2        ;Match le plus long
pp.NotAny(p)            #== ~ p            ;le déni
p + p + p               #== p * 3 or 3 * p ;Abréviation de liaison

Ce n'est peut-être pas une nomenclature très intelligente, mais il faut s'y habituer. De plus, bien que «chaque» soit dans le désordre, il ne revient pas en arrière et est essayé dans l'ordre depuis le début, donc s'il y a une partie qui chevauche chaque élément, l'entrée ne peut pas être mangée et une exception est émise. Si vous voulez vraiment utiliser l'ensemble de somme, utilisez modérément Match First.

La fonction de générateur était à l'origine spécialisée dans un but précis et était composée en interne d'expressions régulières. Si tout cela se produit, vous devriez penser que la politique de conception de l'analyseur est erronée. Je vais l'omettre ici.

exemple

Essayez d'analyser la ligne $ GPGSV de la sortie NMEA 0183 du récepteur GPS. $ GPGSV a la structure suivante. $GPGSV,3,2,12,16,02,229,,22,21,224,16,24,02,095,,25,52,039,35*73\r\n

Il commence par le caractère de début de «$» et se termine par la séquence de fin de «\ r \ n». Une somme de contrôle au format *% 2h est insérée avant la séquence de fin. Le reste contient 7 à 16 valeurs séparées par des virgules (éventuellement vides). Le nombre total ne peut pas être déterminé à partir du message. Le message est divisé et envoyé, et la quantité d'informations à transmettre est décrite, de sorte qu'elle peut être déterminée par calcul, mais cette fois elle ne sera pas effectuée jusqu'à présent. (Le deuxième champ est le nombre total de messages, le troisième est le numéro du message actuel et le quatrième est la quantité d'informations (nombre de satellites))

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)

La seule chose à retenir est Word (" ab ... ") == Each ([Literal ('a'), Literal ('b'), ...]). Puisque Literal peut contenir des caractères d'une longueur de 1 ou plus, il est recommandé d'alimenter des chaînes de caractères fixes avec this et de consommer des valeurs numériques avec Word.

Le résultat analysé en «ret» est renvoyé. Pour le moment, celui qui peut être converti en entier est converti en entier avec setParseAction. Sinon, il renvoie simplement une liste de chaînes. Si vous voulez manger le tampon mais ne voulez pas qu'il reste dans le résultat, vous pouvez le supprimer en utilisant la méthode .suppress () ou en enveloppant l'élément avec pp.Suppress. Il n'est pas utilisé efficacement, mais il peut être nommé avec la méthode .setResultsName.

Comme lieu pratique ou addictif

La valeur obtenue par la méthode finale parseString est une liste, mais la rupture de cet élément est chaque ParserElement ou l'unité explicitement regroupée par Combine. Par conséquent, si vous combinez simplement en utilisant «Et», la correspondance entre la formule d'assemblage de l'analyseur final et le résultat sera étrange. Vous devriez faire «Combiner» modérément.

Il est lié à la combinaison de Combine, mais si rien n'est fait, le résultat sera une liste de chaînes. Il n'est pas imbriqué, vous ne connaîtrez donc pas le résultat lors d'une mauvaise journée comme ZeroOrMore. Utilisez Group lorsque vous souhaitez imbriquer. Si vous pensez utiliser setParseAction, vous l'utiliserez certainement.

Vous ne pouvez donc pas l'écrire de manière déclarative, mais vous pouvez créer une chaîne de méthodes à la place.

Selon le document, certains prototypes sont définis, mais pour chaque prototype, définissez des arguments et lancez, s'il échoue, essayez le suivant, et s'il s'agit d'une erreur jusqu'à la fin, la dernière erreur est renvoyée comme l'erreur entière, donc elle est mal interne Lorsqu'une exception se produit, il est extrêmement difficile de déboguer. Même si le traitement des arguments est incorrect, la dernière erreur sera une erreur de prototype avec des arguments insuffisants, vous ne saurez donc pas ce que vous dites.

c'est tout.

Recommended Posts

Apprendre Pyparsing
Apprenez la science des données
Apprenez les gestes python