Медленная оценка равенства для идентичных объектов (x == x)

Есть ли какая-либо причина x == x не оценивается быстро? Я надеялся, что __eq__ будет проверять, совпадают ли его два аргумента, и если так немедленно вернуть True. Но он этого не делает:

s = set(range(100000000))
s == s # this doesn't short-circuit, so takes ~1 sec

Для встроенных модулей x == x всегда возвращает True, я думаю? Для пользовательских классов, я думаю, кто-то может определить __eq__, который не удовлетворяет этому свойству, но есть ли разумный прецедент для этого?

Причина, по которой я хочу, чтобы x == x была быстро оценена, - это то, что она имеет огромную производительность при memoizing функциями с очень большими аргументами:

from functools import lru_cache
@lru_cache()
def f(s):
    return sum(s)
large_obj = frozenset(range(50000000))
f(large_obj) # this takes >1 sec every time

Обратите внимание, что причина @lru_cache неоднократно медленна для больших объектов не потому, что ей нужно вычислить __hash__ (это делается только один раз и затем жестко кэшируется, поскольку указал by @jsbueno), но из-за того, что таблица хеш-словаря должна выполняться __eq__ каждый раз, чтобы убедиться, что он нашел нужный объект в ведре (очевидно, недостаточно равенства хэшей).

UPDATE:

Кажется, стоит рассмотреть этот вопрос отдельно для трех ситуаций.

1) Пользовательские типы (т.е. не встроенная/стандартная библиотека).

Как отметил @donkopotamus, бывают случаи, когда x == x не следует оценивать True. Например, для типов numpy.array и pandas.Series результат намеренно не конвертируется в логическое, потому что он неясно, какова должна быть естественная семантика (означает ли False, что контейнер пуст, или это означает, что все элементы в нем False?).

Но здесь нет необходимости в том, чтобы python что-либо делал, поскольку пользователи всегда могут коротко закоротить x == x сравнение, если это необходимо:

def __eq__(self, other):
  if self is other:
    return True
  # continue normal evaluation

2) Типы встроенных/стандартных библиотек Python.

a) Неконтейнеры.

Насколько мне известно, короткое замыкание может быть уже реализовано для этого случая - я не могу сказать, так как в любом случае это очень быстро.

b) Контейнеры (включая str).

Как комментирует @Karl Knechtel, добавление короткого замыкания может повредить общую производительность, если экономия от короткого замыкания перевешивается дополнительными накладными расходами в случаях, когда self is not other. Хотя теоретически возможно, даже в этом случае накладные расходы являются небольшими в относительных терминах (сравнение контейнеров никогда не бывает супербыстро). И, конечно же, в случаях, когда короткое замыкание помогает, экономия может быть значительно.

Кстати, оказывается, что str делает короткое замыкание: мгновенное сравнение огромных одинаковых строк.

Ответ 1

Как вы говорите, кто-то может легко определить __eq__, который вы лично не одобряете... например, Институт Инженеры по электротехнике и электронике могут быть настолько глупыми, чтобы это сделать:

>>> float("NaN") == float("NaN")
False

Другой "необоснованный вариант использования":

>>> bool(numpy.ma.masked == numpy.ma.masked)
False

Или даже:

>>> numpy.arange(10) == numpy.arange(10)
array([ True,  True,  True,  True,  True,  True,  True,  True,  True,  True], dtype=bool)

который имеет смелость, даже не будучи конвертируемой в bool!

Таким образом, для x == x до не существует, конечно, практическая возможность быть коротко замкнутой, чтобы быть правдой.

Отклонение курса

Однако, возможно, следующий вопрос:

Почему не проверяется идентификатор экземпляра set.__eq__?

Ну, можно подумать... потому что набор S может содержать NaN, и поскольку NaN не может равняться самому себе, то, несомненно, такое множество S не может самоопределяться? Исследование:

>>> s = set([float("NaN")])
>>> s == s
True

Хм, это интересно, тем более, что:

>>> {float("NaN")} == {float("NaN")}
False

Это поведение связано с желанием Python для последовательностей, которые будут рефлексивными.