Pyparsing:更改所有解析器元素的默认调试操作

Pyparsing: changing default debug actions for all parser elements

我想通过添加缩进来提高 pyparsing 调试输出的可读性。例如,而不是这个:

Match part at loc 0(1,1)
Match subpart1 at loc 0(1,1)
Match subsubpart1 at loc 0(1,1)
Matched subsubpart1 at loc 10(2,1) -> ...
Matched subpart1 at loc 20(3,1) -> ...
Match subpart2 at loc 20(3,1)
Match subsubpart2 at loc 20(3,1)
Matched subsubpart2 at loc 30(4,1) -> ...
Matched subpart2 at loc 40(5,1) -> ...
Matched part at loc 50(6,1) -> ...

我想让它像这样缩进,以便更好地理解解析过程中发生的事情:

Match part at loc 0(1,1)
    Match subpart1 at loc 0(1,1)
        Match subsubpart1 at loc 0(1,1)
        Matched subsubpart1 at loc 10(2,1) -> ...
    Matched subpart1 at loc 20(3,1) -> ...
    Match subpart2 at loc 20(3,1)
        Match subsubpart2 at loc 20(3,1)
        Matched subsubpart2 at loc 30(4,1) -> ...
    Matched subpart2 at loc 40(5,1) -> ...
Matched part at loc 50(6,1) -> ...

所以在 pyparsing.py 中,我只是将 _defaultStartDebugAction_defaultSuccessDebugAction_defaultExceptionDebugAction 更改为:

pos = -1
def _defaultStartDebugAction( instring, loc, expr ):
    global pos
    pos = pos + 1
    print ("\t" * pos + ("Match " + _ustr(expr) + " at loc " + _ustr(loc) + "(%d,%d)" % ( lineno(loc,instring), col(loc,instring) )))

def _defaultSuccessDebugAction( instring, startloc, endloc, expr, toks ):
    print ("\t" * pos + "Matched " + _ustr(expr) + " -> " + str(toks.asList()))
    global pos
    pos = pos - 1

def _defaultExceptionDebugAction( instring, loc, expr, exc ):
    print ("\t" * pos + "Exception raised:" + _ustr(exc))
    global pos
    pos = pos - 1

(我只是将 pos 表达式和 "\t" * pos 添加到输出以获得我想要的结果)

但是,我不喜欢直接篡改pyparsing 库。另一方面,我不想在我定义的每个解析器元素上都使用 .setDebugActions 方法,我希望它们都使用我修改过的默认调试操作。

有没有一种方法可以实现这一点而不必直接篡改 pyparsing.py 库?

谢谢!

Python 模块就像任何其他 Python 对象一样,您可以使用标准 Python 函数装饰方法来操作它们的符号。通常称为 "monkeypatching",这些可以完全通过您自己的代码完成,而无需修改实际的库源。

实现此更改的最简单方法是覆盖符号。在你的代码中,写:

import pyparsing
# have to import _ustr explicitly, since it does not get pulled in with '*' import
_ustr = pyparsing._ustr

pos = -1
def defaultStartDebugAction_with_indent( instring, loc, expr ):
    global pos
    pos = pos + 1
    print ("\t" * pos + ("Match " + _ustr(expr) + " at loc " + _ustr(loc) + "(%d,%d)" % ( lineno(loc,instring), col(loc,instring) )))

def defaultSuccessDebugAction_with_indent( instring, startloc, endloc, expr, toks ):
    global pos
    print ("\t" * pos + "Matched " + _ustr(expr) + " -> " + str(toks.asList()))
    pos = pos - 1

def defaultExceptionDebugAction_with_indent( instring, loc, expr, exc ):
    global pos
    print ("\t" * pos + "Exception raised:" + _ustr(exc))
    pos = pos - 1

pyparsing._defaultStartDebugAction = defaultStartDebugAction_with_indent
pyparsing._defaultSuccessDebugAction = defaultSuccessDebugAction_with_indent
pyparsing._defaultExceptionDebugAction = defaultExceptionDebugAction_with_indent

或者更简洁的版本是用您的代码作为装饰器包装原始方法:

pos = -1

def incr_pos(fn):
    def _inner(*args):
        global pos
        pos += 1
        print ("\t" * pos , end="")
        return fn(*args)
    return _inner

def decr_pos(fn):
    def _inner(*args):
        global pos
        print ("\t" * pos , end="")
        pos -= 1
        return fn(*args)
    return _inner

import pyparsing
pyparsing._defaultStartDebugAction = incr_pos(pyparsing._defaultStartDebugAction)
pyparsing._defaultSuccessDebugAction = decr_pos(pyparsing._defaultSuccessDebugAction)
pyparsing._defaultExceptionDebugAction = decr_pos(pyparsing._defaultExceptionDebugAction)

这样,如果您更新 pyparsing 并且原始代码发生更改,您的 monkeypatch 将获得更新,而无需修改原始方法的副本。

为了使您的意图更加清晰,并避免重复这些函数名称 (DRY),这将替换最后 3 行:

def monkeypatch_decorate(module, name, deco_fn):
    setattr(module, name, deco_fn(getattr(module, name)))

monkeypatch_decorate(pyparsing, "_defaultStartDebugAction", incr_pos)
monkeypatch_decorate(pyparsing, "_defaultSuccessDebugAction", decr_pos)
monkeypatch_decorate(pyparsing, "_defaultExceptionDebugAction", decr_pos)