Python 与循环引用相比,超过了最大递归深度

Python 与循环引用相比,超过了最大递归深度,python,recursion,comparison,Python,Recursion,Comparison,我与学生一起制作了一个有限状态机的简单演示: s2 = {} s1 = {} s1["0"] = s2 s1["1"] = s1 s2["0"] = s1 s2["1"] = s2 machine = {"start": s1, "accepting": [s1]} def recognize(fsm, in_string): return do_recognize(fsm, fsm["start"], in_string) def do_recognize(fsm, current

我与学生一起制作了一个有限状态机的简单演示:

s2 = {}
s1 = {}
s1["0"] = s2
s1["1"] = s1
s2["0"] = s1
s2["1"] = s2
machine = {"start": s1, "accepting": [s1]}

def recognize(fsm, in_string):
    return do_recognize(fsm, fsm["start"], in_string)

def do_recognize(fsm, current, in_string):
    if len(in_string) == 0:
        return current in fsm["accepting"]

   return do_recognize(fsm, current[in_string[0]] ,
                 in_string[1:])

print (recognize(machine, "0"))
这台机器可以识别0为偶数的字符串,并且可以很好地处理“好”字符串(例如“1”或“010”)。但在上面这样的“坏”字符串上,它会 进入无限循环,然后在fsm[“接受”]中返回电流时堆栈溢出

我能够确定问题在于这两种状态的比较。事实上,我可以通过编写s1==s2生成完全相同的bug。但是s1==s1(良好状态)工作正常


我对正在发生的事情的最好猜测是,它正在做一个深入的比较,并试图遵循s2中的所有引用,这些引用都是循环的。但是为什么它是不对称的(即为什么s1==s1没有同样的问题)?我怎样才能避免它呢?

这个问题与
s1
s2
之间的循环引用有关

这使得无法将
s1
s2
进行比较(就
cmp()
而言,这两本词典的深度是无限的)。考虑以下事项:

print s1 == s1 # immediately returns True, probably due to equal object ids
print s1 == s2 # RuntimeError: maximum recursion depth exceeded in cmp
any(s is current for s in fsm["accepting"])
states = {"s1": {"0": "s2", "1": "s1"},
          "s2": {"0": "s1", "1": "s2"}}
machine = {"start": "s1", "accepting": ["s1"]}

def recognize(fsm, in_string):
    return do_recognize(fsm, fsm["start"], in_string)

def do_recognize(fsm, current, in_string):
    if len(in_string) == 0:
        return current in fsm["accepting"]
    return do_recognize(fsm, states[current][in_string[0]], in_string[1:])
这解释了为什么fsm[“接受”]中的
s1起作用,而fsm[“接受”]
中的
s2断开

解决这个问题的一个简单方法是更换

return current in fsm["accepting"]


这将通过标识来比较状态,而不是尝试通过值来比较两个无限深的字典。

当您比较字典时,字典中的每个项目(键/值对)也会进行比较,因此如果字典之间有循环引用,其中循环引用涉及相同的键,在比较它们时,您将得到此最大递归深度超出错误:

例如,如果您有
s1=={'0':s2}
s2=={'0':s1}
,那么尝试
s1==s2
将导致以下比较,这说明了递归是如何发生的:

s1 == s2 --> s1['0'] == s2['0'] --> s2 == s1 --> s2['0'] == s1['0'] --> s1 == s2 --> ...
像[s2]
中的
s1或[s1]
中的
s2这样的包容测试也会导致这种相等性比较,这就是为什么它会发生在fsm[“接受”]
中当前的
代码中

您可以通过使用身份比较而不是相等比较来解决此递归问题,只需将fsm[“接受”]
中的
current替换为以下内容:

print s1 == s1 # immediately returns True, probably due to equal object ids
print s1 == s2 # RuntimeError: maximum recursion depth exceeded in cmp
any(s is current for s in fsm["accepting"])
states = {"s1": {"0": "s2", "1": "s1"},
          "s2": {"0": "s1", "1": "s2"}}
machine = {"start": "s1", "accepting": ["s1"]}

def recognize(fsm, in_string):
    return do_recognize(fsm, fsm["start"], in_string)

def do_recognize(fsm, current, in_string):
    if len(in_string) == 0:
        return current in fsm["accepting"]
    return do_recognize(fsm, states[current][in_string[0]], in_string[1:])
更好的解决方案可能是不使用循环引用,方法是让状态引用标识符而不是对象本身,例如,您可以使用如下结构:

print s1 == s1 # immediately returns True, probably due to equal object ids
print s1 == s2 # RuntimeError: maximum recursion depth exceeded in cmp
any(s is current for s in fsm["accepting"])
states = {"s1": {"0": "s2", "1": "s1"},
          "s2": {"0": "s1", "1": "s2"}}
machine = {"start": "s1", "accepting": ["s1"]}

def recognize(fsm, in_string):
    return do_recognize(fsm, fsm["start"], in_string)

def do_recognize(fsm, current, in_string):
    if len(in_string) == 0:
        return current in fsm["accepting"]
    return do_recognize(fsm, states[current][in_string[0]], in_string[1:])

你是这门课的讲师吗?你可以通过坚持“扁平比嵌套好”的成语来避免这一点;通过字符串(或int)表示状态,并将转换表表示为单个
dict
。我猜
s1==s1
首先检查字典是否具有相同的
id
,如果具有相同的
True
,则返回
True<代码s1==s2将不得不比较口述的内容。乔恩:是的,我是。谢谢你的建议,拉斯曼,这可能是正确的选择。谢谢NPE,我不知道IDF.J.,谢谢你,回答得很好。第二个解决方案显然是要走的路。