Python函数通过备注返回不同的结果

时间:2018-07-29 22:40:02

标签: python recursion optimization path-finding memoization

如果使用记忆修饰符,我的Python寻路函数将返回不同的结果。它会自行返回正确的值,但是在被记忆后会返回错误的值。

我正在谈论的功能如下:

@functools.lru_cache(maxsize=None)
def recursiveTraversal(originIndex, target, steps):
    startingVertex = data[originIndex]

    if startingVertex["ID"] == target:
        if steps == 0:
            return Path(0, [])
        else:
            return None
    else:
        if steps == 0:
            return None
        else:
            potentialPaths = []
            for i in startingVertex["Edges"]:
                nextVertex = data[i]
                nextPath = recursiveTraversal(i, target, steps - 1)
                if nextPath == None:
                    continue
                nextPath.weight += int(nextVertex["Weight"])
                nextPath.vertices.append(i)

                potentialPaths.append(nextPath)
            if len(potentialPaths) > 0:
                minPath = min(potentialPaths, key=lambda x: x.weight)
                return minPath
            else:
                return None

完整的可运行示例can be found here。文件的上部是所有数据,而代码在底部。要重现此内容,只需注释掉第15行,并观察到输出是不同的。

如何获取记忆版本以输出与普通版本相同的内容?

1 个答案:

答案 0 :(得分:1)

问题是您正在修改recursiveTraversal返回值的属性。此函数返回Path个对象,您可以修改它们的属性weightvertices。因此,对于非缓存版本,每次您使用(x, y, z)参数调用该函数时,都会创建一个新的Path(0, [])对象,并稍后在for循环中修改其属性。但是,对于每个(x, y, z)调用,都可以确保从一个新对象开始。现在,对于缓存版本,缓存包装器只是为您提供了先前创建的Path对象(已经修改了{{1})的实例,而不是通过递归树一直提供新的对象。 }和weight属性),并且对它们进行了更进一步的修改(即,修改了缓存)。从以下示例可以看出:

vertices

检查功能# Augment `Path` class with `__repr__`. class Path: # Other methods go here. def __repr__(self): return '{}({}, {})'.format(self.__class__.__name__, repr(self.weight), repr(self.vertices)) data = [ {'ID': '2', 'Weight': 1, 'Edges': [1]}, {'ID': '1', 'Weight': 1, 'Edges': []} ] print(recursiveTraversal(0, '1', 1)) # Prints "Path(1, [1])". print(recursiveTraversal(1, '1', 0)) # Prints "Path(1, [1])". 似乎对于recursiveTraversal,如果目标匹配,它应该返回steps=0。但是,它返回Path(0, [])。发生这种情况是因为先前对Path(1, [1])的调用已调用recursiveTraversal并修改了结果的recursiveTraversal(1, '1', 0)weight属性。在对vertices进行第二次显式调用时,您将获得对该对象的缓存引用。

可能的解决方案

一种可能的解决方案是在进一步修改高速缓存的对象之前创建它们的副本。这样可以防止缓存被修改。

recursiveTraversal(1, '1', 0)