Skip to content

Underdocumented and partially untested behavior of set-like dict views #96408

Description

@filiplajszczak

Unlike sets, set-like dict view objects behave identical with both operator and non-operator versions of set operations.

In case of sets only non-operator versions accept any iterable as an argument, and one has to explicitly convert iterable to set to use operator on it. That's documented and tested.

s = {"a", "b"}
s.union(["a", "c"])  # valid
s | ["a", "c"]       # raises TypeError

Set-like dict view objects, however, accept any iterable after an operator.

d = {"a": "A", "b": "B"}
d.keys() | ["a", "c"]     # valid
d.items() | [("c", "C")]  # valid

As far as we were able to find that difference in behavior is not documented but is tested for keys but not for items. It's also reflected in the typeshed stubs.

Activity

  1. rhettinger commented on Aug 30, 2022

    @rhettinger
    Contributor

    The dict view docs intended cover these behaviors in the final sentence:

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

    However, if you didn't already know the differences between concrete set API and the abstract Set API, I could see how this particular point is easily missed, so perhaps an extra sentence highlighting the difference would be warranted:

    Those operations difference from the corresponding set operations by supporting any iterable as a input.

    As you say, the tests need to be beefed-up. They test registration with KeysView, ValuesView, and ItemsView but do not test compliance with those APIs.

    Would you like to submit a PR?

  2. added a commit that references this issue on Aug 30, 2022
  3. added 2 commits that reference this issue on Aug 30, 2022
  4. added a commit that references this issue on Aug 30, 2022
  5. added a commit that references this issue on Aug 31, 2022
  6. added a commit that references this issue on Aug 31, 2022
  7. rhettinger commented on Aug 31, 2022

    @rhettinger
    Contributor

    I've just reconsidered having an example and now think it should be taken out. It was correct to document that iterables were supported but we don't want an example to encourage it. It's too easy to fall into keys | 'abc' instead of keys | ['abc'].

  8. filiplajszczak commented on Aug 31, 2022

    @filiplajszczak
    ContributorAuthor

    If I understand correctly, it (possibility of easy error) was the reason to make it impossible for sets.

    Maybe it could be possible, to replace keys example with items one with tuple of tuples instead of list of strings as the other operand.

  9. caseneuve commented on Sep 1, 2022

    @caseneuve
    Contributor

    Aren't we heading into a more general discussion now (where my opinion would be: if an API is exposed, it should be accessible and documented; if, on the other hand, it's considered harmful, it shouldn't be exposed...)?

    Just for the background of this ticket (it's a real life scenario, so please bare with me): we had a bit of code in our codebase where we used a sorted(a_dict.keys()) bit; then we discovered that those keys don't cover the data we need, and we wanted to extend it like this sorted(a_dict.keys() + ['missing', 'data']), which obviously failed. Then we tried this (it was an intuitive call): sorted(a_dict.keys() | ['missing', 'data']) which worked well and seemed somewhat elegant, but we realized that we can't find neither docs for this behavior, nor examples. Thus we went back to sorted(list(a_dict.keys()) + ['missing', 'data']), which -- it's a matter of personal opinion, I guess -- is not so elegant, but we were doc-covered at least. TL;DR: discovering an elegant and concise API that suited our need but was hard enough to find in the documention was quite confusing and upsetting :)

  10. pochmann commented on Sep 2, 2022

    @pochmann
    Contributor

    @caseneuve Just alternatives for your example:

    sorted(a_dict.keys() | {'missing', 'data'})
    sorted(set(a_dict) | {'missing', 'data'})
    sorted([*a_dict, 'missing', 'data'])
    sorted({*a_dict, 'missing', 'data'})
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions