Skip to content

Error on dict construction from iterable items #9938

Open

Description

@jaraco

Bug Report

Attempting to construct a mapping from objects that are designed to be constructed into dict items results in an error.

To Reproduce

Run mypy on this file:

from typing import Dict


class MyObject(str):
    """
    >>> tuple(MyObject('G'))
    ('G', 71)
    """
    def __iter__(self):
        return iter((self, ord(self)))


def get_mapping() -> Dict[str, int]:
    """
    >>> get_mapping()
    {'a': 97, 'b': 98}
    """
    items = MyObject('a'), MyObject('b')
    return dict(items)

In this file, MyObject implements __iter__ to return a key/value pair such that a dict can be constructed from an iterable of MyObjects.

Note that python -m doctest dictable.py passes.

Expected Behavior

Mypy should honor dict's behavior to produce pairs from objects if they're iterable and accept any iterable of objects that when iterated on produce pairs of compatible types.

Actual Behavior

dictable.py:18: error: Argument 1 to "dict" has incompatible type "Tuple[MyObject, MyObject]"; expected "Iterable[Tuple[str, int]]"
Found 1 error in 1 file (checked 1 source file)

Your Environment

  • Mypy version used: 0.790
  • Mypy command-line flags: none
  • Mypy configuration options from mypy.ini (and other config files): none
  • Python version used: 3.9.0
  • Operating system and version: macOS 11.0

Activity

  1. gvanrossum commented on Jan 22, 2021

    @gvanrossum
    Member

    Hi @jaraco, I think that's a reasonable error -- mypy doesn't keep track of the length of the sequence returned by __iter__(), since in most cases this is not statically known. Do you have a realistic use case?

    I guess you could rewrite this as follows to help mypy along:

    return dict((it, ord(it)) for it in items)
    

    Or if you want MyObject to be in control, you could give it a helper method that returns a (self, ord(self)) tuple, and do something like

    return dict(helper(it) for it in items)
    

    or

    return dict(map(helper, items))
    
  2. jaraco commented on Jan 22, 2021

    @jaraco
    MemberAuthor

    Hey Guido. Thanks for the quick response.

    The real-world use-case is in how importlib.metadata exposes EntryPoint objects. I'd like for consumers of entry points to be able to call the API function importlib.metadata.entry_points()[group] and readily convert that into a lookup by name (by far the most common use-case). I'd like not to require every consumer to construct their own dict by name or have to import a helper to construct the dict by name.

    The error emerged in pypa/twine#728 when attempting to demonstrate this more concise approach when switching from pkg_resources.

    I think if I'd known this approach was effectively invalid, I'd probably have come up with a different design for querying entry points, something that involved custom containers that could give consumers a clean API with one import.

    Given that the type system can't effectively honor this use-case, would you recommend to avoid the use case or to ignore it for mypy checks?

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

    bugmypy got something wrong

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions