【问题标题】:Does the `is` operator use a __magic__ method in Python?`is` 运算符是否在 Python 中使用 __magic__ 方法?
【发布时间】:2013-03-14 00:21:54
【问题描述】:

is 运算符用于身份测试。

我想知道is 运算符和id() 函数是否调用任何__magic__ 方法,就像== 调用__eq__ 的方式一样。

我在查看__hash__ 时玩得很开心:

class Foo(object):
    def __hash__(self):
        return random.randint(0, 2 ** 32)

a = Foo()
b = {}
for i in range(5000):
    b[a] = i

想想dictbb[a]的值

d[a] 的每个后续查找都是 KeyError 或随机整数。

但作为docs on the special methods 状态

[默认实现] x.__hash__() 返回 id(x)。

所以两者之间存在 关系,但恰恰相反。

我在这里看到了很多 isid 上的 questionsanswers 帮助了许多 confused 的想法,但我找不到这个问题的答案。

【问题讨论】:

  • 默认hash 的定义不正确。在 CPython 中,PyBaseObject_Type.tp_hashlong _Py_HashPointer(void *p) {long x; size_t y = (size_t)p; y = (y >> 4) | (y << (8 * SIZEOF_VOID_P - 4)); x = (long)y; if (x == -1) x = -2; return x;} 这会将地址旋转 4 位以减少哈希冲突,因为地址的底部 2-4 位可能是 0。

标签: python operators


【解决方案1】:

简短的回答是:不,他们没有。正如您链接到的文档所说:

运算符isis not 测试对象身份:当且仅当xy 是同一个对象时,x is y 为真。

成为“同一个对象”是不允许您覆盖的。如果您的对象与另一个对象不同,则不能假装是。


那么……为什么?让您覆盖is 和/或id 会有什么危害?显然,这样做几乎总是一件愚蠢的事情,但如果你足够努力,Python 会让你做很多愚蠢的事情。

设计常见问题解答和类似文件没有说。但我怀疑这主要是因为它使调试 Python 和一些更深层次的标准库模块变得更容易,因为知道有某种方法可以从解释器中验证两个名称是否确实引用了同一个对象,或者打印出来id 以确保名称不会随着时间的推移而改变,等等。想象一下调试 weakref,甚至是 pickle,没有它。


那么,“同一个对象”究竟是什么意思?好吧,这取决于口译员。显然,必须不可能在语言级别区分同一对象的两个实例,并且可能在解释器级别也是如此(特别是因为有一个定义明确的 API 可以插入到大多数解释器实现中)。

所有主要实现都通过在较低级别遵循身份概念来处理此问题。 CPython 比较 PyObject* 指针的值,Jython 标识比较 Java 引用,PyPy 对对象空间对象执行 is...

值得看一下PyPy source,它要求“x is y iff xy 是同一个对象”在两个方向上都是正确的。顶级表达式x is y 为真当且仅当,无论在适当对象空间中的对象wxwy 是什么,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,看看你是否能在修改解释器的艰苦工作之前找到任何有趣的麻烦(即使在这种情况下并不那么难)。


那么,isid 有什么关系? is 可以在 id 之上实现吗?例如,x is y 只检查 id(x) == id(y)?嗯,id

返回对象的“身份”。这是一个整数,保证在其生命周期内对于该对象是唯一且恒定的。两个生命周期不重叠的对象可能具有相同的id() 值。

所以,对象的id 在其生命周期内是唯一且恒定的,并且x is y 为真如果它们是同一个对象,因此x is y 为真如果id(x) == id(y),对吧?

嗯,id 可以反弹到您想要的任何位置,并且不允许影响is。如果您非常仔细地制定了定义(请记住,如果您丢弃 builtinsid 的引用,那么过去的任何实现都不能保证不再存在,或者如果确实存在,也不能保证正常工作......) ,您可以id 的默认实现之上定义is

但这将是一件奇怪的事情。在 CPython 中,id(x) 只是“返回内存中对象的地址”,这与指向内存中对象的指针的值相同。但这只是 CPython 的产物;没有什么说其他实现必须使id 将用于身份比较的基础值作为整数返回。事实上,在用没有指针(可以转换为整数)的语言编写的实现中,你甚至不清楚如何做到这一点。在 PyPy 中,对象的 id 甚至可能是第一次访问并存储在对象空间的字典中时计算的值,由对象本身作为键。


至于__hash__,您误读了文档的重要部分。

[...] x.__hash__() 返回id(x)

您省略的部分清楚地表明这仅适用于用户定义类的实例(不重新定义__hash__)。对于例如tuple,显然不是这样。简而言之,身份与散列无关,只是对于某些对象,身份是一个方便的散列值。

【讨论】:

    【解决方案2】:

    不,is 是直接的指针比较,id 只是将对象的地址返回给long

    来自ceval.c

    case PyCmp_IS:
        res = (v == w);
        break;
    case PyCmp_IS_NOT:
        res = (v != w);
        break;
    

    vw 这里只是PyObject *

    来自bltinmodule.c

    static PyObject *
    builtin_id(PyObject *self, PyObject *v)
    {
        return PyLong_FromVoidPtr(v);
    }
    
    PyDoc_STRVAR(id_doc,
    "id(object) -> integer\n\
    \n\
    Return the identity of an object. This is guaranteed to be unique among\n\
    simultaneously existing objects. (Hint: it's the object's memory address.)");
    

    【讨论】:

    • +1 用于挖掘源代码(我懒得做;))
    • 这个答案是针对 CPython 的;在其他实现中(至少 Jython 和 PyPy),is 不是指针比较,id 不返回地址。
    猜你喜欢
    • 2013-08-18
    • 2012-08-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-21
    • 2021-12-17
    • 2022-10-13
    • 2012-11-18
    相关资源
    最近更新 更多