Как/почему работает функция set() в {frozenset()}?

Несмотря на то, что наборы не сотрясаются, проверка членства в другом наборе работает:

>>> set() in {frozenset()}
True

Я ожидал TypeError: unhashable type: 'set', совместимый с другими поведениями в Python:

>>> set() in {}  # doesn't work when checking in dict
TypeError: unhashable type: 'set'
>>> {} in {frozenset()}  # looking up some other unhashable type doesn't work
TypeError: unhashable type: 'dict'

Итак, как настроено членство в другом наборе?

Ответ 1

В последней строке документации для set s говорится следующее:

Обратите внимание, что аргумент elem __contains__(), remove() и discard() может быть set. Для поддержки поиска эквивалентного параметра frozenset временный создается из elem.

Ответ 2

set_contains реализуется следующим образом:

static int
set_contains(PySetObject *so, PyObject *key)
{
    PyObject *tmpkey;
    int rv;

    rv = set_contains_key(so, key);
    if (rv < 0) {
        if (!PySet_Check(key) || !PyErr_ExceptionMatches(PyExc_TypeError))
            return -1;
        PyErr_Clear();
        tmpkey = make_new_set(&PyFrozenSet_Type, key);
        if (tmpkey == NULL)
            return -1;
        rv = set_contains_key(so, tmpkey);
        Py_DECREF(tmpkey);
    }
    return rv;
}

Таким образом, это будет делегировать непосредственно set_contains_key который будет по существу хешировать объект, а затем искать элемент, используя его хэш.

Если объект не сотрясается, set_contains_key возвращает -1, поэтому мы получаем внутри if. Здесь мы явно проверяем, является ли переданный key объект множеством (или экземпляром установленного подтипа), и мы ранее получили ошибку типа. Это предполагает, что мы попытались проверить сдерживание с помощью set но это не удалось, потому что оно не сотрясается.

В этой точной ситуации мы теперь создаем новый frozenset из этого set и снова set_contains_key проверить сдерживание с помощью set_contains_key. И поскольку фризонсеты правильно хешируются, мы можем найти наш результат таким образом.

Это объясняет, почему следующие примеры будут работать должным образом, даже если сам набор не хешируется:

>>> set() in {frozenset()}
True
>>> set(('a')) in { frozenset(('a')) }
True