关于python:classobjects是单身人士吗?

Are classobjects singletons?

如果我们有x = type(a)x == y,这是否必然意味着x is y

这是一个反例,但它是一个骗局:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> class BrokenEq(type):
...     def __eq__(cls, other):
...         return True
...    
>>> class A(metaclass=BrokenEq):
...     pass
...
>>> a = A()
>>> x = type(a)
>>> x == A, x is A
(True, True)
>>> x == BrokenEq, x is BrokenEq
(True, False)

我无法创建这样的反例:

1
2
3
4
5
6
7
8
>>> A1 = type('A', (), {})
>>> A2 = type('A', (), {})
>>> a = A1()
>>> x = type(a)
>>> x == A1, x is A1
(True, True)
>>> x == A2, x is A2
(False, False)

为了澄清我的问题——在不重写相等运算符的情况下做一些疯狂的事情,类是否可能存在于两个不同的内存位置,或者导入系统是否以某种方式阻止了这一点?

如果是这样,我们如何证明这种行为——例如,用reload或__import__做奇怪的事情?

如果没有,这是由语言保证的还是在任何地方记录的?

后记:

1
2
3
# thing.py
class A:
    pass

最后,这就是为我澄清真实行为的原因(它支持BLCKKNGHT答案中的主张)

1
2
3
4
5
6
7
8
9
>>> import sys
>>> from thing import A
>>> a = A()
>>> isinstance(a, A), type(a) == A, type(a) is A
(True, True, True)
>>> del sys.modules['thing']
>>> from thing import A
>>> isinstance(a, A), type(a) == A, type(a) is A
(False, False, False)

因此,尽管使用importlib.reload的代码可以通过类标识破坏类型检查,但它也会破坏isinstance


我不知道关于==如何为类型工作的任何文档,但它确实是按身份工作的。您可以看到,cpython 2.7实现是一个指针比较:

1
2
3
4
5
6
7
8
9
10
11
static PyObject*
type_richcompare(PyObject *v, PyObject *w, int op)
{
    ...

    /* Compare addresses */
    vv = (Py_uintptr_t)v;
    ww = (Py_uintptr_t)w;
    switch (op) {
    ...
    case Py_EQ: c = vv == ww; break;

在cpython 3.5中,type没有实现自己的tp_richcompare,因此它继承了object的默认相等比较,这是一个指针比较:

1
2
3
PyTypeObject PyType_Type = {
    ...
    0,                                          /* tp_richcompare */


不,除了用元类__eq__方法来混淆之外,没有办法创建两个不完全相同、比较相等的类对象。

但是这种行为并不是类特有的。它是没有在类中定义__eq__方法的任何对象的默认行为。该行为继承自object,它是所有其他(新样式)类的基类。它只被具有其他语义的内置类型(例如,比较其内容的容器类型)和定义自己的__eq__运算符的自定义类重写。

至于在不同的内存位置获得对同一类的两个不同引用,由于Python的对象语义,这实际上是不可能的。对象的内存位置是其标识(至少在cpython中)。另一个具有相同内容的类可以存在于其他地方,但就像在您的A1A2示例中一样,它将被所有python逻辑视为不同的对象。