我以为我了解Python中set
和frozenset
之间的关系,但是集合成员身份(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'
...但是我可以将in
与set
一起使用,以检查相应的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
答案 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小组讨论了不赞成使用此行为的可能性,因为它似乎前后矛盾且可能存在问题。开发人员认为它仍然是一个有用的功能,缺点不值得破坏兼容性。