我将Python对象定义为"在任何深度都是不可变的" iff
- 它(名义上)是不可变的;和
- 如果它是一个"容器"对象,那么它只包含"在任何深度都不可变"的对象;
例如,((1, 2), (3, 4))
在任何深度都是不可变的,而((1, 2), [3, 4])
则不是(尽管后者由于是元组,"名义上"是不可变的)。
是否有一种合理的方法来测试Python对象是否"在任何深度都是不可变的"?
测试第一个条件相对容易(例如,使用collections.Hashable
类,忽略__hash__
方法不正确实现的可能性),但测试第二个条件比较困难,因为"容器"对象的异质性,以及迭代它们的"内容"的方法…
谢谢!
不存在一般的不变性测试。只有当对象的所有方法都不能改变底层数据时,对象才是不可变的。
更有可能的是,你对哈希性感兴趣,而哈希性通常依赖于不变性。可哈希的容器将递归哈希其内容(即元组和冷冻集)。所以,你的测试相当于运行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'
我想你在寻找这样的东西:
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的观点:没有简单、优雅、通用的方法来做到这一点。
我不确定你到底在找什么。但是使用您的示例数据:
>>> 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
足以验证对象是否可哈希。当然,正如您在问题中已经指出的那样,如果__hash__
被错误地实现为list
的子类,那么此检查将不起作用。
有这样一个测试绝对是有意义的!
考虑'deepcopy()-ing'(或手动克隆()-ing)的时间对象vs简单引用赋值!
假设两个实体需要拥有同一个对象,但是依赖于它没有被改变(dict-keys就是一个很好的例子)。
那么,只有当且仅当,使用引用赋值才安全如果不变性可以验证。
我会考虑递归地测试 之类的东西def check(Candidate):
if isinstance(Candidate, (str, int, long)):
return True
elif isinstance(Candidate, tuple):
return not any(not check(x) for x in Candidate)
else:
return False