On Sep 30, 2019, at 23:46, Ben Rudiak-Gould <benrud...@gmail.com> wrote:
> 
> On Mon, Sep 30, 2019 at 10:08 AM Andrew Barnert via Python-ideas
> <python-ideas@python.org> wrote:
>> Also, what we’re checking for really is subtyping.
> 
> Is it? Subtyping in type theory satisfies some axioms, one of which is
> transitivity. The addition of the ABCs broke transitivity:

Python subtyping isn’t perfect. The very fact that you’re allowed to disable 
inherited methods by assigning them to None breaks this; you don’t need ABCs 
for that:

    def spam(x: object):
        hash(x)

    spam([])

Even though [] is-a object, and objects have the __hash__ method, this raises a 
TypeError because lists don’t have the __hash__ method. That violates 
substitutability. [] is-a object is wrong. The Hashable ABC correctly reflects 
that incorrect relationship:

>>>> issubclass(list, object)
>    True
>>>> issubclass(object, collections.abc.Hashable)
>    True
>>>> issubclass(list, collections.abc.Hashable)
>    False
> 
> ABC membership is a subtype relationship in some sense, and ordinary
> Python subclassing is a subtype relationship in some sense, but they
> aren't quite the same sense,

But in this case, they actually match. Hashable is correctly checking for 
structural subtyping, and the problem is that list isn’t actually a proper 
subtype of object, not that object isn’t a proper subtype of Hashable.

> and merging them creates an odd hybrid
> system in which I'm no longer sure which subclass relationships should
> hold, let alone which do.

Let’s say instead of ABCs that test structural subtyping, we added a bunch of 
callable-like predicates to do the tests. You would have the exact same problem 
here:

    >>> issubclass(list, object)
    True
    >>> collections.ishashable(object)
    True
    >>> collections.ishashable(list):
    False

The problem is with list is-a object, not with the way you test.

> For example:
> 
>>>> class A(collections.abc.Hashable):
>    ...     __hash__ = None
>    ...
>>>> issubclass(A, collections.abc.Hashable)
>    True

This one is a direct consequence of the fact that you can lie to ABCs—if you 
inherit from an ABC you are treated as a subtype even if you don’t qualify, and 
the check isn’t perfect. You are explicitly, and obviously, lying to the system 
here.

Should ABCs check whether the required methods are actually methods (with a 
Method ABC, or by calling _get__ and checking callable on the result, or 
whatever)? Or at least not None? I don’t know. Maybe. But that wouldn’t 
eliminate the ability to lie to them, because they have the register method. 
Even if you couldn’t inherit from an ABC to lie to it, you could still register 
with it, and there is no check at all there, and that’s definitely working as 
designed.

And allowing you to lie isn’t really a bug; it’s a consenting-adults feature 
that can be misused but can also be useful for migrating legacy code. Of course 
register isn’t used only, or even primarily, for lying—Sequence can’t be tested 
structurally (at least not if you want to distinguish Sequence indexing from 
Mapping lookup when both protocols use the same dunder method), so list and 
tuple register with Sequence. But I believe range also used to register with 
Sequence to lie even before it became a proper sequence in 3.2, because it was 
“close enough” to being a Sequence and often used in real-life code that used 
sequences and worked. Registration can also be used for “file-like object” code 
that was written to the vague 2.x definition and worked fine in practice, but 
didn’t actually meet the ABCs in the io module which are no longer vague about 
what it means (because they require methods your legacy code never used). And 
so on.

If ABCs had been in Python since 2.2, I’m not sure this feature would be a good 
idea, but adding it after the fact, I think it was.

>>>> hash(A())
>    Traceback (most recent call last):
>      File "<stdin>", line 1, in <module>
>    TypeError: unhashable type: 'A'
> 
> I didn't know what the issubclass call would return before I keyed in
> the example, and I can't decide what it should return. In contrast, I
> have no trouble deciding that the equivalent test implemented as a
> predicate ought to return False, since instances of A are in fact not
> hashable.

What about this:

    class A:
        __hash__ = None
    ishashable.register(A)
    ishashable(A)

Would you be surprised if this returned true?

If that registration were never useful, it would be a bizarre feature, a 
pointless bug magnet. But if it were useful for lots of legacy code and added 
for that reason, and you deliberately misused the feature like this, would you 
say the bug is in ishashable, in the predicate system, or in your code?

Also, how would you write the issequence and is ismapping predicates?
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/M7J6M7EPPHUDAHYTJDQDNTUDB5VZNEQ2/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to