Python中递归的可变对象与不可变对象

时间:2018-02-18 14:41:19

标签: python-3.x recursion immutability

这个问题的灵感来自阅读DFS上的Edd Mann's post

据我所知,如果一个可变对象作为函数参数传递,它的值适用于递归调用;如果将不可变对象作为函数参数传递,则其值将固定为每个递归调用。

让我举一些例子来说明我的意思,假设输入是

adj_list = {1: {2, 3}, 2: {1, 4}, 3: {1, 5}, 4: {2, 5}, 5: {3, 4}}

可变案例visitedlist

def dfs(adj_list, s, visited=None):
    if visited is None:
        visited = []
    visited += [s]
    for neighbor in adj_list[s]:
        if neighbor not in visited:
            dfs(adj_list, neighbor, visited)

    return visited

返回:

[1, 2, 4, 5, 3]

不可变案例visitedtuple

def dfs(adj_list, s, visited=None):
    if visited is None:
        visited = tuple()
    visited += (s,)
    for neighbor in adj_list[s]:
        if neighbor not in visited:
            dfs(adj_list, neighbor, visited)

    return visited

返回:

(1,)

不明确的案例visitedlist

def dfs(adj_list, s, visited=None):
    if visited is None:
        visited = [s]
    for neighbor in adj_list[s]:
        if neighbor not in visited:
            dfs(adj_list, neighbor, visited + [neighbor])

    return visited

返回:

[1]

正如您可能观察到的那样,对于模糊的情况,可变list对象被传递给递归调用,但答案与可变案例中的答案不同。我怀疑它是因为list是通过函数参数而不是函数体传递的。

我希望有人可以帮我解决这两个方面,解释一下幕后发生的事情:

  • Python递归的Mutable与Immutable(参数)
  • 仅可变:通过函数参数传递vs通过函数体传递

1 个答案:

答案 0 :(得分:1)

  

我怀疑它是因为列表是通过函数参数传递的   功能体。

不,这是因为您传递了从列表添加中创建的新列表,您没有参考。

  1. 在原始情况下,列表就地扩展(通过+=),这样在所有递归调用结束时,您的列表将收集所有递归调用的结果。将分配给可变对象的变量视为对内存中某些可扩展/可简化对象的引用更容易。

    >>> l = []
    >>> id(l)
    4317346848
    >>> l += [2]
    >>> id(l)
    4317346848 # same list
    
  2. 在不可变的情况下,为每个'就地创建一个新的元组对象。 +=操作(在这种情况下不是这样)。递归调用的结果收集在全新的元组中,与根调用中的元组无关。这就是为什么你只能从第一个函数调用(1,)得到结果。

    >>> c = tuple()
    >>> id(c)
    4316020816
    >>> c += (2,)
    >>> id(c)
    4317473616 # new tuple
    
  3. 不明确的情况类似于第二种情况,创建的列表是一个新列表(visited + [neighbor]),与您的根调用中的原始visited列表几乎没有关系递归。

    >>> id(l+[2])
    4317534832 # new list