>
> And what about PEP544 (protocols), which is being drafted? The PEP seems
> to aim for having type objects that represent duck-typing
> protocols/interfaces. Checking whether a protocol is implemented by an
> object or type is clearly a useful thing to do at runtime, but it is not
> really clear if isinstance would be a guaranteed feature for PEP544
> Protocols.
>
> So one question is, is it possible to draw the lines between what works
> with isinstance and what doesn't, and between what details are checked by
> isinstance and what aren't? -- Or should insinstance be reserved for a more
> limited purpose, and add another check function, say `implements(...)`,
> which would perhaps guarantee some answer for all combinations of object
> and type?
>

I'm guessing to implement PEP 544, many of the `__instancecheck__` and
`__subclasscheck__` methods in `typing.py` would need to be updated to
check the `__annotations__` of the class of the object it's passed against
its own definition, (covered in this section
<https://www.python.org/dev/peps/pep-0544/#runtime-decorator-and-narrowing-types-by-isinstance>
of the PEP).

I've been somewhat surprised that many of the `__instancecheck__`
implementations do not work at runtime, even when the implementation would
be trivial (e.g. for `Union`), or would not have subtle edge cases due to
immutability (e.g. for `Tuple`, which cannot be used for checking
parameterized instances). This seems like counterintuitive behavior that
would be straightforward to fix, unless there are subtleties & edge cases
I'm missing.

If people are amenable to updating those cases, I'd be interested in
submitting a patch to that effect.

Best,
Lucas

On Sat, Jun 24, 2017 at 12:42 PM, Koos Zevenhoven <k7ho...@gmail.com> wrote:

> There has been some discussion here and there concerning the differences
> between runtime types and static types (mypy etc.). What I write below is
> not really an idea or proposal---just a perspective, or a topic that people
> may want to discuss. Since the discussion on this is currently very fuzzy
> and scattered and not really happening either AFAICT (I've probably missed
> many discussions, though). Anyway, I thought I'd give it a shot:
>
> Clearly, there needs to be some sort of distinction between runtime
> classes/types and static types, because static types can be more precise
> than Python's dynamic runtime semantics. For example, Iterable[int] is an
> iterable that contains integers. For a static type checker, it is clear
> what this means. But at runtime, it may be impossible to figure out whether
> an iterable is really of this type without consuming the whole iterable and
> checking whether each yielded element is an integer. Even that is not
> possible if the iterable is infinite. Even Sequence[int] is problematic,
> because checking the types of all elements of the sequence could take a
> long time.
>
> Since things like isinstance(it, Iterable[int]) cannot guarantee a proper
> answer, one easily arrives at the conclusion that static types and runtime
> classes are just two separate things and that one cannot require that all
> types support something like isinstance at runtime.
>
> On the other hand, there are many runtime things that can or could be done
> using (type) annotations, for example:
>
> Multidispatch (example with hypothetical syntax below):
>
> @overload
> def concatenate(parts: Iterable[str]) -> str:
>     return "".join(parts)
>
> @overload
> def concatenate(parts: Iterable[bytes]) -> bytes:
>     return b"".join(parts)
>
> @overload
> def concatenate(parts: Iterable[Iterable]) -> Iterable:
>     return itertools.chain(*parts)
>
> or runtime type checking:
>
> @check_types
> def load_from_file(filename: Union[os.PathLike, str, bytes]):
>     with open(filename) as f:
>         return do_stuff_with(f.read())
>
> which would automatically give a nice error message if, say, a file object
> is given as argument instead of a path to a file.
>
> However useful (and efficient) these things might be, the runtime type
> checks are problematic, as discussed above.
>
> Furthermore, other differences between runtime and static typing may
> emerge (or have emerged), which will complicate the matter further. For
> instance, the runtime __annotations__ of classes, modules and functions may
> in some cases contain something completely different from what a type
> checker thinks the type should be.
>
> These and other incompatibilities between runtime and static typing will
> create two (or more) different kinds of type-annotated Python:
> runtime-oriented Python and Python with static type checking. These may be
> incompatible in both directions: a static type checker may complain about
> code that is perfectly valid for the runtime folks, and code written for
> static type checking may not be able to use new Python techniques that make
> use of type hints at runtime. There may not even be a fully functional
> subset of the two "languages". Different libraries will adhere to different
> standards and will not be compatible with each other. The split will be
> much worse and more difficult to understand than Python 2 vs 3, peoples
> around the world will suffer like never before, and programming in Python
> will become a very complicated mess.
>
>
> One way of solving the problem would be that type annotations are only a
> static concept, like with stubs or comment-based type annotations. This
> would also be nice from a memory and performance perspective, as evaluating
> and storing the annotations would not occupy memory (although both issues
> and some more might be nicely solved by making the annotations lazily
> ealuated). However, leaving out runtime effects of type annotations is not
> the approach taken, and runtime introspection of annotations seems to have
> some promising applications as well. And for many cases, the traditional
> Python class actually acts very nicely as both the runtime and static type.
>
> So if type annotations will be both for runtime and for static checking,
> how to make everything work for both static and runtime typing?
>
> Since a writer of a library does not know what the type hints will be used
> for by the library users, it is very important that there is only one way
> of making type annotations which will work regardless of what the
> annotations are used for in the end. This will also make it much easier to
> learn Python typing.
>
> Regarding runtime types and isinstance, let's look at the Iterable[int]
> example. For this case, there are a few options:
>
> 1) Don't implement isinstance
>
> This is problematic for runtime uses of annotations.
>
> 2) isinstance([1, '2', 'three'], Iterable[int]) returns True
>
> This is in fact now the case. This is ok for many runtime situations, but
> lacks precision compared to the static version. One may want to distinguish
> between Iterable[int] and Iterable[str] at runtime (e.g. the multidispatch
> example above).
>
> 3) Check as much as you can at runtime
>
> There could be something like Reiterable, which means the object is not
> consumed by iterating over it, so one could actually check if all elements
> are instances of int. This would be useful in some situations, but not
> available for every object. Furthermore, the check could take an arbitrary
> amount of time so it is not really suitable for things like multidispatch
> or some matching constructs etc., where the performance overhead of the
> type check is really important.
>
> 4) Do a deeper check than in (2) but trust the annotations
>
> For example, an instance of a class that has a method like
>
> def __iter__(self) -> Iterator[int]:
>     some code
>
> could be identified as Iterable[int] at runtime, even if it is not
> guaranteed that all elements are really integers.
>
> On the other hand, an object returned by
>
> def get_ints() -> Iterable[int]:
>     some code
>
> does not know its own annotations, so the check is difficult to do at
> runtime. And of course, there may not be annotations available.
>
> 5) Something else?
>
>
> And what about PEP544 (protocols), which is being drafted? The PEP seems
> to aim for having type objects that represent duck-typing
> protocols/interfaces. Checking whether a protocol is implemented by an
> object or type is clearly a useful thing to do at runtime, but it is not
> really clear if isinstance would be a guaranteed feature for PEP544
> Protocols.
>
> So one question is, is it possible to draw the lines between what works
> with isinstance and what doesn't, and between what details are checked by
> isinstance and what aren't? -- Or should insinstance be reserved for a more
> limited purpose, and add another check function, say `implements(...)`,
> which would perhaps guarantee some answer for all combinations of object
> and type?
>
> I'll stop here---this email is probably already much longer than a single
> email should be ;)
>
> -- Koos
>
>
> --
> + Koos Zevenhoven + http://twitter.com/k7hoven +
>
> _______________________________________________
> Python-ideas mailing list
> Python-ideas@python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/
>
>
_______________________________________________
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to