Что произойдет «за кулисами», если я вызову `None == x` в Python?

Я изучаю и играю с Python, и я придумал следующий тестовый код (имейте в виду, что я бы не стал писать продуктивный код, но при изучении новых языков мне нравится играть с крайними случаями языка):

a = None    
print(None == a) # I expected True, I got True

b = 1
print(None == b) # I expected False, I got False

class MyNone:
    # Called if I compare some myMyNone == somethingElse
    def __eq__(self, __o: object) -> bool:
        return True

c = MyNone()
print (None == c) # !!! I expected False, I got True !!!

Пожалуйста, смотрите самую последнюю строку примера кода.

Как может быть, чтобы None == something там, где что-то явно не None, возвращалось True? Я ожидал такого результата для something == None, но не для None == something.

Я ожидал, что это вызовет None is something за кадром.

Поэтому я думаю, что вопрос сводится к следующему: как выглядит метод __eq__ объекта None singleton и как я мог это узнать?


PS: я знаю о PEP-0008 и его цитате

Сравнения с синглтонами, такими как None, всегда должны выполняться с операторами is или not, а не с операторами равенства.

но я все же хотел бы знать, почему print (None == c) в приведенном выше примере возвращает True.

почти уверен, что используются левые аргументы eq, попробуйте c == None

Filip Haglund 09.12.2022 13:36

@FilipHaglund Весь смысл моего вопроса в том, что я написал не c == None (что напрямую назвало бы «мой __eq__ метод»), а None == c.

Markus Weninger 09.12.2022 13:38

«Как работает метод __eq__ одноэлементного объекта None» — почему бы вам просто не попробовать?

Kelly Bundy 09.12.2022 13:39

@KellyBundy Это то, что я сделал, я попробовал, я не мог понять, и поэтому этот вопрос существует.

Markus Weninger 09.12.2022 13:39

Каков был результат?

Kelly Bundy 09.12.2022 13:40

Результат в вопросе, он возвращает True... Не знаю, куда вы клоните свои комментарии? Кроме того, я спросил, как выглядит метод __eq__ объекта Singleton None, поэтому вы упустили самую важную часть моего вопроса, когда «повторили» его.

Markus Weninger 09.12.2022 13:44

Нет, это не в вопросе. Опять же: что вы получили, когда попробовали этот метод, т. е. сделали print(None.__eq__(c))?

Kelly Bundy 09.12.2022 13:48

Благодарим вас за вклад. Я не знал, что при использовании == происходит больше, чем None.__eq__(c) (что возвращает NotImplemented). Ответ VPfB указывает на то, что когда __eq__ возвращает NotImplemented, имеет место особое поведение.

Markus Weninger 09.12.2022 13:49
Почему в 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 может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
8
8
243
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Как указано в документах: x==y вызывает x.__eq__(y), но None.__eq__(...) возвращает NotImplemented для всего, кроме самого None (отсутствует часть: почему? Я не знаю), поэтому Python пытается сравнить наоборот и вызывает __eq__ из MyNone, который всегда возвращает True.

Обновление: None (класс NoneType) не определяет свой собственный __eq__ и использует значение по умолчанию object.__eq__, основанное на тесте is: ​​он возвращает True, если аргумент is идентичен или NotImplemented в противном случае. (спасибо соавторам)

NotImplemented поднятый из __eq__ означает то же самое, как если бы он не существовал, что позволяет интерпретатору вернуться к правосторонней проверке.
bereal 09.12.2022 13:48

Можете ли вы указать мне место в документации, где описывается/упоминается часть «Python пытается выполнить сравнение наоборот, если возвращается NotImplemented»? Это важное знание, которого мне не хватало. Спасибо!

Markus Weninger 09.12.2022 13:54

Недостающая часть заключается в том, что type(None).__eq__ — это просто object.__eq__, который всегда возвращает True только в том случае, если два входа являются одним и тем же объектом (по идентичности, а не по значению). Но на самом деле та часть исходного кода, которая реализует это поведение, не достигается в этом коде, поскольку он имеет более низкий приоритет, чем метод __eq__ в правой части, когда он есть. Подробности смотрите в моем ответе.

kaya3 09.12.2022 14:05

@VPfB __eq__ всегда возвращает NotImplemented для несовпадающих типов данных, следовательно, __eq__ с None и все остальное всегда будет возвращать NotImplemented, не так ли?

Rohit Lal 09.12.2022 14:36

@Rohit Не совсем так; независимо от того, возвращает ли __eq__NotImplemented, зависит от того, какую реализацию __eq__ вы используете, и некоторые реализации __eq__ действительно возвращают результат True или False при вызове другого типа, например. float.__eq__(1.0, 1) или float.__eq__(1.0, 2). Обычно NotImplemented означает, что либо другой тип недостаточно связан для сравнения этой реализацией __eq__, либо что другой тип является подтипом, и вместо него следует использовать его реализацию __eq__.

kaya3 09.12.2022 14:42
Ответ принят как подходящий

На самом деле у типа None нет собственного метода __eq__; внутри Python мы видим, что он явно наследуется от базового класса object:

>>> type(None).__eq__
<slot wrapper '__eq__' of 'object' objects>

Но на самом деле это не то, что происходит в исходном коде. Реализацию None можно найти в Objects/object.c в исходниках CPython, где мы видим:

PyTypeObject _PyNone_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "NoneType",
    0,
    0,
    none_dealloc,       /*tp_dealloc*/ /*never called*/
    0,                  /*tp_vectorcall_offset*/
    0,                  /*tp_getattr*/
    0,                  /*tp_setattr*/
    // ...
    0,                  /*tp_richcompare */
    // ...
    0,                  /*tp_init */
    0,                  /*tp_alloc */
    none_new,           /*tp_new */
};

Я пропустил большую часть ненужных частей. Здесь важно то, что _PyNone_Typetp_richcompare — это 0, то есть нулевой указатель. Это проверяется в функции do_richcompare:

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

Перевод для тех, кто не говорит на C:

  • Если функция tp_richcompare в левой части не равна нулю, вызовите ее, а если ее результат не равен NotImplemented, верните этот результат.
  • В противном случае, если реверс еще не был проверен *, а функция tp_richcompare в правой части не равна нулю, вызовите ее, и если результат не равен NotImplemented, верните этот результат.

В коде есть несколько других ветвей, к которым можно вернуться, если ни одна из этих ветвей не вернет результат. Но этих двух ветвей достаточно, чтобы увидеть, что происходит. Дело не в том, что type(None).__eq__ возвращает NotImplemented, а в том, что тип вообще не имеет соответствующей функции в исходном коде C. Это означает, что вторая ветвь взята, следовательно, результат, который вы наблюдаете.

*The flag checked_reverse_op is set if the reverse direction has already been checked; this happens if the right-hand-side is a strict subtype of the left-hand-side, in which case it takes priority. That doesn't apply in this case since there is no subtype relation between type(None) and your class.

@KellyBundy Интересный вопрос, но я думаю, что ответ на него будет примерно таким же, как и этот ответ. Я не уверен на 100%, но я считаю, что object.__eq__ определено где-то еще, и это то, что вы получите, когда будете искать type(None).__eq__, поскольку оно не переопределяет его; но оператор == не ищет __eq__ напрямую, а вызывает функцию tp_richcompare, которая определена __eq__ для обычных классов, но жестко запрограммирована для встроенных типов.

kaya3 09.12.2022 14:12

Это идеальное низкоуровневое объяснение, спасибо!

Markus Weninger 09.12.2022 14:14

Извините, я неправильно прочитал (надеялся, что удалил достаточно быстро). Я думал, вы показываете код object C, т. е. что object не имеет метода __eq__ (потому что вы говорили об этом и потому что он есть в object.c).

Kelly Bundy 09.12.2022 14:16

@KellyBundy Я думаю, что это в любом случае актуальный вопрос. Чтобы было ясно, я не думаю, что object также имеет функцию tp_richcompare, я думаю, что механизм поиска __eq__ — это отдельная вещь, которая вызывается из оператора == для пользовательских типов, но не для встроенных типов. Но я не уверен, где находится исходный код C, соответствующий классу object.

kaya3 09.12.2022 14:24

Далее в do_richcompare он реализует поведение по умолчанию для операторов сравнения, если tp_richcompare не указывает на соответствующую функцию: идентификатор объекта для == и != и TypeError для остальных.

chepner 09.12.2022 14:36

@chepner Верно, но вопрос Келли Банди заключался в том, откуда берется None.__eq__, когда вы пишете его на Python, поскольку, похоже, нет соответствующей реализации этого в C. Понятно, что когда вы звоните object.__eq__(1, 2), вы не звоните do_richcompare, потому что результат NotImplemented вместо False.

kaya3 09.12.2022 14:38

Чтобы было понятнее, что я имел в виду: как я уже сказал, я думал, что вы говорите о object, поэтому я действительно задался вопросом, откуда берется object.__eq__. (Я скопировал ваш type(None).__eq__, который, как вы показали, <slot wrapper '__eq__' of 'object' objects>).

Kelly Bundy 09.12.2022 14:44

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