Подтвердить что ты не робот

Являются ли классные объекты одиночными?

Если мы имеем 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

Наконец, это то, что прояснило реальное поведение для меня (и оно поддерживает утверждения в ответе Блеккна)

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

4b9b3361

Ответ 1

Нет, нет возможности создать два объекта класса, сравнивающих одинаковые, не будучи идентичными, за исключением того, что вы можете использовать методы metaclass __eq__.

Это поведение, однако, не является чем-то уникальным для классов. Это поведение по умолчанию для любого объекта без метода __eq__, определенного в его классе. Поведение наследуется от object, который является базовым классом для всех остальных классов (новый стиль). Он переопределяется только для встроенных типов, у которых есть еще одна семантика для равенства (например, типы контейнеров, которые сравнивают их содержимое) и для пользовательских классов, которые определяют собственный оператор __eq__.

Что касается получения двух разных ссылок на один и тот же класс в разных местах памяти, это не совсем возможно из-за семантики объекта Python. Расположение памяти объекта - это его идентификатор (по крайней мере, в cpython). Другой класс с идентичным содержимым может существовать где-то в другом месте, но, как в примере A1 и A2, он будет рассматриваться как другой объект по всей логике Python.

Ответ 2

Я не знаю никакой документации о том, как == работает для типов, но определенно работает по идентификатору. Вы можете видеть, что реализация 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 */