[Python-ideas] Runtime types vs static types

Steven D'Aprano steve at pearwood.info
Sun Jul 2 07:54:22 EDT 2017

On Sun, Jun 25, 2017 at 09:13:44AM -0700, Lucas Wiman wrote:
> >
> > For some background on the removal of __instancecheck__, check the linked
> > issues here:
> >
> Thanks for the reference (the most relevant discussion starts here
> <https://github.com/python/typing/issues/136#issuecomment-104698674>).
> That said, I think I totally disagree with the underlying philosophy of
> throwing away a useful and intuitive feature (having `is_instance(foo,
> Union[Bar, Baz])` just work as you'd naively expect) in the name of making
> sure that people *understand* there's a distinction between types and
> classes.

Yes... I agree. I think Mark Shannon has an exaggerated preference for 
"purity over practicality" when he writes:

    Determining whether a class is a subclass of a type is 
    meaningless as far I'm concerned.


That implies that runtime types ("classes") and static types are 
completely unrelated. I don't think that's true, and I think that would 
make static types pointless if it were true.

I'd put it this way... runtime types are instantations of static types 
(not *instances*).


If we didn't already use the terms for something else, I'd say that 
static types are *abstract types* and runtime types ("classes") are 
*concrete types*. But that clashes with the existing use of abstract 
versus concrete types.

I can see that there are actual problems to be solved, and *perhaps* 
Mark's conclusion is the right one (even if for the wrong reasons). For 

    isinstance([], List[int])
    isinstance([], List[str])

How can a single value be an instance of two mutually incompatible 
types? (But see below, for an objection.)

On the other hand, just because a corner case is problematic, doesn't 
mean that the vast majority of cases aren't meaningful. It just seems 
perverse to me to say that it is "meaningless" (in Mark's words) to ask 

    isinstance(['a', 'b'], List[int])

    isinstance(123, List[str])

for example). If static type checking has any meaning at all, then the 
answers to those two surely have to be False.

> This seems opposed to the "zen" of python that there should be exactly one
> obvious way to do it, since (1) there isn't a way to do it without a third
> party library, and (2) the obvious way to do it is with `isinstance` and
> `issubclass`. Indeed, the current implementation makes it somewhat
> nonobvious even how to implement this functionality yourself in a
> third-party library (see this gist
> <https://gist.github.com/lucaswiman/21373bea33ccd2c5e868ec52b6eff412>).

I think that the current status is that the MyPy folks, including Guido, 
consider that it *is* reasonable to ask these questions for the purpose 
of introspection, but issubclass and isinstance are not the way to do 

> One of the first things I did when playing around with the `typing` module
> was to fire up the REPL, and try runtime typechecks:
> >>> from typing import *
> >>> isinstance(0, Union[int, float])
> Traceback (most recent call last):
>   File "<stdin>", line 1, in <module>
>   File "/Users/lucaswiman/.pyenv/versions/3.6/lib/python3.6/typing.py",
> line 767, in __instancecheck__
>     raise TypeError("Unions cannot be used with isinstance().")
> TypeError: Unions cannot be used with isinstance().
> I think the natural reaction of naive users of the library is "That's
> annoying. Why? What is this library good for?", not "Ah, I've sagely
> learned a valuable lesson about the subtle-and-important-though-unmentioned
> distinction between types and classes!"


Why shouldn't 

     isinstance(x, Union[A, B])

     isinstance(x, (A, B))

be treated as equivalent?

> Mark Shannon's example also specifically does not apply to the types I'm
> thinking of for the reasons I mentioned:
> > For example,
> > List[int] and List[str] and mutually incompatible types, yet
> > isinstance([], List[int]) and isinstance([], List[str))
> > both return true.
> >
> > There is no corresponding objection for `Union`; I can't think of any*
> inconsistencies or runtime type changes that would result from defining
> `_Union.__instancecheck__` as `any(isinstance(obj, t) for t in
> self.__args__`. 

Or just isinstance(x, tuple(self.__args__)) as above.

> For `Tuple`, it's true that `()` would be an instance of
> `Tuple[X, ...]` for all types X. However, the objection for the `List` case
> (IIUC; extrapolating slightly) is that the type of the object could change
> depending on what's added to it. That's not true for tuples since they're
> immutable, so it's not *inconsistent* to say that `()` is an instance of
> `Tuple[int, ...]` and `Tuple[str, ...]`, it's just applying a sensible
> definition to the base case of an empty tuple.

I'm not even completely convinced that the List example really is a 
problem. Well, it may be a problem for applying the theory of types, 
which in turn may make actually programming a type-checker more 
difficult. But to the human reader, why is is a problem that an empty 
list can be considered both a list of strings and a list of ints? That's 
just the vacuous truth!

An empty bag can be equally well described as a bag containing no 
apples or a bag containing no oranges. They're both true, and if the 
theory of types cannot cope with that fact, that's a weakness in the 
theory, not the fact.

(That's analogous to the Circle-Ellipse problem for the the theory 
behind object oriented code.)



More information about the Python-ideas mailing list