I want to provide a type hint that is any type.
For example the type can be int
, float
, str
, or even a yet-to-be-created custom type. It is being used in a case where I am created custom types, and I'll be creating more.
So I need something like:
def myfunc(entry: Union[str, float, char, MyCustomType1, MyCustomType2, ...]) -> Bool:
where entry
can be anything as long as it is a type. To be clear, I am not saying it can be anything. E.g. entry
can be int
, but it cannot be 7
.
It is not clear to me what I can provide in the type hint to represent a Union
of any type, including types not yet defined in the module (to allow for the library to grow, and break nothing).
The solution is to use type[Any]
as the annotation. (Docs)
Given the types T1
and T2
, when you annotate a variable x
with T1 | T2
(or Union[T1, T2]
in the old notation), you are saying that x
can be either an instance of T1
or an instance of T2
. So x
can never be the type/class T1
itself or T2
itself.See footnote for exception
Example: (Playground)
x: str | float
x = "spam" # valid
x = 3.14 # valid
x = str # invalid
x = float # invalid
type
is correctSince everything (including types) in Python is an object, everything is an instance of a class. The type of a class is its metaclass. And the base metaclass in Python is type
. Therefore the type of any type is... type
.
You can verify this with the built-in isinstance
function for any given type.
Example:
assert isinstance(str, type)
assert isinstance(int, type)
class Foo:
...
assert isinstance(Foo, type)
This invariant is equivalent to the assertion that everything is an instance of the base class object
.
Example:
assert isinstance("spam", object)
assert isinstance(3.14, object)
class Foo:
...
assert isinstance(Foo(), object)
So if you want a function f
to accept either the type T1
or the type T2
as an argument, you need to write:
def f(x: type[T1 | T2]) -> object:
...
Equivalent:
def f(x: type[T1] | type[T2]) -> object:
...
But if you want it to accept any instance of type
(i.e. including any yet-to-be-created class), you need to use type[Any]
Example: (Playground)
from typing import Any
def f(x: type[Any]) -> object:
...
class Foo:
...
f(str) # valid
f(float) # valid
f(Foo) # valid
f("spam") # invalid
f(3.14) # invalid
f(Foo()) # invalid
Ironically, there are two exceptions to the statement "x: T
means you cannot assign the type T
itself to x
". These two exceptions are object
and type
.
type
is an instance of type
To start with the latter, type
is of course itself a class. And because the base metaclass in Python is type
, this means that type
is an instance of itself or in other words type
is its own type:
assert isinstance(type, type)
object
is an instance of object
For object
, it may get a bit confusing, but bear with me.
Statement 1: Because the type hierarchy in Python defines object
as the ultimate base class, this means even type
is a subclass of object
. So everything that is an instance of type
is also an instance of object
.
Statement 2: But object
is also a type (obviously) and thus an instance of the type
(meta-)class.
Together these two statements imply that object
is its own type.
assert issubclass(type, object) # `object` is the parent class of `type`
assert isinstance(object, type) # `object` has the metaclass `type`
assert isinstance(object, object) # `object` has the metaclass `object`
So annotating a variable with object
also allows you to assign object
itself to it. In fact, there is nothing you cannot assign to it, when it is annotated with object
. (Syntax rules notwithstanding of course.)
Feel free to try: (Playground)
x: object
x = object() # valid
x = object # valid
x = ... # valid