为何copy.deepcopy会导致set.difference结果不符合预期?
为什么copy.deepcopy会影响set.difference的执行行为?
近期发现copy.deepcopy会改变set.difference的执行行为:定义了一个Foo类,实现__hash__方法返回id属性值;创建两个id不同的Foo实例存入数组后,对数组执行深拷贝。计算原数组与深拷贝数组的集合差集时,结果非空;但原数组自身的集合差集为空。
复现代码
import copy class Foo: def __init__(self, id: int) -> None: self.id = id def __hash__(self) -> int: return self.id def __repr__(self) -> str: return f"<Foo id={self.id}>" arr = [Foo(1), Foo(2)] arr_copy = copy.deepcopy(arr) print(f"hash(arr) -> {', '.join(f'{hash(x)}' for x in arr)}") print(f"hash(arr_copy) -> {', '.join(f'{hash(x)}' for x in arr_copy)}") arr_diff = set(arr) - set(arr) # Empty set arr_copy_diff = set(arr) - set(arr_copy) # Set with both Foo's??? print(f"{arr_diff=}") print(f"{arr_copy_diff=}") assert arr_diff == arr_copy_diff
问题原因
这并非copy.deepcopy的问题,而是Python集合的元素相等判断规则导致的:
- 集合判断元素是否相同时,会先通过
__hash__比较哈希值; - 哈希值相同时,再调用
__eq__方法确认是否真正相等。
你的Foo类只实现了__hash__,未实现__eq__,此时Python会使用默认的__eq__逻辑——比较对象的内存地址(身份标识)。深拷贝生成的Foo实例虽然id属性和原实例一致、哈希值相同,但它们是不同的对象,内存地址不同,所以默认__eq__会判定二者不相等。
因此:
set(arr) - set(arr)中元素都是同一个对象,差集为空;set(arr) - set(arr_copy)中,原数组元素和拷贝数组元素被认为是不同元素,差集保留原数组的所有元素。
解决方案
在Foo类中同时实现__eq__方法,根据id属性判断实例是否相等:
class Foo: def __init__(self, id: int) -> None: self.id = id def __hash__(self) -> int: return self.id def __eq__(self, other) -> bool: if isinstance(other, Foo): return self.id == other.id return False def __repr__(self) -> str: return f"<Foo id={self.id}>"
这样,只要两个Foo实例的id属性相同,__eq__就会返回True,集合会将它们视为相等元素,差集结果就会符合预期。
内容的提问来源于stack exchange,提问作者Martim Martins
相关产品推荐
相关产品推荐

