2

In Python 3.7, I'd like to calculate the intersection of two dictionaries' keys. To do this, I'd like to call the .intersection() method on their keys(), however it does not work.

.keys() produces a set-like object, however most set methods don't work on it. What works however is the extremely unknown bitwise operator overloads for set-like objects, like &.

m = {'a':1, 'b':2}
n = {'b':3, 'c':4}

m.keys().intersection(n.keys())  # Pythonic, but doesn't work

m.keys() & n.keys()  # works but not readable

set(m.keys()).intersection(set(n.keys()))  # works, readable, but too verbose

I find that the & overload on a set-like object is extremely rarely used and is not known by most programmers. Method names like .intersection() or .union() is self-documenting and definitely more Pythonic by this definition.

Why isn't it supported then? Even the documentation lists the & and .intersection() methods like aliases, not mentioning that only & is supported on set-like objects.

note: For some reason, in IPython the autocomplete lists .isdisjoin() as a method which is available on dict.keys(). Out of the 17 set methods, 1 is present.

hyperknot
  • 13,454
  • 24
  • 98
  • 153
  • 1
    If you're worried about being too verbose, you don't need to cast `n.keys` to `set` in the last example: `set(m.keys()).intersection(n.keys())` – sacuL Nov 01 '18 at 02:13
  • While not disagreeing with the general point that the methods should be supported to match expectations, I find the overloads of the various operators rather intuitive, and prefer them to the overly verbose (and often confusing) names of the methods. I know what all the bitwise-operators do by analogy to what they do for bits; `^` makes sense immediately, but `symmetric_difference` always takes a moment to register; `<` and `<=` are immediately clear, but which one `issubset` mimics is not. – ShadowRanger Nov 01 '18 at 02:17
  • 1
    @sacul: For that matter, you don't need to call `.keys()`; `set(m).intersection(n)` will work equivalently, since `dict`s already act like unique iterables of their own values. The *only* reason to bother with `.keys()` is to get the set-like behaviors; if you're converting to `set` anyway, you don't need it. – ShadowRanger Nov 01 '18 at 02:21
  • @ShadowRanger so you are saying that the cleanest code is actually: `set(m).intersection(n)`, right? – hyperknot Nov 01 '18 at 02:30
  • @hyperknot: Or just `m.keys() & n.keys()` (and actually, `m.keys() & n` works just fine too, but it feels weird to leave it asymmetric like that). – ShadowRanger Nov 01 '18 at 02:36

2 Answers2

2

dict views only guarantee the API of collections.abc.Set, not to be equivalent to set itself:

For set-like views, all of the operations defined for the abstract base class collections.abc.Set are available (for example, ==, <, or ^).

So they're not claiming to match set, or even frozenset, just collections.abc.Set collections.abc.Set doesn't require any of the named methods aside from isdisjoint (which has no operator equivalent).

As for why collections.abc.Set doesn't require the named methods, it may be because they are somewhat more complex (most take varargs, and the varargs can be any iterable, not just other set-like things, so you can, for example, intersection with many iterables at once), and they may have wanted to limit the complexity required to implement new subclasses (especially virtual subclasses, that wouldn't inherit any of the methods collections.abc.Set might choose to provide).

All that said, I generally agree with your point that it seems needlessly inconsistent to omit the method forms. I'd recommend you open a bug on the Python bug tracker requesting the change; just because it only guarantees Set compatibility doesn't mean it can't do more.

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
0

The format should be

set.intersection(*others)

where other is any iterable. m.keys() is dict_keys not a set so that won't work.

set(m.keys()).intersection(n.keys())

will work :)