Функция Pyparsing setParseAction не получает аргументов
Я пытаюсь разобрать простой язык запроса = значение. Я на самом деле выполнил это с помощью огромного парсера чудовищ, чтобы затем сделать второй проход, чтобы очистить дерево синтаксического анализа. То, что я хотел бы сделать, это сделать чистый синтаксис снизу вверх, который включает в себя такие вещи, как использование наборов для пар (ключ, вал), поэтому лишние пары устраняются и т.д. Пока я его работал раньше, я не чувствую как я полностью понял, почему пираринг действовал так, как было, поэтому я много работал и т.д., вроде борьбы с зерном.
В настоящее время вот начало моего "упрощенного" синтаксического анализатора:
from pyparsing import *
bool_act = lambda t: bool(t[0])
int_act = lambda t: int(t[0])
def keyval_act(instring, loc, tokens):
return set([(tokens.k, tokens.v)])
def keyin_act(instring, loc, tokens):
return set([(tokens.k, set(tokens.vs))])
string = (
Word(alphas + '_', alphanums + '_')
| quotedString.setParseAction( removeQuotes )
)
boolean = (
CaselessLiteral('true')
| CaselessLiteral('false')
)
integer = Word(nums).setParseAction( int_act )
value = (
boolean.setParseAction(bool_act)
| integer
| string
)
keyval = (string('k') + Suppress('=') + value('v')
).setParseAction(keyval_act)
keyin = (
string('k') + Suppress(CaselessLiteral('in')) +
nestedExpr('{','}', content = delimitedList(value)('vs'))
).setParseAction(keyin_act)
grammar = keyin + stringEnd | keyval + stringEnd
В настоящее время нетерминал "грамматики" - это всего лишь заглушка, в конечном итоге я добавлю вложенные конъюнкции и дизъюнкции к клавишам, чтобы такие поисковые запросы могли быть проанализированы:
a = 1, b = 2 , c in {1,2,3} | d = 4, ( e = 5 | e = 2, (f = 3, f = 4))
Пока что, мне трудно понять, как pyparsing вызывает мои функции setParseAction. Я знаю, что есть какая-то магия с точки зрения того, сколько аргументов передано, но я получаю сообщение об ошибке, когда никакие аргументы вообще не передаются функции. Итак, в настоящее время, если я это сделаю:
grammar.parseString('hi in {1,2,3}')
Я получаю эту ошибку:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.6/site-packages/pyparsing.py", line 1021, in parseString
loc, tokens = self._parse( instring, 0 )
File "/usr/lib/python2.6/site-packages/pyparsing.py", line 894, in _parseNoCache
loc,tokens = self.parseImpl( instring, preloc, doActions )
File "/usr/lib/python2.6/site-packages/pyparsing.py", line 2478, in parseImpl
ret = e._parse( instring, loc, doActions )
File "/usr/lib/python2.6/site-packages/pyparsing.py", line 894, in _parseNoCache
loc,tokens = self.parseImpl( instring, preloc, doActions )
File "/usr/lib/python2.6/site-packages/pyparsing.py", line 2351, in parseImpl
loc, resultlist = self.exprs[0]._parse( instring, loc, doActions, callPreParse=False )
File "/usr/lib/python2.6/site-packages/pyparsing.py", line 921, in _parseNoCache
tokens = fn( instring, tokensStart, retTokens )
File "/usr/lib/python2.6/site-packages/pyparsing.py", line 675, in wrapper
return func(*args[limit[0]:])
TypeError: keyin_act() takes exactly 3 arguments (0 given)
Как вы можете видеть из трассировки, я использую python2.6 и pyparsing 1.5.6
Может ли кто-нибудь дать мне некоторое представление о том, почему функция не получает правильное количество аргументов?
Ответы
Ответ 1
Ну, последняя версия setParseAction
делает некоторую дополнительную магию, но, к сожалению, за счет некоторой простоты разработки. Логика обнаружения аргументов в setParseAction теперь основана на повышении исключений в действии синтаксического разбора до тех пор, пока она не будет вызвана с правильным количеством аргументов, начиная с 3 и дойдя до 0, после чего она просто сдается и вызывает исключение пила.
За исключением этого случая исключение, исходящее из действия разбора, было вызвано несоответствием списка аргументов, но будет реальной ошибкой в вашем коде. Чтобы получить более полное представление об этом, вставьте общий пример, кроме как в действие разбора:
def keyin_act(instring, loc, tokens):
try:
return set([(tokens.k, set(tokens.vs[0]))])
except Exception as e:
print e
И вы получите:
unhashable type: 'set'
Фактически, второй элемент вашего списка, из которого вы создаете возвращаемый набор, сам является множеством, изменяемым контейнером, поэтому не может быть хеширован для включения в набор. Если вы измените это, чтобы вместо этого использовать frozenset, вы получите:
[set([('hi', frozenset([]))])]
Почему фрезенсет пуст? Я предлагаю вам изменить местоположение вашего имени результатов 'vs' на:
nestedExpr('{','}', content = delimitedList(value))('vs')
И теперь проанализированные результаты, возвращаемые путем синтаксического анализа "привет в {1,2,3}", следующие:
[set([('hi', frozenset([([1, 2, 3], {})]))])]
Это что-то вроде беспорядка, если мы отбросим эту строку в верхней части вашего действия синтаксического анализа, вы увидите, что на самом деле содержат разные именованные результаты:
print tokens.dump()
Получаем:
['hi', [1, 2, 3]]
- k: hi
- vs: [[1, 2, 3]]
Итак, 'vs' фактически указывает на список, содержащий список. Поэтому мы, вероятно, хотим построить наш набор из tokens.vs[0]
, а не tokens.vs
. Теперь наши проанализированные результаты выглядят так:
[set([('hi', frozenset([1, 2, 3]))])]
Некоторые другие советы по вашей грамматике:
-
Вместо CaselessLiteral попробуйте использовать CaselessKeyword. Ключевые слова - лучший выбор для ключевых слов грамматики, поскольку они по своей сути избегают ошибочной интерпретации "внутри" внутри, как ключевое слово "in" в вашей грамматике.
-
Не уверен, куда вы направляетесь с возвратом наборов из действий синтаксического анализа - для пар ключ-значение кортеж, вероятно, будет лучше, так как он сохранит порядок токенов. Создайте свои наборы ключей и значений в фазе после анализа.
-
Для других инструментов отладки грамматики проверьте setDebug
и декоратор traceParseAction
.
Ответ 2
Пол уже объяснил, что такое корневая проблема: TypeError
, поднятый вашим действием синтаксического анализа, путает автоматическое определение размера аргумента, которое ожидает ваше действие синтаксического анализа.
Вот что я использую, чтобы избежать такого путаницы: декоратор, который повторно поднимает любой TypeError
, который вызывается украшенной функцией, если функция вызывается снова с меньшим количеством аргументов:
import functools
import inspect
import sys
def parse_action(f):
"""
Decorator for pyparsing parse actions to ease debugging.
pyparsing uses trial & error to deduce the number of arguments a parse
action accepts. Unfortunately any ``TypeError`` raised by a parse action
confuses that mechanism.
This decorator replaces the trial & error mechanism with one based on
reflection. If the decorated function itself raises a ``TypeError`` then
that exception is re-raised if the wrapper is called with less arguments
than required. This makes sure that the actual ``TypeError`` bubbles up
from the call to the parse action (instead of the one caused by pyparsing's
trial & error).
"""
num_args = len(inspect.getargspec(f).args)
if num_args > 3:
raise ValueError('Input function must take at most 3 parameters.')
@functools.wraps(f)
def action(*args):
if len(args) < num_args:
if action.exc_info:
raise action.exc_info[0], action.exc_info[1], action.exc_info[2]
action.exc_info = None
try:
return f(*args[:-(num_args + 1):-1])
except TypeError as e:
action.exc_info = sys.exc_info()
raise
action.exc_info = None
return action
Здесь, как его использовать:
from pyparsing import Literal
@parse_action
def my_parse_action(tokens):
raise TypeError('Ooops')
x = Literal('x').setParseAction(my_parse_action)
x.parseString('x')
Это дает вам:
Traceback (most recent call last):
File "test.py", line 49, in <module>
x.parseString('x')
File "/usr/local/lib/python2.7/dist-packages/pyparsing-2.0.2-py2.7.egg/pyparsing.py", line 1101, in parseString
loc, tokens = self._parse( instring, 0 )
File "/usr/local/lib/python2.7/dist-packages/pyparsing-2.0.2-py2.7.egg/pyparsing.py", line 1001, in _parseNoCache
tokens = fn( instring, tokensStart, retTokens )
File "/usr/local/lib/python2.7/dist-packages/pyparsing-2.0.2-py2.7.egg/pyparsing.py", line 765, in wrapper
ret = func(*args[limit[0]:])
File "test.py", line 33, in action
return f(*args[:num_args])
File "test.py", line 46, in my_parse_action
raise TypeError('Ooops')
TypeError: Ooops
Сравните это с трассировкой, которую вы получите без украшения @parse_action
:
Traceback (most recent call last):
File "test.py", line 49, in <module>
x.parseString('x')
File "/usr/local/lib/python2.7/dist-packages/pyparsing-2.0.2-py2.7.egg/pyparsing.py", line 1101, in parseString
loc, tokens = self._parse( instring, 0 )
File "/usr/local/lib/python2.7/dist-packages/pyparsing-2.0.2-py2.7.egg/pyparsing.py", line 1001, in _parseNoCache
tokens = fn( instring, tokensStart, retTokens )
File "/usr/local/lib/python2.7/dist-packages/pyparsing-2.0.2-py2.7.egg/pyparsing.py", line 765, in wrapper
ret = func(*args[limit[0]:])
TypeError: my_parse_action() takes exactly 1 argument (0 given)