如何在Python中测试“任意深度的不变性”?

9 投票
4 回答
599 浏览
提问于 2025-04-17 07:05

我在定义一个Python对象为“在任何深度都是不可变的”,这个定义的条件是:

  1. 它本身是(名义上)不可变的;并且
  2. 如果它是一个“容器”对象,那么它里面只包含那些“在任何深度都是不可变的”对象;

举个例子,((1, 2), (3, 4))就是在任何深度都是不可变的,而((1, 2), [3, 4])就不是(即使后者因为是元组,所以名义上是不可变的)。

有没有什么合理的方法来测试一个Python对象是否“在任何深度都是不可变的”?

测试第一个条件相对简单(比如可以使用collections.Hashable类,当然要忽略那些实现不当的__hash__方法的情况),但第二个条件就比较难测试了,因为“容器”对象的种类很多,而且它们的“内容”遍历方式也各不相同……

谢谢!

4 个回答

2

我不太确定你具体在找什么。不过根据你给的例子数据:

>>> a = ((1, 2), (3, 4))
>>> b = ((1, 2), [3, 4])
>>> isinstance(a, collections.Hashable)
True
>>> isinstance(b, collections.Hashable)
True

所以,确实使用 collections.Hashable 不是正确的做法。不过,

>>> hash(a)
5879964472677921951
>>> hash(b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

至少对于这个例子数据来说,使用 hash 就足够用来检查一个对象是否可以被哈希。当然,正如你在问题中提到的,如果某个子类,比如说 list,的 __hash__ 方法实现得不对,那么这个检查就不管用了。

3

我想你可能在寻找这样的东西:

def deeply_hashable(obj):
    try:
        hash(obj)
    except TypeError:
        return False
    try:
        iter(obj)
    except TypeError:
        return True
    return all(deeply_hashable(o) for o in obj)

这里一个明显的问题是,当你遍历一个 dict(字典)时,其实是在遍历它的键,而不是你真正感兴趣的值。字典的键是不可变的,这就让事情变得复杂了。除了特别处理 dict 之外,没有简单的方法可以解决这个问题。而且,这种处理方式对其他类似但不是从 dict 派生的类也没有帮助。最后,我同意 delnan 的看法:没有简单、优雅、通用的方法来做到这一点。

5

没有通用的方法来测试一个对象是否是不可变的。一个对象只有在它的任何方法都不能改变其内部数据时,才被认为是不可变的。

更可能的是,你想知道的是“可哈希性”,这通常和不可变性有关。可哈希的容器会递归地对它们的内容进行哈希处理(比如元组和冻结集合)。所以,你的测试实际上就是运行 hash(obj),如果成功了,那就说明这个对象是深层次可哈希的。

换句话说,你的代码已经使用了最好的测试方法:

>>> a = ((1, 2), (3, 4))
>>> b = ((1, 2), [3, 4])
>>> hash(a)
5879964472677921951
>>> hash(b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

撰写回答