Понимание деталей равенства в Python

При попытке построить пример, в котором a == b не совпадает с b == a, кажется, что я случайно создал пример, в котором a == b не совпадает с a.__eq__(b):

class A:

    def __eq__(self, other: object) -> bool:
        return type(other) is A


class B(A):
    pass


if __name__ == '__main__':
    a = A()
    b = B()
    assert not a.__eq__(b)  # as expected
    assert not (a == b)  # Why does this fail?

Может ли кто-нибудь объяснить мне, почему последнее утверждение не работает? Я ожидал, что он будет таким же, как и второй.

Потому что b.__eq__(a)

mousetail 05.06.2024 13:46

Отвечает ли это на ваш вопрос? Есть ли разница между «= = " и «есть»?

toyota Supra 05.06.2024 13:46

@toyotaSupra Я подозреваю, что они уже знают разницу, они проводили эксперимент.

Mark Ransom 05.06.2024 13:48

@toyotaSupra Нет, если честно, это не так.

Sebastian Thomas 05.06.2024 13:49

@mousetail Просто чтобы убедиться, что я понял ваш комментарий: вы утверждаете, что a == b оценивается как a.__eq__(b) or b.__eq__(a)? Если да, есть ли у вас ссылка?

Sebastian Thomas 05.06.2024 13:51

@SebastianThomas Кажется, это особый случай, если один класс является подклассом другого, тогда будут использоваться только подклассы __eq__

mousetail 05.06.2024 13:56

@mousetail Но B.__eq__ — это то же самое, что A.__eq__, не так ли?

Sebastian Thomas 05.06.2024 14:02

Да, но сейчас other есть a, что типа A делает __eq__ Правда.

matszwecja 05.06.2024 14:03

@SebastianThomas Да, но (см. ответ Matszwecja) аргументы перевернуты. a == b, поскольку B является подклассом A, реализуется как b.__eq__(a), а не a.__eq__(b).

chepner 05.06.2024 14:03

Кстати, равенство должно быть симметричным; не давайте ему определения, чувствительного к порядку аргументов (и, конечно же, не игнорируйте self в определении).

chepner 05.06.2024 14:04

@chepner Спасибо, я это знаю. Я пытался явно дать бессмысленное определение, которое должно было быть несимметричным.

Sebastian Thomas 05.06.2024 14:06
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
3
11
111
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Если вы дадите B собственную версию __eq__() и поместите в каждую операторы печати, легко продемонстрировать, что a == b на самом деле оценивается как b.__eq__(a). Нет, я не знаю, почему сделано совсем не так, как я наивно ожидал.

Кажется, это происходит только в том случае, если B является подклассом

mousetail 05.06.2024 13:56
Ответ принят как подходящий

Соответствующая цитата, объясняющая происходящее, находится в документации:

Если операнды имеют разные типы и тип правого операнда является прямым или косвенным подклассом типа левого операнда, отраженный метод правого операнда имеет приоритет, в противном случае приоритет имеет метод левого операнда. Виртуальное подклассирование не рассматривается.

В частности, a == b превращается в b.__eq__(a), а не в a.__eq__(b).

chepner 05.06.2024 14:02

Когда сравниваются два объекта в Python, интерпретатор Python использует операцию do_richcompare, написанную на C. См. здесь:

https://github.com/python/cpython/blob/v3.8.3/Objects/object.c#L717

В коде C одна из первых проверок предназначена для подтипирования.

do_richcompare(PyObject *v, PyObject *w, int op)
{
    richcmpfunc f;
    PyObject *res;
    int checked_reverse_op = 0;

    if (v->ob_type != w->ob_type &&
        PyType_IsSubtype(w->ob_type, v->ob_type) &&
        (f = w->ob_type->tp_richcompare) != NULL) {
        checked_reverse_op = 1;
        res = (*f)(w, v, _Py_SwappedOp[op]);
        if (res != Py_NotImplemented)
            return res;
        Py_DECREF(res);
    }

По сути, это говорит о том, что если вы используете сравнение:

v == w

Но тип w является подклассом типа v, тогда верните

w == v

что эквивалентно w.__eq__(v)

Или в этом конкретном случае вызов:

a == b

вернется

b == a

потому что B это подкласс A

Другие вопросы по теме