Skip to content

Classes that implement getitem are reported as incompatible with enumerate #9737

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

Closed
scuac opened this issue Nov 19, 2020 · 8 comments
Closed
Labels
bug mypy got something wrong

Comments

@scuac
Copy link

scuac commented Nov 19, 2020

Bug Report

Mypy reports erroneously that a class that implements getitem is not suitable to be used with enumerate.

To Reproduce

Small example:

class A:
    def __init__(self):
        self.data = [1, 2, 3]

    def __len__(self):
        return len(self.data)

    def __getitem__(self, item):
        return self.data[item]


a = A()
for i, item in enumerate(a):
    print(f'item {i} = {item}')

Expected Behavior

Running mypy on the above code should report no errors

Actual Behavior

Mypy reported:

error: Argument 1 to "enumerate" has incompatible type "A"; expected "Iterable[<nothing>]"

Your Environment

  • Mypy version used: 0.790
  • Mypy command-line flags: none
  • Mypy configuration options from mypy.ini (and other config files):
[mypy]
ignore_missing_imports = True
  • Python version used: 3.7.6
  • Operating system and version: macOS 10.15.7
@scuac scuac added the bug mypy got something wrong label Nov 19, 2020
@JelleZijlstra
Copy link
Member

This is because we don't fully understand the "old-style" iteration protocol, where you make an iterable with just __getitem__ and __len__. I believe this has been discussed before but I'm not sure where.

@JelleZijlstra
Copy link
Member

An easy fix is to add def __iter__(self): return self.

@scuac
Copy link
Author

scuac commented Dec 2, 2020

An easy fix is to add def __iter__(self): return self.

That doesn't work. It does make the mypy error go away but the code fails with
TypeError: iter() returned non-iterator of type 'A'

For now the only work-around I found it to append a # type: ignore to the line with enumerate. But that is cumbersome since it needs to be done every time this class is used in a loop.

@JelleZijlstra
Copy link
Member

Maybe you can put the def __iter__ inside an if TYPE_CHECKING: block.

@scuac
Copy link
Author

scuac commented Dec 2, 2020

This is because we don't fully understand the "old-style" iteration protocol, where you make an iterable with just __getitem__ and __len__. I believe this has been discussed before but I'm not sure where.

From what I could find, it seems the protocol is like this:
IF __iter__ is not present, fallback to iterating by repeatedly calling __getitem__ with integers starting from 0 (i.e. 0, 1, 2, ...) and stop when receiving an IndexError.

So, the requirement for this to work is that the __getitem__ method has to work with integers as input and raise IndexError, which is the case in the example I gave. That being said, it seems it would be too complicated for mypy to figure that out without assistance, so I guess that it raising the error is correct.

I tried updating the __getitem__ in the example to make all of this explicit, but still get the error:

    def __getitem__(self, index: int):
        if index < 0 or index >= len(self.data):
            raise IndexError
        return self.data[index]

@scuac
Copy link
Author

scuac commented Dec 2, 2020

Maybe you can put the def __iter__ inside an if TYPE_CHECKING: block.

That works, and I like it better than the comment since it is in the class and doesn't need to be put in every loop.

@scuac
Copy link
Author

scuac commented Dec 2, 2020

Well, it seems that extending a class similar to my example is quite easy after all, simply add:

    def __iter__(self):
        for item in self.data:
            yield item

And everything works.

@scuac scuac closed this as completed Dec 2, 2020
@hauntsaninja
Copy link
Collaborator

You could do return iter(self.data) to save yourself a line :-)

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

3 participants