[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.
https://github.com/python/typing/issues/136#issuecomment-217386769
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*).
https://en.wiktionary.org/wiki/instantiation
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
example:
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
whether
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
it.
> 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!"
Indeed!
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.)
https://en.wikipedia.org/wiki/Circle-ellipse_problem
--
Steve
More information about the Python-ideas
mailing list