You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

含双向引用集合的Node对象deepcopy失败原因及解决方案

深度复制Network/Node类触发AttributeError的原因及正确修复

问题背景

我们构建了包含双向引用的Network和Node类,用于管理网络结构,代码如下:

from copy import deepcopy

class Network:
    def __init__(self):
        self.nodes = []

    def add_node(self, node):
        self.nodes.append(node)

    def add_successor(self, node, successor_node):
        node.add_successor(successor_node)
        successor_node.add_predecessor(node)
        self.add_node(successor_node)

class Node:
    def __init__(self, index):
        self.index = index
        self.predecessors = set()
        self.successors = set()

    def __hash__(self):
        return self.index

    def add_successor(self, successor):
        self.successors.add(successor)

    def add_predecessor(self, predecessor):
        self.predecessors.add(predecessor)

network = Network()
node1 = Node(1)
node2 = Node(2)
network.add_node(node1)
network.add_successor(node1, node2)
deepcopy(network)

执行最后一行deepcopy(network)时触发错误:

... etc ...
  File "/path/to/lib/python3.8/copy.py", line 172, in deepcopy
    y = _reconstruct(x, memo, *rv)
  File "/path/to/lib/python3.8/copy.py", line 264, in _reconstruct
    y = func(*args)
  File "/path/to/deepcopy_mwe.py", line 22, in __hash__
    return self.index
AttributeError: 'Node' object has no attribute 'index'

错误原因

这个错误的核心是**deepcopy的对象重建顺序与自定义__hash__方法的依赖冲突**:

  1. Python的deepcopy在复制对象时,会先通过__new__创建一个空的实例,之后再逐步恢复实例的属性(包括调用__init__或直接赋值);
  2. Node类的predecessors和successors是集合类型,集合要求元素必须可哈希——当复制双向引用的节点时,系统会尝试将未完全初始化的Node实例加入集合,此时会触发__hash__方法;
  3. 但此时这个新的Node实例的index属性还没被赋值(属性恢复还没到这一步),调用__hash__时自然找不到index,抛出AttributeError。

你提到的三个临时操作能消除错误的本质:

  • 改用列表:列表不要求元素可哈希,不会触发__hash__调用;
  • 取消双向引用:没有了循环依赖,复制时不会提前访问未初始化的节点;
  • 删除__hash__:此时Node使用默认的基于对象内存地址的哈希,不依赖实例属性,即使未初始化也能正常返回哈希值。

正确修复方案

以下是几种可靠的修复方式,既保留原有功能,又解决复制错误:

方案1:让__hash__兼容未初始化状态

修改Node的__hash__方法,当index未设置时返回默认哈希值:

class Node:
    # 其他代码不变
    def __hash__(self):
        # 优先使用index,不存在则调用父类默认哈希(基于对象身份)
        return self.index if hasattr(self, 'index') else super().__hash__()

方案2:自定义__deepcopy__控制复制顺序

在Node类中实现__deepcopy__方法,确保先初始化index再处理集合属性:

class Node:
    # 其他代码不变
    def __deepcopy__(self, memo):
        # 先创建带index的新节点,避免哈希依赖问题
        new_node = Node(self.index)
        # 记录到memo字典,避免循环引用导致重复复制
        memo[id(self)] = new_node
        # 递归复制前驱和后继节点
        new_node.predecessors = set(deepcopy(p, memo) for p in self.predecessors)
        new_node.successors = set(deepcopy(s, memo) for s in self.successors)
        return new_node

# 同时给Network补充__deepcopy__,确保节点列表正确复制
class Network:
    # 其他代码不变
    def __deepcopy__(self, memo):
        new_network = Network()
        # 先复制所有节点,确保memo中已有记录
        new_network.nodes = [deepcopy(node, memo) for node in self.nodes]
        return new_network

方案3:用__getstate__和__setstate__控制序列化

通过这两个方法定义对象的序列化状态,确保index先被恢复:

class Node:
    # 其他代码不变
    def __getstate__(self):
        # 返回需要复制的属性,将index放在最前面
        return {
            'index': self.index,
            'predecessors': self.predecessors,
            'successors': self.successors
        }

    def __setstate__(self, state):
        # 先恢复index,确保__hash__调用时有值
        self.index = state['index']
        # 再复制前驱和后继节点
        self.predecessors = set(deepcopy(p) for p in state['predecessors'])
        self.successors = set(deepcopy(s) for s in state['successors'])

内容的提问来源于stack exchange,提问作者LarrySnyder610

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 14:40:28