pythonmypypython-typing

How to type annotate optional class type param


I have following code

from typing import TypeVar, Type, overload

T = TypeVar('T')


@overload
def foo(bar: Type[T]) -> T:
    ...

@overload
def foo(bar: Type[T] | None) -> T | None:
    ...


def foo(bar: Type[T] | None) -> T | None:
    # implementation goes here
    ...


class Bar:
    ...


bar = foo(Bar)
bar2 = foo(Bar | None)  # No overload variant of "foo" matches argument type "UnionType"

How to properly type hint case for bar2?


I tried some others:

Type[T | None], mypy says Overloaded function signature 2 will never be matched: signature 1's parameter type(s) are the same or broader

removing 2nd overload (resulting in only Type[T] allowed), mypy says No overload variant of "foo" matches argument type "UnionType" (meaning 2nd overload is incorrect for that case anyways)


Solution

  • type[T] implies something that can be invoked to retrieve an instance of type T. type[T] | None means such a thing, or None; thus, bar might be invocable.

    def foo(bar: type[T] | None) -> None:
        if bar is not None:
            reveal_type(bar)  # type[T]
            instance = bar()  # fine
    

    However, Bar | None returns an instance of UnionType at runtime, and objects of this kind cannot be invoked.

    foo(Bar | None)       # (Bar | None) is not None
                          # bar() => error
    

    You seem to want the proposed TypeForm, which, as of yet, only exists in a PEP draft:

    @overload
    def foo[T](bar: TypeForm[T]) -> T:
        ...
    
    @overload
    def foo[T](bar: TypeForm[T | None]) -> T | None:
        ...
    
    def foo[T](bar: TypeForm[T | None]) -> T | None:
        ...
    

    To quote the draft:

    Both TypeForm[] and type[] can be used to constrain the same type variable within the same function definition:

    def as_instance[T](form: TypeForm[T]) -> T | None:
        return form() if isinstance(form, type) else None