Python 中的对象标识问题及其解决方案

72 阅读2分钟

在 Python 中,当使用列表作为类的属性时,对象标识可能会出现问题。例如,下面的代码创建了一个包含多个节点的树,每个节点都有一个名称和一个子节点列表:

class Node(object):
    def __init__(self, name, children=[]):
        self.name = name
        self.children = children

    def add_child(self, child):
        self.children.append(child)

    def __str__(self):
        return self.name

def create_tree(num_vertices):
    vs = [Node(str(i)) for i in range(num_vertices)]
    for i in range(num_vertices):
        if 2 * i + 1 < num_vertices:
            vs[i].add_child(vs[2 * i + 1])
        if 2 * i + 2 < num_vertices:
            vs[i].add_child(vs[2 * i + 2])

    return vs[0]

def bfs(top_node, visit):
    """Breadth-first search on a graph, starting at top_node."""
    visited = set()
    queue = [top_node]
    while len(queue):
        curr_node = queue.pop(0)   # Dequeue
        visit(curr_node)           # Visit the node
        visited.add(curr_node)

        # Enqueue non-visited and non-enqueued children
        queue.extend(c for c in curr_node.children
                     if c not in visited and c not in queue)

def visit(tree):
    print(tree)

如果我们尝试多次调用 create_tree() 函数来创建新的树,我们会发现这些树实际上并不是新的,而是之前的树的副本。这是因为在 Node 类的 __init__() 方法中,我们将 children 属性初始化为空列表 []。这意味着每次调用 Node 类的构造函数时,都会使用同一个空的列表作为 children 属性,而不会创建一个新的列表。

  1. 解决方案 为了解决这个问题,我们需要在 Node 类的 __init__() 方法中创建一个新的列表作为 children 属性。我们可以通过将 children 属性的默认值设置为 None 来实现这一点,如下所示:
class Node(object):
    def __init__(self, name, children=None):
        self.name = name
        if children is None:
            children = []
        self.children = children

    def add_child(self, child):
        self.children.append(child)

    def __str__(self):
        return self.name

def create_tree(num_vertices):
    vs = [Node(str(i)) for i in range(num_vertices)]
    for i in range(num_vertices):
        if 2 * i + 1 < num_vertices:
            vs[i].add_child(vs[2 * i + 1])
        if 2 * i + 2 < num_vertices:
            vs[i].add_child(vs[2 * i + 2])

    return vs[0]

def bfs(top_node, visit):
    """Breadth-first search on a graph, starting at top_node."""
    visited = set()
    queue = [top_node]
    while len(queue):
        curr_node = queue.pop(0)   # Dequeue
        visit(curr_node)           # Visit the node
        visited.add(curr_node)

        # Enqueue non-visited and non-enqueued children
        queue.extend(c for c in curr_node.children
                     if c not in visited and c not in queue)

def visit(tree):
    print(tree)

现在,当我们多次调用 create_tree() 函数时,我们将会得到不同的树,因为每次调用都会创建一个新的列表作为 children 属性。