代码之家  ›  专栏  ›  技术社区  ›  Silas Coker

为什么我的Python代码在调试器中按预期运行,而不是按预期运行?

  •  4
  • Silas Coker  · 技术社区  · 7 年前

    我在python3.6中编写了一个解析器;我在生成bug的同时尽可能地简化了它:

    def tokenize(expr):
        for i in expr:
            try:
                yield int(i)
            except ValueError:
                yield i
    
    
    def push_on_stream(obj, stream):
        yield obj
        yield from stream
    
    
    class OpenBracket:
        "just a token value, could have used Ellipsis"
        pass
    
    
    def parse_toks(tokstream):
        result = []
        leading_brak = False
        for tok in tokstream:
            if tok == OpenBracket:
                leading_brak = True
            elif tok == '(':
                result.append(parse_toks(
                    push_on_stream(OpenBracket, tokstream)))
            elif tok == ')':
                if not leading_brak:
                    raise SyntaxError("Very bad ')'.")
                break
            else:
                result.append(tok)
        return sum(result)
    
    
    def test(expr="12(34)21"):
        tokens = tokenize(expr)
        print( parse_toks(tokens) )
        print(list(tokens))
    
    test()
    

    这个例子很简单;其效果应该是添加字符串中的所有数字,包括括号中的数字。

    tokenize()函数生成令牌,parse_tok()函数解析令牌流。如果它遇到一个左括号,它会重复出现(按 开括号 在token流上),它应该具有将括号中的数字视为单独表达式、对其进行解析并将结果添加到 结果 堆叠。

    当我解析代码时,例如在表达式“1(2)3”上,它立即在右括号后结束,返回 三 事实上,令牌流似乎已经结束。

    但是,当我使用pdb运行它,并在parse-tok的循环中设置断点时,我可以在处理')'时小心地执行,并且程序正确地返回 六 .

    我认为这个bug与从push_on_stream()中的token流中让步有关。

    这是解释程序中的错误吗?如果有,是否有一个好的解决方法?

    我是为python-3.6编写的,但我也在另一台机器上用相同的结果在python-3.7上进行了测试。

    3 回复  |  直到 7 年前
        1
  •  3
  •   wizzwizz4    7 年前

    假设

    当 break 语句离开循环,a GeneratorExit 引发异常,该异常通过生成器传播。 pdb 修改它的传播方式,这正是我希望它引入的一种微妙的bug,使它不会耗尽 push_on_stream 是 yield 来自。

    试验

    如果我们改变 推波江溪 来自:

    def push_on_stream(obj, stream):
        yield obj
        yield from stream
    

    到:

    def push_on_stream(obj, stream):
        yield obj
        stream = iter(stream)
        while True:
            yield next(stream)
    

    然后,这将足以保证在这两种情况下的正确行为。

    结果

    修正错误!

    解释

    提供更好的 user2357112's answer . 基本上 , yield from 当发电机因 打破 语句, 收益来自 使正在迭代的生成器将自身标记为已耗尽。( PDB 中断此操作,因为这有点麻烦。)这会导致您的解析器在第一次终止 ) ,因为基础迭代器在第一个 打破 语句运行。

        2
  •  5
  •   user2357112    7 年前

    你的 push_on_stream 不太符合你的想法。

    看,当 推波江溪 生成器被回收,python调用 close 在发电机上, GeneratorExit 进入发电机以确保 finally 块和 __exit__ 方法运行。自从 推波江溪 使用 yield from 在底层生成器上,如果 推动流 被暂停在 产量从 这扔了一个 发电机出口 在底层 tokenize 发电机 .

    这将立即终止令牌流。在PDB中,一些东西导致了 推波江溪 不收集发电机,防止这种影响。

        3
  •  -1
  •   Prune    7 年前

    问题就在你描述的地方:

        elif tok == ')':
            if not leading_brak:
                raise SyntaxError("Very bad ')'.")
            break
    

    只要你点击右括号,你就会中止paring循环,要么是一个异常,要么是一个显式中断。只需移除 break .您希望这里有什么功能?

    测试代码:

    test("1(2)3")
    test()
    test("1(2(4)8)5")
    

    输出值:

    6
    []
    13
    []
    20
    []