Установить оператор «в»: использует равенство или идентичность? - PullRequest
31 голосов
/ 01 февраля 2012
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

Ответы [ 5 ]

16 голосов
/ 01 февраля 2012

Set __contains__ выполняет проверки в следующем порядке:

 'Match' if hash(a) == hash(b) and (a is b or a==b) else 'No Match'

Соответствующий исходный код C находится в Objects / setobject.c :: set_lookkey () и в Objects / object.c :: PyObject_RichCompareBool ().

15 голосов
/ 01 февраля 2012

Вам также нужно определить __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__().

7 голосов
/ 01 февраля 2012

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

Хеш-функция по умолчанию использует идентификатор объекта, что довольно бесполезно в качестве быстрого приближения к полному равенству, но, по крайней мере, позволяет вам использовать произвольный экземпляр класса в качестве ключа словаря и извлекать значение, хранящееся в нем, если вы передать точно такой же объект, как ключ. Но это означает, что если вы переопределите равенство и не переопределите хеш-функцию, ваши объекты попадут в словарь / набор, не пожаловавшись на то, что они не хэш, но все равно не будут работать так, как вы ожидаете к.

См. официальные документы по Python по __hash__ для получения более подробной информации.

1 голос
/ 01 февраля 2012

Тангенциальный ответ, но ваш вопрос и мои тесты вызвали у меня любопытство. Если вы игнорируете оператор set, который является источником вашей проблемы __hash__, то ваш вопрос по-прежнему интересен.

Благодаря помощи, которую я получил по этому такому вопросу , я смог отследить оператор in через исходный код до его корня. Внизу я нашел функцию 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;
}
0 голосов
/ 01 февраля 2012

Наборы, кажется, используют хэш-коды, а затем тождество, прежде чем сравнивать на равенство. Следующий код:

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

То, что происходит, кажется:

  1. Хеш-код вычисляется, когда элемент вставляется в хеш. (Для сравнения с существующими элементами.)
  2. Вычисляется хеш-код для объекта, который вы проверяете с помощью оператора in.
  3. Элементы набора с одинаковым хеш-кодом проверяются, сначала проверяя, являются ли они тем же объектом, что и тот, который вы ищете, или они логически равны ему.
...