Am 24.04.2021 um 01:26 schrieb Gregory P. Smith:
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'm using isinstance from time to time, mostly to satisfy the type checker in some cases. I think having a "hasshape()" builtin would be a huge win. In addition it would be useful for type checkers to better support hasattr() for distinguishing between separate types. For example, mypy has a problem with the following:

    class A: pass
    class B:
        x: int

    def foo(x: A | B) -> None:
        if hasattr(x, "b"):
            accepts_b(x)
        else:
            accepts_a(x)

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
I think a PEP to discuss these questions would be appropriate. Things like also checking types could also be optional.
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.

+1

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.

My background is with TypeScript, not go. But TypeScript uses "interfaces" extensively to describe the shape of expected objects. I agree mostly with you and Luciano here, but it can make sense to define some protocols in a more visible location. Examples are the protocols in collections.abc. In typeshed we collected a few more common protocols in the type-check only module _typeshed. (https://github.com/python/typeshed/tree/master/stdlib/_typeshed) These are a bit experimental, but could eventually find their way into the standard library.

A bit off-topic: But intersection types (as discussed here: https://github.com/python/typing/issues/213) could also make a nice addition to quickly compose ad-hoc protocols from pre-made protocols:

    def read_stuff(f: HasRead & HasSeek) -> None: ...

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.

Jukka Lehtosalo proposed a new "type" keyword over at typing-sig: https://mail.python.org/archives/list/typing-sig@python.org/thread/LV22PX454W4VDTXY6NDJV7NZD4LFK464/ This could also be used to define protocols a bit more succintly, and with additional syntax constraints, making protocols feel a bit more "first class" than they feel now.

 - Sebastian