为什么我的python代码在调试器中按预期运行,而不是在其他地方?

2024-09-30 08:33:03 发布

您现在位置:Python中文网/ 问答频道 /正文

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

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

但是,当我使用pdb运行它,并在parse_tok中的循环内设置断点时,当它处理“)”时,我可以小心地进行操作,程序正确地返回6。你知道吗

我认为这个bug与push\u on\u stream()中的令牌流的屈服有关。你知道吗

这是口译员的错误吗?如果是这样,是否有一个好的解决方法?你知道吗

我为python-3.6编写了它,但我也在python-3.7上的另一台机器上测试了它,得到了相同的结果。你知道吗


Tags: streamparseondefresultpush括号yield
3条回答

问题就在你描述的地方:

    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
[]

你的push_on_stream没有按照你认为应该的方式工作。你知道吗

请参阅,当回收push_on_stream生成器时,Python调用生成器上的close,这会将GeneratorExit抛出到生成器中,以确保任何finally块和__exit__方法都能运行。因为push_on_stream在底层生成器上使用yield from,如果push_on_streamyield from中挂起,这将在底层tokenize生成器中抛出一个GeneratorExit。你知道吗

这将立即终止令牌流。在pdb中,某些东西导致push_on_stream生成器不能被收集,从而阻止了这种效果。你知道吗

假设

break语句离开循环时,将引发一个GeneratorExit异常,该异常通过生成器传播。pdb修改了它的传播方式,这正是我希望它引入的那种微妙的bug,使它不会耗尽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)

那么,这将对它产生足够的影响,以保证在这两种情况下的正确行为。你知道吗

结果

修正了错误!你知道吗

解释

user2357112's answer提供。基本上yield from没有按您认为的方式工作;当生成器由于break语句退出时,yield from会导致您正在迭代的生成器将自己标记为已耗尽。(pdb中断了这个过程,因为这是一个有点麻烦的问题。)这会导致解析器在第一个)终止,因为当第一个break语句运行时,底层迭代器会停止。你知道吗

相关问题 更多 >

    热门问题