When reading this, I wrote most of it early and left a draft to bake....
Then deleted a ton of it after other people replied. I'm conscious that my
terminology might be all over the map.  Keep that in mind before hitting
reply.  It'll take me a while to digest and pedantically use Luciano's
terms, they appear to be a great start. :)

On Tue, Apr 20, 2021 at 10:09 AM Mark Shannon <m...@hotpy.org> wrote:

> Hi everyone,
>
> Once upon a time Python was a purely duck typed language.
>
> Then came along abstract based classes, and some nominal typing starting
> to creep into the language.
>
> If you guarded your code with `isinstance(foo, Sequence)` then I could
> not use it with my `Foo` even if my `Foo` quacked like a sequence. I was
> forced to use nominal typing; inheriting from Sequence, or explicitly
> registering as a Sequence.
>

True.  Though in practice I haven't run into this often *myself*.  Do you
have practical examples of where this has bitten users such that code they
would've written pre-abc is no longer possible?  This audience can come up
with plenty of theoretical examples, those aren't so interesting to me.
I'm more interested in observances of actual real world fallout due to
something "important" (as defined however each user wants) using isinstance
checks when it ideally wouldn't.

Practically speaking, one issue I have is how easy it is to write
isinstance or issubclass checks. It has historically been much more
difficult to write and maintain a check that something looks like a duck.

 `if hasattr(foo, 'close') and hasattr(foo, 'seek') and hasattr(foo,
'read'):`

Just does not roll off the figurative tongue and that is a relatively
simple example of what is required for a duck check.

To prevent isinstance use when a duck check would be better, we're missing
an easy builtin elevated to the isinstance() availability level behaving
as lookslikeaduck() that does matches against a (set of) declared
typing.Protocol shape(s). An implementation of this exists -
https://www.python.org/dev/peps/pep-0544/#runtime-checkable-decorator-and-narrowing-types-by-isinstance
- but it requires the protocols to declare runtime checkability and has
them work with isinstance similar to ABCs...  technically accurate *BUT via
isinstance*? Doh!  It promotes the use of isinstance when it really isn't
about class hierarchy at all...

Edit: Maybe that's okay, isinstance can be read leniently to mean "is an
instance of something that one of these things over here says it matches"
rather than meaning "a parent class type is..."?  From a past experience
user perspective I don't read "isinstance" as "looks like a duck" when I
read code.  I assume I'm not alone.

I'd prefer something not involving metaclasses and __instancecheck__ type
class methods.  Something direct so that the author and reader both
explicitly see that they're seeing a duck check rather than a type
hierarchy check.  I don't think this ship has sailed, it could be built up
on top of what already exists if we want it.  Was this already covered in
earlier 544 discussions perhaps?

As Nathaniel indicated, how deep do we want to go down this rabbit hole of
checking?  just names?  signatures and types on those?  What about
exceptions (something our type system has no way to declare at all)?  and
infinite side effects?  At the end of the day we're required to trust the
result of whatever check we use and any implementation may not conform to
our desires no matter how much checking we do. Unless we solve the halting
problem. :P

PEP 544 supports structural typing, but to declare a structural type you
> must inherit from Protocol.
> That smells a lot like nominal typing to me.
>

Not quite.  A Protocol is merely a way to describe a structural type.  You
do not *need* to have your *implementations* of anything inherit from
typing.Protocol.  I'd *personally* advise people *do not inherit* from
Protocol in their implementation. Leave that for a structural type
declaration for type description and annotation purposes only, even though
Protocol appears to support direct inheritance. I understand why some don't
like this separate shape declaration concept.

Luciano notes that it is preferred to define your protocols as narrow and
define them in places *where they're used*, to follow a golang interface
practice.  My thinking aligns with that.

That inheritance is used in the *declaration* of the protocol is an
implementation detail because our language has never had a syntax for
declaring an interface.  544 fit within our existing language syntax.

Then came PEP 563 and said that if you wanted to access the annotations
> of an object, you needed to call typing.get_type_hints() to get
> annotations in a meaningful form.
> This smells a bit like enforced static typing to me.
>

I think useful conversations are ongoing here.  Enforced is the wrong
word.  *[rest of comment deleted in light of Larry's work in progress
response]*

Nominal typing in a dynamically typed language makes little sense. It
> gains little or no safety, but restricts the programs you can write.
>

"makes little sense" is going further than I'd go, but overall agreed.
It's a reason why we discourage people from using isinstance.  When part of
my job was reviewing a lot of new to the language folks code,
the isinstance antipattern was a common thing to see from those who weren't
yet comfortable with dynamic concepts.

An extreme example of that is this:
>
> # Some magic code to mess with collections.abc.Sequence
>  >>> match {}:
> ...     case []:
> ...        print("WTF!")
> ...
> WTF!
>
> With duck typing this would be impossible (unless you use ctypes to mess
> with the dict object).
>

user: Doctor, it hurts when i do this.
doctor: So... don't do that.

Anyone who has code that messes with the definitions of things in
collections.abc (or really any stdlib module) deserves all of the pain they
cause their entire program.  That is the same sentiment we should all have
for anyone using ctypes to mess with the underlying VM.  Some footguns
can't be meaningfully prevented.

Python has always been a language that both supported duck typing and
nominal typing "I said I'm a duck, trust me" at the same time. When the
duck like thing or the thing that named itself a duck (goose?) turns out
not to quack like a duck (honk) it's always been a problem.  I don't see
anything new here.  Our language is built on trust.  (if that's interpreted
as "hope is our strategy"... that is a reason static analyzers like pytype
and mypy exist, to reduce the reliance on hope/trust)

If you don't want Nominal typing, a logical conclusion could be "just get
rid of classes".  Or at least "get rid of inheritance".  Obviously
impossible while still identifying as Python.

So, lets stick to our promise that type hints will always be optional,
> and restore duck typing.
>

You word this as if it were a call to arms, but duck typing hasn't gone
anywhere, and there are no concrete actions to take or specific problems
cited. Just a vague feeling that some more recent designs are drifting away
from letting things quack.  Fair enough as a sentiment, but not something I
see as specific.  From my perspective nobody wants to throw the duckling
out with the bath water and I don't see anybody *intentionally* trying to.

I'm not suggesting that we get rid type hints and abstract base classes.
> They are popular for a reason.
> But let's treat them as useful tools, not warp the rest of the language
> to fit them.
>

Ultimately I agree with this sentiment.  Maybe because I read it
differently than you wrote it.  It's one of the reasons PEP-484 was as
limited as it was.  It did what could fit within the existing language.
Knowing that this wasn't enough to satisfy all needs.  Where you see
language warping, others see natural evolution to support previously
ignored concepts.  PEP 544 doesn't warp anything.  PEP 563 raised good
questions that are being worked on and actively discussed in this and other
threads.

The thing that concerns me most are not static analysis tools.   Those have
for the most part all been communicating and coordinating (typing-sig
FTW).  Runtime tools consuming annotations concern me more.  When those are
used, they become non-optional in that they cannot be ignored *by the end
user*.  We really all need to play in the same park here with regards to
annotation meaning, so if anyone has runtime uses of annotations they
really need to participate in typing-sig.  It looks like that has started
to happen and our deciders now know more people to ensure are looped in if
not.  Great!  =)

I care more about not painting ourselves into a corner blocking future
improvements than about being perfect on the first try.

*[enough editing and re-editing, if there are non-sequitur edits left
above, sorry!]*

-gps
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/NA6XXYAJUR65DCRU6AYY2HS3QYHNDB62/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to