Is `typing.Never` really a bottom type?

With issubclass(), trying to check if typing.Never is a bottom type gets the error as shown below:

*Memo:

from typing import Never


print(issubclass(Never, int))
# TypeError: issubclass() arg 1 must be a class

Next, with type(), checking the type of typing.Never returns typing._SpecialForm as shown below:

from typing import Never

print(type(Never))
# <class 'typing._SpecialForm'>

Finally, with issubclass(), checking if typing._SpecialForm is a bottom type returns False as shown below:

from typing import _SpecialForm

print(issubclass(_SpecialForm, int))
# False

Now, is typing.Never really a bottom type?

You can use this to judge whether it is a type:

isinstance(Never, type)

It got “False”, so “Never” is not a type.

The statement “Never is a bottom type” is about static behavior, not runtime behavior. You are only checking runtime behavior, not static behavior. Never is a type according to these definitions. But it’s not a runtime class. Similar to list[int], Any or a type Alias.

3 Likes

The _SpecialForm is an implementation detail. In the view of type checkers, it still is a bottom type (or rather: all typing._SpecialForm instancs are).

Runtime behavior often differs from behavior at type checking time.

1 Like

There’s a difference between types and classes. A class is an object that can create other objects. A type is a collection of objects with some shared behaviour and properties. When we write something like a: int we are using the class int to refer to the type of all objects that can be created by the int class. But some types aren’t just all objects that can be created by some class. For example list[int] doesn’t include every list object and there exists no class that can create every object in the str | int type.

So even though Never isn’t a class, it certainly is the bottom type. The issubclass builtin rejects it, because what it does isn’t type checking, but class inheritance checking. The type builtin is somewhat confusingly named. When called as type(some_value), it returns the object’s class, which is related to the types that contain the object, but not the same. When used as a class like in isinstance(Never, type) it doesn’t refer to the set of all types, but simply the class object that creates other class objects. So what isinstance(something, type) does isn’t checking whether something is a type in the typing sense, but just whether the object something is an instance of the builtin class called type.

1 Like