Skip to content

Support iteration on enums #2305

Closed
Closed

Description

@bbc2

Iterating on enums makes mypy (0.4.5) raise errors even though it's OK:

import enum


class Colors(enum.Enum):
    RED = 0
    BLUE = 1


print([color.name for color in Colors])
> python3 color.py 
['RED', 'BLUE']
> mypy color.py 
color.py:9: error: Iterable expected
color.py:9: error: "Colors" has no attribute "__iter__"

(follow-up issue created as suggested in #529)

Activity

  1. added this to the milestone on Oct 24, 2016
  2. elazarg commented on Oct 26, 2016

    @elazarg
    Contributor

    At first sight, we only need #2193

    class Enum:
        ...
        @classmethod
        def __iter__(cls: Type[T]) -> Iterator[T]: ...

    And somehow making Enum itself iterable?

    But it's not so simple, since Enum is a class appearing in the context of an expression, and therefore "coerce" to a callable somewhere in the code. Thus the above code still results in error: Iterable expected. At the point where the error is emitted, the information that it's enum does not seem to exist anymore - it is a simple callable.

  3. dokai commented on Oct 26, 2016

    @dokai

    In terms of making a class itself iterable it seems you have to use a metaclass instead of using just a @classmethod in the class definition.

    In [7]: class A:
       ...:     @classmethod
       ...:     def __iter__(cls):
       ...:         return iter(range(3))
       ...:
    
    In [8]: list(A)
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-8-ed10f13c6f39> in <module>()
    ----> 1 list(A)
    
    TypeError: 'type' object is not iterable
    
    In [9]: class MetaIter(type):
       ...:     def __iter__(self):
       ...:         return iter(range(3))
       ...:
    
    In [10]: class B(metaclass=MetaIter): pass
    
    In [11]: list(B)
    Out[11]: [0, 1, 2]
  4. gvanrossum commented on Oct 26, 2016

    @gvanrossum
    Member
  5. gvanrossum commented on Oct 27, 2016

    @gvanrossum
    Member
  6. elazarg commented on Oct 27, 2016

    @elazarg
    Contributor

    I didn't check yet.

    As you said, it's not how mypy work. I think it should, but I guess it will require a big change.

    (I wonder whether mypy should mimic Smalltalk's object model. It's very elegant, and perhaps we can think of Python as "hiding the theory" (e.g. that type(type) != type) for the convenience of the user. Similarly to the -type-in-type flag in COQ, which collapses the universe of types into a single one.
    Yes, I know, I get carried away sometimes. Sorry)

  7. added a commit that references this issue on Feb 8, 2017
    bebb49a
  8. removed this from the milestone on Mar 29, 2017
  9. added 2 commits that reference this issue on Apr 4, 2017
    2a8bb31
    78d9f8e
  10. added a commit that references this issue on Apr 4, 2017
    7027c3e
  11. gvanrossum commented on Apr 21, 2017

    @gvanrossum
    Member

    Fixed by #1136.

  12. 11 remaining items

  13. srittau commented on Jan 31, 2018

    @srittau
    Contributor

    For reference, using mypy 0.560, the following:

    from enum import Enum
    
    class Foo(Enum):
        X = "x"
    
    reveal_type(list(Foo))
    reveal_type(set(Foo))

    will print

    foo.py:6: error: Revealed type is 'builtins.list[enum.Enum*]'
    foo.py:7: error: Revealed type is 'builtins.set[enum.Enum*]'
    

    I'd expect this to be builtins.list[Foo] and builtins.set[Foo].

  14. elazarg commented on Jan 31, 2018

    @elazarg
    Contributor

    This example seems to work on master

  15. srittau commented on Jan 31, 2018

    @srittau
    Contributor

    I will report back when 0.570 is released.

  16. added a commit that references this issue on Feb 13, 2018
  17. ilevkivskyi commented on Feb 13, 2018

    @ilevkivskyi
    Member

    All examples I have found in this issue now work on mypy master.

  18. otchita commented on Oct 8, 2020

    @otchita

    I'm seeing this issue when using a subclass of MultiValueEnum on mypy==0.782

    test.py:

    from aenum import MultiValueEnum
    
    class MyEnum(MultiValueEnum):
        A = 1, 2
        B = 3
        C = 4
    
    for x in MyEnum:
        print(x)
    > mypy test.py
    test.py:8: error: "Type[MyEnum]" has no attribute "__iter__" (not iterable)
    
  19. JelleZijlstra commented on Oct 8, 2020

    @JelleZijlstra
    Member

    @otchita that might be an issue with the stubs for aenum.

  20. harahu commented on Aug 23, 2022

    @harahu

    @ilevkivskyi I'm seeing this issue when trying to express a function that should work for enums in general. Simplified example:

    from enum import Enum
    from typing import Type
    
    
    class Color(Enum):
        RED = 1
        GREEN = 2
        BLUE = 3
    
    
    def enum_getter(x: Type[Enum]) -> Enum:
        return next(iter(x))
    
    
    x = enum_getter(Color)

    This code runs in practice, but i get the following type checking error:
    error: Argument 1 to "iter" has incompatible type "Type[Color]"; expected "SupportsIter[<nothing>]"

  21. colatkinson commented on Jan 19, 2023

    @colatkinson

    Confirming that I'm running into the same behavior as @harahu on 0.991. Possibly related: #12553.

    Interestingly, if that example is modified slightly, it does work:

    from enum import Enum
    from typing import Type
    
    
    class Color(Enum):
        RED = 1
        GREEN = 2
        BLUE = 3
    
    
    def enum_getter(x: Type[Enum]) -> Enum:
        for v in x:
            return v
        assert False, "unreachable"
    
    
    x = enum_getter(Color)

    So it seems that as far as mypy is concerned Enums support iteration, but not iter().

    This also makes the error go away:

    from enum import Enum
    from typing import Type, Iterable
    
    
    class Color(Enum):
        RED = 1
        GREEN = 2
        BLUE = 3
    
    
    def enum_getter(x: Type[Enum]) -> Enum:
        y: Iterable[Enum] = x
        return next(iter(y))
    
    
    x = enum_getter(Color)

    Not sure if this is exactly the same problem as the original -- perhaps a maintainer could advise as to whether a new issue is warranted.

  22. ilevkivskyi commented on Jan 29, 2023

    @ilevkivskyi
    Member

    Yes, this is most likely #12553 (which seems easy to fix, so I may submit PR there soon).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions