非冻结集的集成员身份

时间:2018-09-18 09:10:18

标签: python set

我以为我了解Python中setfrozenset之间的关系,但是集合成员身份(set1 in set2)发生了一些不可思议的事情,我不知道它如何工作。

set中的frozenset起作用:

>>> s = set()
>>> s.add(frozenset(['hello', 'world']))
>>> frozenset(['hello', 'world']) in s
True

我无法将list之类的不可哈希类型添加到我的set中,也无法将in运算符用于不可哈希类型:

>>> s.add(['hello', 'world'])
TypeError: unhashable type: 'list'
>>> ['hello', 'world'] in s
TypeError: unhashable type: 'list'

同样,我无法在自己的集合中添加set

>>> s.add({'hello', 'world'})
TypeError: unhashable type: 'set'

...但是我可以将inset一起使用,以检查相应的frozenset是否是成员:

>>> {'hello', 'world'} in s
True

...它给出了正确的结果:

>>> {'jello', 'world'} in s
False

set1 in set2为什么特别?在测试成员资格之前,它真的在计算我的集合的哈希值吗?还是回到蛮力状态?

编辑:找到了它,set.__contains__的C实现知道每当key = frozenset(key)抛出hash(key)和{{1 }}。

https://github.com/python/cpython/blob/6c7d67c/Objects/setobject.c#L1890-L1897

TypeError

1 个答案:

答案 0 :(得分:4)

看看源代码,set_contains的当前实现(3.7)表明,在检查集合中的成员资格时,确实将集合对象转换为冻结集合:

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;
}

基本上,如果给定对象是集合(PySet_Check(key)),则将创建新的冻结集合(make_new_set(&PyFrozenSet_Type, key)),并再次检查成员资格(set_contains_key(so, tmpkey))。我认为这实际上并没有记录在任何地方,它可能意味着它是一个“可以正常使用”的功能,而您甚至没有注意到如果您考虑得并不认真的话。

它似乎与被拒绝的PEP-0351具有相同的精神,尽管它不是基于任何特殊方法,并且仅针对集合。

编辑:有关更多详细信息,显然此功能最早是在commit的请求下由Raymond Hettinger在2003(Alex Martelli)上实现的。因此,如果其中一个碰巧在附近,也许他们可以为此提供更多背景信息。

编辑2:值得注意的是,这在特定情况下可能会对性能产生重大影响:

s = set(range(100000))
sf = frozenset(s)
t = { sf }
%timeit sf in t  # True
>>> 31.6 ns ± 1.04 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
%timeit s in t  # True
>>> 4.9 ms ± 168 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

在第二种情况下,测试速度降低了五个个数量级!

编辑3:我raised an issue为Python小组讨论了不赞成使用此行为的可能性,因为它似乎前后矛盾且可能存在问题。开发人员认为它仍然是一个有用的功能,缺点不值得破坏兼容性。