Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error on dict construction from iterable items #9938

Open
jaraco opened this issue Jan 22, 2021 · 2 comments
Open

Error on dict construction from iterable items #9938

jaraco opened this issue Jan 22, 2021 · 2 comments
Labels
bug mypy got something wrong

Comments

@jaraco
Copy link
Member

jaraco commented Jan 22, 2021

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
@gvanrossum
Copy link
Member

gvanrossum commented Jan 22, 2021

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))

@jaraco
Copy link
Member Author

jaraco commented Jan 22, 2021

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
Labels
bug mypy got something wrong
Projects
None yet
Development

No branches or pull requests

2 participants