I thought I understood the relationship between set and frozenset in Python, but something magic is happening with set membership (set1 in set2) and I don't see how it can work.
A set of frozenset works:
>>> s = set()
>>> s.add(frozenset(['hello', 'world']))
>>> frozenset(['hello', 'world']) in s
True
I can't add unhashable types, such as list, to my set, and I can't use the in operator with unhashable types either:
>>> s.add(['hello', 'world'])
TypeError: unhashable type: 'list'
>>> ['hello', 'world'] in s
TypeError: unhashable type: 'list'
Likewise I can't add a set to my set:
>>> s.add({'hello', 'world'})
TypeError: unhashable type: 'set'
...but I can use in with a set to check whether the corresponding frozenset is a member:
>>> {'hello', 'world'} in s
True
...and it gives the right result:
>>> {'jello', 'world'} in s
False
Why is set1 in set2 special? Is it really calculating the hash of my set before testing membership? Or is it falling back on brute force?
Edit: found it, the C implementation of set.__contains__ knows how to call call key = frozenset(key) whenever hash(key) throws TypeError and isinstance(key, set).
https://github.com/python/cpython/blob/6c7d67c/Objects/setobject.c#L1890-L1897
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);