Should it be possible to use None as a type (instead of NoneType)?

I think it’s important here to highlight the distinction between types and classes. Classes are Python objects that are used to make other objects, things like int or str. Types are sets of objects that share some common properties and behaviours. Some types like “the set of all integers” or “the set of all strings” just are the sets of objects that are instances of a particular class, so we just use the class’s name to spell those types. So int and str also are types in addition to being classes. But some more complicated types aren’t collections of all instances of one single class. For example, int | str or list[str].

The thing with isinstance is that it conceptually doesn’t do type checking but class instance checking. For simple types like int or str those are the same thing, but not for the more complex types. In principle, the second argument of isinstance must specify a class, not a more general type. This is the reason why you’ll get an error when you call e.g. isinstance([1, 2, 3], list[int]).

But isinstance also has two layers of ergonomics in it: first, people wanted to check whether an object is an instance of not just one specific class but one of several options, so the isinstance(1, (str, int)) overload was introduced. Note that here the second argument again isn’t actually a type, it’s a tuple of classes. Then once we had union syntax people saw that the semantics of asking whether an object is an instance of one of several classes actually is the same as asking whether it is a member of the union of those classes. Because of that, another overload was added and isinstance now also accepts (non-empty) unions.

In my (and several other peoples’) opinion, that last option was a mistake. It muddles the waters by making it seem like isinstance can handle arbitrary types as the second argument and checks type membership. When really it just accepts a selection of classes and checks instance relations. This also is why it doesn’t accept None and only NoneType. While the former is a valid way to express the type that contains the object None, that isn’t what isinstance actually deals with.

In general, you probably won’t get around having to add a bunch of other checks and special cases to functions like the one you’re describing, even if None is special cased by isinstance. Type checking is a fairly involved process and most types can’t just be thrown into isinstance. This one special case might make some code that only cares about very specific types a bit cleaner, but in general it doesn’t really help much and just adds confusion over what exactly a fairly simple built in actually is supposed to do.

14 Likes