为什么我的python代码在调试器中按预期运行,而不是在其他地方运行?
我用python3.6编写了一个解析器;我尽可能地简化了它,同时仍然产生bug:为什么我的python代码在调试器中按预期运行,而不是在其他地方运行?,python,python-3.x,parsing,debugging,pdb,Python,Python 3.x,Parsing,Debugging,Pdb,我用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 OpenBr
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推到令牌流上),这应该具有将括号中的数字作为单独表达式处理、解析并将结果添加到结果堆栈的效果
当我解析代码时,例如在表达式“1(2)3”上,它立即在右括号后结束,返回3,事实上,令牌流似乎已经结束
但是,当我使用pdb运行它,并在parse_tok中的循环内设置断点时,我可以在它处理“')”时小心地进行操作,并且程序正确地返回6
我认为这个bug与push_on_stream()中的令牌流的屈服有关
这是解释器中的错误吗?如果有,是否有一个好的解决办法
我是为python-3.6编写的,但我也在另一台机器上的python-3.7上进行了测试,结果相同。问题就在您描述的地方:
elif tok == ')':
if not leading_brak:
raise SyntaxError("Very bad ')'.")
break
只要点击右括号,就会中止配对循环,出现异常或显式中断。只需删除中断
。你期望这里有什么功能
测试代码:
test("1(2)3")
test()
test("1(2(4)8)5")
输出值:
6
[]
13
[]
20
[]
假设
当break
语句离开循环时,将引发GeneratorExit
异常,该异常将通过生成器传播pdb
修改了它的传播方式,这正是我希望它引入的一种微妙的bug,导致它不会耗尽push on_流
产生的生成器
试验
如果我们从以下位置更改推送流上的内容
:
def push_on_stream(obj, stream):
yield obj
yield from stream
致:
然后,这将对它产生足够的影响,以保证在这两种情况下都能正确地进行操作
结果
修正错误
解释
提供更好的。基本上,产生的
收益并不像你想象的那样有效;当生成器由于break
语句退出时,yield from
会导致您正在迭代的生成器将自身标记为耗尽。(pdb
会中断此操作,因为这是一个轻微的bug问题。)这会导致解析器在第一个处终止。
,因为当第一个break
语句运行时,底层迭代器会停止。您的推送流
并没有按照您认为应该的方式工作
请参见,当回收push_on_流
生成器时,Python调用生成器上的close
,将GeneratorExit
抛出到生成器中,以确保所有最终
块和\u退出
方法运行。由于push_on_流
在底层生成器上使用yield from
,如果push_on_流
在yield from
中挂起,这将在底层标记化
生成器中抛出一个GeneratorExit
这将立即终止令牌流。在pdb中,某种原因导致push\u on\u流
生成器未被收集,从而阻止了这种效果。这仍然不能解释pdb
中的行为差异。这应该结束递归调用。我不确定它是否做了正确的事情,但在中断
@wizzwizz4的背后有一个合理的想法,你比我能更好地涵盖这一部分——因此我投了赞成票。如果你觉得有必要在一篇帖子中包含完整的答案,欢迎你将我的作品合并到你的文章中。该代码应该包含一个不匹配的右括号(尽管我给出的代码不会包含一个不匹配的左括号)。在中断后,它应该返回一个值到第一个parse_tok实例,该实例应该继续处理令牌,但它不会,除非我使用调试器。@Prune实际上是逻辑的声音。这是由于
的收益率如何处理中断
。我已经编辑了我的答案。有什么特别的原因你不只是,你知道,把数字加起来吗?或者,如果您需要保证正确的括号匹配,有什么特别的原因使您没有保留未闭合括号的计数而不是复杂的递归?@user2357112这显然是从一个更复杂的解析器中提炼出来的。@wizzwizz4:啊,是的,“在产生bug的同时尽可能简化它”部分。我今天有点想睡。因为我写的代码不是用来求和的,而是用来解析相当简单但不平凡的集合表达式。我不想用大量不相关的代码向人们发送垃圾邮件,所以我试图找出问题所在。我只是比你更快地发现了这一点,尽管没有解释…@wizzwizz4:Eh,你认为这是StopIteration
<代码>停止迭代
不是那样工作的。草率的总结。但是是的;你得到学分是因为你不可能在看过我的编辑(没有抄袭)之后就把这些都打出来,而且你的答案会更好。
def push_on_stream(obj, stream):
yield obj
stream = iter(stream)
while True:
yield next(stream)