
On 05.07.20 16:56, Stephen J. Turnbull wrote:
Steven D'Aprano writes:
Regarding your observation that dict views behave poorly if they have unhashable values, I agree, it is both odd and makes them less useful. Possibly at some point between the PEP and the release of the feature something changed, or perhaps it's just an oversight.
I'm not sure what you expect from views, though:
Python 3.8.3 (default, May 15 2020, 14:39:37)
set([[1]]) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unhashable type: 'list' {'a' : [1]}.keys() <= {'a' : [1], 'b' : 2}.keys() True {'a' : [1]}.values() <= {'a' : [1], 'b' : 2}.values() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: '<=' not supported between instances of 'dict_values' and 'dict_values' {'a' : [1]}.items() <= {'a' : [1], 'b' : 2}.items() True
So all of the above are consistent with the behavior of sets, except that items views do some part of comparisons themselves to deal with non-hashables which is an extension to set behavior. And values views don't pretend to be sets. The ValuesView ABC is not derived from Set, presumably because dict.values returns something like a multiset.
Most set operations on key and item views seem to convert to set and where appropriate return set (which makes sense, since returning a view would require synthesizing a dict to be the view of!) This means you can't do set operations (except comparisons) on items views if any values aren't hashable.
Well, the point is that this "except comparisons" is not quite true: >>> i = {'a': []}.items() >>> s = {('a', 1)} >>> i == s TypeError: unhashable type: 'list' If passed a set as `other` operand, dict_items seems to decide to convert itself to a set, for no obvious reasons since, as you mentioned, it does know how to compare itself to another view containing non-hashable values: >>> i == {'a': {}}.items() False So if you're dealing with items views and want to compare them to a set representing dict items, then you need an extra `try/except` in order to handle non-hashable values in the items view. Not only does this require an extra precautionary step, it also seems strange given that in Python you can compare all sorts of objects without exceptions being raised. I can't think of any another built-in type that would raise an exception on equality `==` comparison. dict_items seems to make an exception to that rule.
I'm not sure what I think about this:
{'a' : 1, 'b' : 2}.values() == {'b' : 2, 'a' : 1}.values() False
That does seem less than useful. But I guess a multiset comparison requires an auxiliary data structure that can be sorted or a complicated, possibly O(n^2), comparison in place.
dict_values seems to rely on object.__eq__ since they always compare unequal (except when it is the same object); this behavior is mentioned by the docs:
An equality comparison between one `dict.values()` view and another will always return `False`. This also applies when comparing `dict.values()` to itself.
Surely that must be a relic from pre-3.7 days where dicts were unordered and hence order-based comparison wouldn't be possible (though PEP 3106 describes an O(n*m) algorithm). However the current behavior is unfortunate because it might trick users into believing that this is a meaningful comparison between distinct objects (given that it works with `dict.keys` and `dict.items`) when it isn't. So why not make dict_values a Sequence, providing __getitem__ and additionally order-based __eq__ comparison?