简短的回答是:不,他们没有。正如您链接到的文档所说:
运算符is 和is not 测试对象身份:当且仅当x 和y 是同一个对象时,x is y 为真。
成为“同一个对象”是不允许您覆盖的。如果您的对象与另一个对象不同,则不能假装是。
那么……为什么?让您覆盖is 和/或id 会有什么危害?显然,这样做几乎总是一件愚蠢的事情,但如果你足够努力,Python 会让你做很多愚蠢的事情。
设计常见问题解答和类似文件没有说。但我怀疑这主要是因为它使调试 Python 和一些更深层次的标准库模块变得更容易,因为知道有某种方法可以从解释器中验证两个名称是否确实引用了同一个对象,或者打印出来id 以确保名称不会随着时间的推移而改变,等等。想象一下调试 weakref,甚至是 pickle,没有它。
那么,“同一个对象”究竟是什么意思?好吧,这取决于口译员。显然,必须不可能在语言级别区分同一对象的两个实例,并且可能在解释器级别也是如此(特别是因为有一个定义明确的 API 可以插入到大多数解释器实现中)。
所有主要实现都通过在较低级别遵循身份概念来处理此问题。 CPython 比较 PyObject* 指针的值,Jython 标识比较 Java 引用,PyPy 对对象空间对象执行 is...
值得看一下PyPy source,它要求“x is y iff x 和y 是同一个对象”在两个方向上都是正确的。顶级表达式x is y 为真当且仅当,无论在适当对象空间中的对象wx 和wy 是什么,wy.is_(wx) 为真,并且is_ 实现为wy is wx。所以,x is y 在 N 级 iff y is x 在 N-1 级。
请注意,这意味着您可以很容易地使用 PyPy 构建 Python 方言,其中 is 可以 被覆盖,只需将 is_ 附加到更高的 dunder 方法 __is__等级。但是有一种更简单的方法可以做同样的事情:
def is_(x, y):
if hasattr(x, '__is__'):
return x.__is__(y)
elif hasattr(y, '__is__'):
return y.__is__(x)
else:
return x is y
现在使用is_(x, y) 而不是x is y,看看你是否能在修改解释器的艰苦工作之前找到任何有趣的麻烦(即使在这种情况下并不那么难)。
那么,is 与 id 有什么关系? is 可以在 id 之上实现吗?例如,x is y 只检查 id(x) == id(y)?嗯,id:
返回对象的“身份”。这是一个整数,保证在其生命周期内对于该对象是唯一且恒定的。两个生命周期不重叠的对象可能具有相同的id() 值。
所以,对象的id 在其生命周期内是唯一且恒定的,并且x is y 为真如果它们是同一个对象,因此x is y 为真如果id(x) == id(y),对吧?
嗯,id 可以反弹到您想要的任何位置,并且不允许影响is。如果您非常仔细地制定了定义(请记住,如果您丢弃 builtins 对 id 的引用,那么过去的任何实现都不能保证不再存在,或者如果确实存在,也不能保证正常工作......) ,您可以在id 的默认实现之上定义is。
但这将是一件奇怪的事情。在 CPython 中,id(x) 只是“返回内存中对象的地址”,这与指向内存中对象的指针的值相同。但这只是 CPython 的产物;没有什么说其他实现必须使id 将用于身份比较的基础值作为整数返回。事实上,在用没有指针(可以转换为整数)的语言编写的实现中,你甚至不清楚如何做到这一点。在 PyPy 中,对象的 id 甚至可能是第一次访问并存储在对象空间的字典中时计算的值,由对象本身作为键。
至于__hash__,您误读了文档的重要部分。
[...] x.__hash__() 返回id(x)。
您省略的部分清楚地表明这仅适用于用户定义类的实例(不重新定义__hash__)。对于例如tuple,显然不是这样。简而言之,身份与散列无关,只是对于某些对象,身份是一个方便的散列值。