类对象是单例模式吗?

13

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

这里有一个反例,但这是一种欺骗:

>>> 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)

而我无法创造出这样的反例:

>>> 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__进行奇怪的操作?

如果不是,那么这是否由语言保证或在任何地方记录?


结语

# thing.py
class A:
    pass

最后,这是让我明确真正行为的事情(它支持 Blckknght 的回答中所述的主张)。

>>> 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

1
有趣的问题!我认为它们必须是这样,因为任何给定类型的对象可能只会以一种一致的方式创建。我能引用证明吗?还没有。 - Tom Zych
由于在Python中一切都是对象,类只是其元类的实例 - 为什么我们不能有“相同”类的其他实例呢?然而,我没有找到一个例子,其中导入一个类或以其他方式将名称绑定到类不仅仅是获取对同一对象的另一个引用。 - wim
也许这只是在特定实现中真实的事情,但它没有在语言规范中进行说明,也没有得到保证。 - Tom Zych
如果您从不同的导入路径导入相同的类,则它们将存在于两个不同的内存位置,但它们不会评估为相同(即x = type(a)和x == y --> x is y仍然成立)。 - DylanYoung
2个回答

6

不,除非通过操纵元类的__eq__方法,否则没有办法创建两个相等但不完全相同的类对象。

这种行为并不是仅限于类。对于任何没有在其类中定义__eq__方法的对象,这都是默认行为。该行为继承自object,它是所有其他(新式)类的基类。仅为具有其他相等语义的内置类型(例如比较其内容的容器类型)和定义自己的__eq__运算符的自定义类覆盖它。

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


你好!所以答案似乎是:它们不是单例(这意味着必须截取类对象的创建)。但是它们从object继承默认的__eq__方法非常愚蠢 - 即使这些类是从相同的源代码生成的,它们也不能被视为相等的。这样对吗? - wim
没错,类对象不是某个单例类的实例,它们只是type元类型的普通实例。调用type(obj)并不会创建一个新类型,它只是查找实例obj(从obj.__class__)的类型,并返回对该预先存在的对象的引用。 - Blckknght

6
我不知道有关类型中 == 如何工作的任何文档,但它肯定是根据标识工作的。你可以看到 CPython 2.7 实现 是一个指针比较。
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的默认相等比较,即指针比较。
PyTypeObject PyType_Type = {
    ...
    0,                                          /* tp_richcompare */

为了后代,这在CPython 3.10中仍然是正确的。type对象仍然使用来自object的默认相等比较。 - Brian61354270

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接