【问题标题】:Python: Is "pass by reference" acceptable in recursive extraction?Python:递归提取中是否可以接受“通过引用传递”?
【发布时间】:2014-02-06 12:44:30
【问题描述】:

我必须从树结构中提取节点,以便获得树中所有节点的平面列表。我想到了两个选择

干净、可读但可能效率不高,因为有很多列表合并:

def _recursive_extraction(node):
    store = []

    # .get_subnodes() returns only the direct children
    for subnode in node.get_subnodes():
        store.append(subnode)

        subsubnodes = _recursive_extraction(subnode)
        store += subsubnodes

    return store

而且(可能)更有效,但可读性较差。我的老板试图说服我使用它,但我怀疑。它在一个可变对象内部传递,该对象被隐式/隐藏更新,在其他语言中作为“通过引用传递”。

def _recursive_extraction(node, store=None):
    if store is None:
        store = []

    for subnode in node.get_subnodes():
        store.append(subnode)

        # Watch out! store is updated in this func call
        _recursive_extraction(subnode, store)        

    return store

免责声明:代码未经测试,只是表达我的想法。

免责声明 2:“通过引用”是指传递可以更新的可变对象。

侧面编辑:将子节点批量附加到循环之外会更有效,如下所示:

def _recursive_extraction(node, store=None):
    if store is None:
        store = []

    subnodes = node.get_subnodes()
    store += subnodes

    for subnode in subnodes:

        # Watch out! store is updated in this func call
        _recursive_extraction(subnode, store)        

    return store

【问题讨论】:

  • 我会使用第二种形式,是的。所有 python 调用都是“通过引用传递”;该列表是一个可变引用。
  • 我没有看到任何不使用第二个的理由。
  • 好吧,看来Python的大师们都喜欢它:)

标签: python recursion pass-by-reference


【解决方案1】:

我通常会使用第二种情况。它几乎不是“隐含的”;您将列表对象显式传递给递归调用,并正确使用 None 可变参数的默认值。无论对象是否可变,所有 Python 调用都“通过引用”传递。

但是,如果您想坚持使用版本 1,请注意,您也可以使用 extend 保持 store 不变:

store.extend(_recursive_extraction(subnode))

我认为这比添加列表更有效; extend 修改列表,+ 创建一个新列表。

【讨论】:

  • store += liststore.extend(list) 几乎相同:)。我在引号之间说“通过引用”表示对象可以/将被更改(即它是可变的......不可变的对象有点“by val”)
【解决方案2】:

使用这样的存储是使其成为非递归的一半。

def extraction(node):
    nodes_so_far = []
    to_do = [node]

    while to_do:
        current_node = to_do.pop(0)
        nodes_so_far.append(current_node)
        to_do = current_node.get_subnodes() + to_do  # Assuming get_subnodes returns a list

    return nodes_so_far

但现在我已经把它写出来了,我不认为它更好。

【讨论】:

  • 无论如何这都是一种有趣的方法
【解决方案3】:

这可能有点跑题了,但是您是否考虑过使用生成器?

尽可能明确。您指定它应该产生给定的节点值,以及从该节点提取的所有节点:

def _recursive_extraction(node):
    for subnode in node.get_subnodes():
        yield subnode

        yield from _recursive_extraction(subnode)

此外,指定提取算法的优势,无需指定提取数据的容器。

最后但同样重要的是,它可能比第一个解决方案更快/内存效率更高 - 如果您想在提取的节点上迭代一次,那么这就是您所需要的,如果您想存储它,请传递这个生成器到list()。如果构造函数比扩展列表更快(我认为可能是这样),那么您就有了加速,并且不需要中间数据结构。

【讨论】:

  • 仅适用于 Python 3?
  • yield from 仅适用于 Python 3,但在 2 中只是写成 for node in _resursive_extraction(subnode): yield node
  • 生成器是未来,这就是为什么 p3k 在大多数内置函数中使用它们而不是列表;)很高兴我能提供帮助。
【解决方案4】:

您也可以使用迭代方法,因为 Python 函数调用有点慢。

def _iterative_extraction(node):
    stack = [node]
    store = []

    while stack:
        node = stack.pop()

        for subnode in node.get_subnodes():
            store.append(subnode)
            stack.append(subnode)   

    return store

(相同的免责声明,未经测试。)

【讨论】:

  • 我也尝试过这样做,但是这样写,与递归解决方案相比,节点的访问顺序不同(递归版本是深度优先,第一个子节点在前,这不是严格首先访问最后一个子节点),并且固定顺序会使代码不那么整洁。当然,我们不知道这是否重要。
  • 确实如此。它仍然是 DFS,但顺序已更改。添加一个 reverse() 应该可以解决这个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-01-28
  • 1970-01-01
  • 1970-01-01
  • 2011-05-02
  • 2012-10-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多