Я изучаю и играю с 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
.
@FilipHaglund Весь смысл моего вопроса в том, что я написал не c == None
(что напрямую назвало бы «мой __eq__
метод»), а None == c
.
«Как работает метод __eq__
одноэлементного объекта None» — почему бы вам просто не попробовать?
@KellyBundy Это то, что я сделал, я попробовал, я не мог понять, и поэтому этот вопрос существует.
Каков был результат?
Результат в вопросе, он возвращает True
... Не знаю, куда вы клоните свои комментарии? Кроме того, я спросил, как выглядит метод __eq__
объекта Singleton None, поэтому вы упустили самую важную часть моего вопроса, когда «повторили» его.
Нет, это не в вопросе. Опять же: что вы получили, когда попробовали этот метод, т. е. сделали print(None.__eq__(c))
?
Благодарим вас за вклад. Я не знал, что при использовании ==
происходит больше, чем None.__eq__(c)
(что возвращает NotImplemented
). Ответ VPfB указывает на то, что когда __eq__
возвращает NotImplemented
, имеет место особое поведение.
Как указано в документах: x==y
вызывает x.__eq__(y)
, но None.__eq__(...)
возвращает NotImplemented
для всего, кроме самого None
(отсутствует часть: почему? Я не знаю), поэтому Python пытается сравнить наоборот и вызывает __eq__
из MyNone
, который всегда возвращает True
.
Обновление: None
(класс NoneType
) не определяет свой собственный __eq__
и использует значение по умолчанию object.__eq__
, основанное на тесте is
: он возвращает True
, если аргумент is
идентичен или NotImplemented
в противном случае. (спасибо соавторам)
NotImplemented
поднятый из __eq__
означает то же самое, как если бы он не существовал, что позволяет интерпретатору вернуться к правосторонней проверке.
Можете ли вы указать мне место в документации, где описывается/упоминается часть «Python пытается выполнить сравнение наоборот, если возвращается NotImplemented
»? Это важное знание, которого мне не хватало. Спасибо!
Недостающая часть заключается в том, что type(None).__eq__
— это просто object.__eq__
, который всегда возвращает True
только в том случае, если два входа являются одним и тем же объектом (по идентичности, а не по значению). Но на самом деле та часть исходного кода, которая реализует это поведение, не достигается в этом коде, поскольку он имеет более низкий приоритет, чем метод __eq__
в правой части, когда он есть. Подробности смотрите в моем ответе.
@VPfB __eq__
всегда возвращает NotImplemented
для несовпадающих типов данных, следовательно, __eq__
с None
и все остальное всегда будет возвращать NotImplemented
, не так ли?
@Rohit Не совсем так; независимо от того, возвращает ли __eq__
NotImplemented
, зависит от того, какую реализацию __eq__
вы используете, и некоторые реализации __eq__
действительно возвращают результат True
или False
при вызове другого типа, например. float.__eq__(1.0, 1)
или float.__eq__(1.0, 2)
. Обычно NotImplemented
означает, что либо другой тип недостаточно связан для сравнения этой реализацией __eq__
, либо что другой тип является подтипом, и вместо него следует использовать его реализацию __eq__
.
На самом деле у типа 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_Type
tp_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__
для обычных классов, но жестко запрограммирована для встроенных типов.
Это идеальное низкоуровневое объяснение, спасибо!
Извините, я неправильно прочитал (надеялся, что удалил достаточно быстро). Я думал, вы показываете код object
C, т. е. что object
не имеет метода __eq__
(потому что вы говорили об этом и потому что он есть в object.c
).
@KellyBundy Я думаю, что это в любом случае актуальный вопрос. Чтобы было ясно, я не думаю, что object
также имеет функцию tp_richcompare
, я думаю, что механизм поиска __eq__
— это отдельная вещь, которая вызывается из оператора ==
для пользовательских типов, но не для встроенных типов. Но я не уверен, где находится исходный код C, соответствующий классу object
.
Далее в do_richcompare
он реализует поведение по умолчанию для операторов сравнения, если tp_richcompare
не указывает на соответствующую функцию: идентификатор объекта для ==
и !=
и TypeError
для остальных.
@chepner Верно, но вопрос Келли Банди заключался в том, откуда берется None.__eq__
, когда вы пишете его на Python, поскольку, похоже, нет соответствующей реализации этого в C. Понятно, что когда вы звоните object.__eq__(1, 2)
, вы не звоните do_richcompare
, потому что результат NotImplemented
вместо False
.
Чтобы было понятнее, что я имел в виду: как я уже сказал, я думал, что вы говорите о object
, поэтому я действительно задался вопросом, откуда берется object.__eq__
. (Я скопировал ваш type(None).__eq__
, который, как вы показали, <slot wrapper '__eq__' of 'object' objects>
).
почти уверен, что используются левые аргументы
eq
, попробуйтеc == None