Установить оператор "в": использует равенство или идентичность?
class A(object):
def __cmp__(self):
print '__cmp__'
return object.__cmp__(self)
def __eq__(self, rhs):
print '__eq__'
return True
a1 = A()
a2 = A()
print a1 in set([a1])
print a1 in set([a2])
Почему первая строка печатает True, а вторая печатает False? И ни один из них не входит в оператор eq?
Я использую Python 2.6
Ответы
Ответ 1
Вы также должны определить __hash__
. Например
class A(object):
def __hash__(self):
print '__hash__'
return 42
def __cmp__(self, other):
print '__cmp__'
return object.__cmp__(self, other)
def __eq__(self, rhs):
print '__eq__'
return True
a1 = A()
a2 = A()
print a1 in set([a1])
print a1 in set([a2])
Будет работать как положено.
Как правило, каждый раз, когда вы реализуете __cmp__
вы должны реализовывать __hash__
такой что для всех x
и y
такой, что x == y
, x.__hash__() == y.__hash__()
.
Ответ 2
Установить __contains__ делает проверки в следующем порядке:
'Match' if hash(a) == hash(b) and (a is b or a==b) else 'No Match'
Соответствующий исходный код C находится в Object/setobject.c:: set_lookkey() и в Object/object.c:: PyObject_RichCompareBool().
Ответ 3
Наборы и словари набирают скорость, используя хеширование как быстрое приближение полной проверки равенства. Если вы хотите переопределить равенство, вам обычно нужно переопределить алгоритм хэширования так, чтобы он был согласован.
Хэш-функция по умолчанию использует идентификатор объекта, который довольно бесполезен в качестве быстрого приближения полного равенства, но по крайней мере позволяет использовать произвольный экземпляр класса в качестве словарного ключа и извлекать значение, хранящееся с ним, если вы передать точно тот же объект, что и ключ. Но это означает, что если вы переопределите равенство и не переопределите хеш-функцию, ваши объекты войдут в словарь/набор, не жалуясь на то, что не будут хешировать, но все равно не будут работать так, как вы ожидаете.
Подробнее см. официальные документы python на __hash__
.
Ответ 4
Тангенциальный ответ, но ваш вопрос и мое тестирование вызвали у меня любопытство. Если вы проигнорируете оператор set, который является источником вашей проблемы __hash__
, выясняется, что ваш вопрос по-прежнему интересен.
Благодаря помощи, которую я получил в этом вопросе SO, я смог преследовать оператора in через исходный код root. В нижней части я обнаружил функцию PyObject_RichCompareBool, которая действительно проверяет идентичность (см. Комментарий о "Быстрый результат" ) перед тестированием на равенство.
Поэтому, если я не пойму, как все работает, технический ответ на ваш вопрос - это первая идентичность, а затем равенство, через сам тест равенства. Чтобы повторить, это не источник поведения, которое вы видели, а просто технический ответ на ваш вопрос.
Если я неправильно понял источник, кто-то, пожалуйста, поставил меня прямо.
int
PyObject_RichCompareBool(PyObject *v, PyObject *w, int op)
{
PyObject *res;
int ok;
/* Quick result when objects are the same.
Guarantees that identity implies equality. */
if (v == w) {
if (op == Py_EQ)
return 1;
else if (op == Py_NE)
return 0;
}
res = PyObject_RichCompare(v, w, op);
if (res == NULL)
return -1;
if (PyBool_Check(res))
ok = (res == Py_True);
else
ok = PyObject_IsTrue(res);
Py_DECREF(res);
return ok;
}
Ответ 5
Наборы, похоже, используют хеш-коды, а затем идентичность перед сравнением для равенства. Следующий код:
class A(object):
def __eq__(self, rhs):
print '__eq__'
return True
def __hash__(self):
print '__hash__'
return 1
a1 = A()
a2 = A()
print 'set1'
set1 = set([a1])
print 'set2'
set2 = set([a2])
print 'a1 in set1'
print a1 in set1
print 'a1 in set2'
print a1 in set2
выходы:
set1
__hash__
set2
__hash__
a1 in set1
__hash__
True
a1 in set2
__hash__
__eq__
True
Кажется, что происходит:
- Хэш-код вычисляется, когда элемент вставлен в хеш. (Для сравнения с существующими элементами.)
- Вычисляется хэш-код объекта, который вы проверяете с помощью оператора
in
.
- Элементы набора с одним и тем же хэш-кодом проверяются, сначала проверяя, являются ли они тем же объектом, что и тот, который вы ищете, или если они логически равны ему.