为什么我的 python 代码在调试器中如预期的那样 运行 而不是?

Why does my python code run as expected in the debugger but not otherwise?

我在python3.6 写了一个解析器;我尽可能地简化它,同时仍然产生错误:

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() 函数解析令牌流。如果它遇到一个左括号,它会递归(将 OpenBracket 推到令牌流上),这应该具有将括号中的数字视为单独表达式的效果,对其进行解析并添加结果到 result 堆栈。

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

当我 运行 它使用 pdb 但是,并在 parse_tok 的循环内设置断点时,我可以在它处理 ')' 和正确处理程序时小心操作 returns 6.

我认为该错误与从 push_on_stream() 中的令牌流产生有关。

这是解释器中的错误吗?如果是这样,有什么好的解决方法吗?

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

问题正是您描述的地方:

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

一旦你击中右括号,你就会中止配对循环,异常或显式中断。只需删除 break。您期望这里有什么功能?

测试代码:

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

输出值:

6
[]
13
[]
20
[]

假设

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

测试

如果我们将 push_on_stream 更改为:

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)

那么这将对其产生足够的影响以保证两种情况下的正确行为。

结果

错误已修复!

说明

提供的更好。 基本上yield from 并不像您认为的那样工作;当生成器由于 break 语句退出时,yield from 会导致您正在迭代的生成器将其自身标记为耗尽。 (pdb 打断了这个,因为它有点小问题。)这会导致您的解析器在第一个 ) 处终止,因为当第一个 break 语句运行时底层迭代器停止。

您的 push_on_stream 并不像您认为的那样有效。

看到,当 push_on_stream 生成器被回收时,Python 调用生成器上的 close,它向生成器抛出一个 GeneratorExit 以确保任何 finally 块和 __exit__ 方法 运行。由于 push_on_stream 在底层生成器上使用 yield from,如果 push_on_streamyield from 中暂停,这会在底层 GeneratorExit =21=]生成器.

这会立即终止令牌流。在 pdb 中,某些东西导致 push_on_stream 生成器未被收集,从而阻止了这种效果。