On Tue, Oct 1, 2019 at 9:26 AM Andrew Barnert <abarnert@yahoo.com> wrote:
On Sep 30, 2019, at 23:46, Ben Rudiak-Gould <benrudiak@gmail.com> wrote:
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.
If you take the perspective that the ABC notion of subtyping is the correct one, then list isn't a proper subtype of object. I wasn't taking the perspective that either one is the correct one. I think they're probably both okay in isolation. I can understand the ordinary subclass relation in Python as the partial order generated by the inheritance dag, which is a perfectly good notion of subtyping. (Yes a subclass instance might not *actually* work where a superclass instance is expected, but that's true in any OOP language. A 1234 may not work where an int instance is expected either. Perfect substitutability is not achievable, but at least there's a partial ordering of classes.) I think I can understand the ABC subclass test as a subset relation, which is also a perfectly good notion of subtyping. The problem is that when you combine them, you have neither of those things. I'm not even certain how they're being combined (is it just the union of the graphs of the two relations?) but the result has none of the properties that you'd expect a subtype relation to have, and that the two subtype relations do have in isolation.
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.
My problem is not that I can't justify the current behavior, it's that if it behaved differently, I could justify that behavior too. I feel like you're using CPython as an oracle of what ABCs should do, and that if issubclass had returned False in this example, you would have been ready with an explanation for that too - namely that I broke the subtyping relation by deleting __hash__, the same explanation you used earlier in the case where it did return False. What *should* it mean to inherit from an ABC? The module encourages you to use them as mixins, but maybe that isn't the intended meaning, just a side hack? Is the primary meaning to do the equivalent of registering the class with the predicate? I was worried that someone would complain about my A not making sense, and thought about using a more complicated example: class A(Hashable): def __hash__(self): return 4 class B(A): __hash__ = None issubclass(B, Hashable) # True So empirically, inheriting from Hashable registers not only that class but all subclasses of it that may later be defined with the predicate. Is that the intended behavior, or is it an accidental side effect of combining two different notions of subclassing in a single test? You could end up with a situation where you'd have to choose between using an ABC as a mixin and living with potentially incorrect ABC predicate tests in subclasses, or implementing the methods yourself (in the same way the mixin would have) to get the correct predicate behavior. Hashable isn't useful as a mixin and I think none of the other ABCs test deletable properties, but that doesn't seem to be a design principle, just a coincidence.