Special attributes in sequences having different types - error: "object" has no attribute "__name__"; maybe "__ne__" or "__new__"?

Viewed 460

I want to use a collection that has both Enums and Enum members. But I can't find a way to write it that doesn't cause mypy errors when I try to access the Special Attribute __name__. (See a list of when __name__ can be expected types and members.)

For context a brief explanation about what happens is given by the BDFL in mypy issue #3728. Basically when you iterate over a collection that has items of different types they are joined. So in the next example what mypy is saying is that the Enum and the Enum member are joined to object...

Example 1:

from enum import Enum
from typing import Type

class Color(Enum):
    RED = 1

my_collection: tuple[Type[Color], Color] = (Color, Color.RED)

for item in my_collection:
    if item is Color:
        print(item.__name__)

mypy gives the error:

error: "object" has no attribute "__name__"; maybe "__ne__" or "__new__"?

Example 2:

Next I tried to annotate the variable in the for loop to see if the mypy error gained some more color...

from enum import Enum
from typing import Type, Union

class Color(Enum):
    RED = 1

my_collection: tuple[Type[Color], Color] = (Color, Color.RED)

item: Union[Type[Color], Color]
for item in my_collection:
    if item is Color:
        print(item.__name__)

This time mypy gave 2 errors, the first caused by the incompatible types in the for loop (makes sense), and the second expectable error saying the Enum member doesn't have the __name__ special attribute.

error: Incompatible types in assignment (expression has type "object", variable has type "Union[Type[Color], Color]")

error: Item "Color" of "Union[Type[Color], Color]" has no attribute "__name__"

Example 3:

My next step was trying typing.cast before using __name__ which usually is enough for mypy to restrict the type...

from enum import Enum
from typing import Type, cast

class Color(Enum):
    RED = 1

my_collection: tuple[Type[Color], Color] = (Color, Color.RED)

for item in my_collection:
    if item is Color:
        item = cast(Type[Color], item)
        print(item.__name__)

This time the error was:

error: has no attribute "__name__"

The 3 examples work at run-time, but what's the Pythonic way of iterating over a collection having items of different types that doesn't cause mypy errors? (Using Python 3.9 and mypy 0.812 with default configurations.)

1 Answers

I looked at several issues on the MyPy's GitHub and I learned two things :

  1. <nothing> should not be showed in the output, according to Guido
  2. <nothing> means that MyPy union'ed the types (for item it is Type[Color] and Color) and the result is nothing (i.e. "an empty set"), so that it types to nothing. In other words, MyPy fails to compute a useful type for item after the cast, see :
    reveal_type(my_collection[0])
    reveal_type(my_collection[1])
    for item in my_collection:
        if item is Color:
            reveal_type(item)
            item = cast(Type[Color], item)
            reveal_type(item)
            print(item.__name__)
    
    $ mypy -V; mypy so70636782.py
    mypy 0.931
    so70636782.py:9: note: Revealed type is "Type[so70636782.Color]"
    so70636782.py:10: note: Revealed type is "so70636782.Color"
    so70636782.py:13: note: Revealed type is "builtins.object"
    so70636782.py:15: note: Revealed type is "<nothing>"
    so70636782.py:16: error: <nothing> has no attribute "__name__"
    Found 1 error in 1 file (checked 1 source file)
    
    I think you hit a limitation of the tool. You may post an issue on GitHub for that.

MyPy is not very good with unions in my experience, so that I recommend you just use the fact that your tuples are typed couples :

color_class, color = my_collection  # unpacking
print(color_class.__name__)

This way MyPy can keep track individually of each variable, each having a type.

Related