pythonpython-typing

Python How to type hint the class Any itself


The Problem And Approach #1

I have a function that has a parameter which can take some specific types and the type Any (the class itself), which in my case is also the default. Here is a simple example:

def foo(type: type[int] | type[str] | type[Any] = Any) -> Any:
    ...

The problem is that this represents something different than what I want. Using Any as the single type hint argument of type makes it act like the usual use case for Any, which is that the parameter can be a type of anything. This causes multiple issues for type checking purposes:

  1. Now any kind of class is allowed, not just the class Any itself, which defeats the purpose of type checking.
  2. My type checker (Pylance) even sees this as an error, claiming Type "type[typing.Any]" is not assignable to declared type "builtins.type[Any]

Approach #2

As a second approach I looked into the code and found this code that defines Any (Python 3.13):

class _AnyMeta(type):
    def __instancecheck__(self, obj):
        if self is Any:
            raise TypeError("typing.Any cannot be used with isinstance()")
        return super().__instancecheck__(obj)

    def __repr__(self):
        if self is Any:
            return "typing.Any"
        return super().__repr__()  # respect to subclasses


class Any(metaclass=_AnyMeta):
    """Special type indicating an unconstrained type.

    - Any is compatible with every type.
    - Any assumed to have all methods.
    - All values assumed to be instances of Any.

    Note that all the above statements are true from the point of view of
    static type checkers. At runtime, Any should not be used with instance
    checks.
    """

    def __new__(cls, *args, **kwargs):
        if cls is Any:
            raise TypeError("Any cannot be instantiated")
        return super().__new__(cls)

So I saw that Any has the metaclass _AnyMeta and therefore tried importing it and simply type hinting my function like this:

def foo(type: type[int] | type[str] | _AnyMeta = Any) -> Any:
    ...

The problem here is that I cannot import or use _AnyMeta from the typing module. I actually don't know why, as _AnyMeta doesn't get deleted with del anywhere in the source code of the typing module.

Summary

So I could not figure out how to type hint the Any class itself. I also couldn't find any similar questions or answers online that could answer this. I am left wondering if this is even possible in the current version of Python (3.13), but I hope that somebody might know how to. Technically I could use some kind of sentinel object instead of the Any type, but I'm still curious if this is possible. It would also be a more elegant solution for my problem if it was possible.


Solution

  • At type checking time, Any is not a normal class. It is a one-of-a-kind singleton, a special form, that is treated specially by type checkers.


    Yes, at runtime it is a class. typeshed also defines it as a class. Any can be used as a base class, even:

    (playgrounds: Pyright, Mypy, Pyrefly, ty)

    class C: ...
    class D(Any): ...
    
    C().foo = 1  # error: `C` instances have no declared attribute `foo`
    D().foo = 1  # fine, `D` instances are treated the same as `Any`
    

    That Any is not assignable to type[Any] is an implementation-specific detail. Some other type checkers do treat it as a class:

    (playgrounds: Pyright, Mypy, Pyrefly, ty)

    a: type[Any] = Any  # pyright => error: Type "type[typing.Any]" is not assignable to declared type "builtins.type[Any]"
                        # mypy    => fine
                        # pyrefly => fine
                        # ty      => fine
    

    Pyright/Pylance's maintainers, however, have chosen not to do so:

    [...] Any is not assignable to type. Any is a special form. Its implementation details are not documented or defined in typeshed. Type checkers should therefore not make assumptions about its runtime implementation.


    In the comment section, PEP 747's TypeForm was suggested as a possible solution, but, at least as of writing, the PEP does not define a way to specify the type of Any itself. Surprisingly (or maybe not), TypeForm[Any] does not mean just Any:

    TypeForm[Any] describes a TypeForm type whose type argument is not statically known but is a valid type form object. It is thus assignable both to and from any other TypeForm type (because Any is assignable both to and from any type).

    § Specification