1

I'm trying to create a lazy container with typing in python:

T = TypeVar("T")

class Lazy(Generic[T]):
    ...

a = Lazy[str]
issubclass(a, Lazy)

However, I'm getting a TypeError: issubclass() arg 1 must be a class on the last line.
Help to explain this and how I can fix it would be much appreciated.
Code taken from Custom type hint annotation

George Ogden
  • 596
  • 9
  • 15
  • Please put the attribution in the question body itself, thank you. – The Empty String Photographer Aug 07 '23 at 12:36
  • [This](https://stackoverflow.com/questions/49171189/whats-the-correct-way-to-check-if-an-object-is-a-typing-generic) will help to fix it. – ken Aug 07 '23 at 14:09
  • 1
    Hi George, I saw your suggestion edit to my post, but it was actually an answer not an edit, so I rejected it. Please post your answer separately. – S.B Aug 09 '23 at 16:49

2 Answers2

2

I don't know the rationale behind it but it's mentioned in the documentation.

From PEP 484:

Note that special type constructs, such as Any, Union, and type variables defined using TypeVar are only supported in the type annotation context, and Generic may only be used as a base class. All of these (except for unparameterized generics) will raise TypeError if appear in isinstance or issubclass.

Lazy is Generic, When it is subscripted, it becomes a <class 'typing._GenericAlias'> which is apparently not accepted as the first parameter of the issubclass().

For isinstance or issubclass, just use the unparameterized version of your generic classes.

S.B
  • 13,077
  • 10
  • 22
  • 49
  • The rationale behind this decision is clear: `isinstance(something, list[int])` semantically means "`something` is a list of integers", but runtime checks only it's a list. This was prohibited to avoid mistakes caused by such confusion. – STerliakov Aug 09 '23 at 22:17
0

To create such a container, you can do:

class Lazy:
    __origin__: Type[T]
    _registered_types: ClassVar[Dict[Type[T], Type[Lazy[T]]]] = {}

    def __class_getitem__(cls, subcls: Type[T]):
        if subcls not in cls._registered_types:
            # save type for type checking
            # List[T] == List[T]
            cls._registered_types[subcls] = type(
                f"Lazy[{subcls.__name__}]",  # add Lazy to start of name
                (cls, subcls),  # inherit from both Lazy and the subclass
                {"__origin__": subcls},  # store the origin attribute
            )
        return cls._registered_types[subcls]

This is now a type so supports checking subtypes.

a = Lazy[str]
issubclass(a, Lazy)  # True
issubclass(a, Lazy[str])  # True
George Ogden
  • 596
  • 9
  • 15