On Thu, 2020-12-03 at 13:10 -0600, Sebastian Berg wrote:
> Hi all,
> 
> I just sniped myself wondering about how correct dispatching for
> dunders works for independently derived subclasses.

Sorry, this was likely mostly noise...

I had forgotten about the asymmetry in `__add__` and `__radd__` [1]. As
the docs clearly explain, this asymmetry resolves the issue as long as
all subclasses implement `__add__` (and do not inherit it).

When `__add__` is inherited (or `super()`) used I think the "strict"
approach I mentioned maybe still has a point (although only for
`__add__` probably, not `__radd__`).
But, inheriting a dunder unmodified is probably not worth thinking too
much about.

Cheers,

Sebastian


[1] In my defense, NumPy's protocol I was coming from is "multiple
dispatch", so it cannot distinguish forward and backward method.

> 
> This is an extreme corner case where two subclasses may not know
> about
> each other, and further cannot establish a hierarch:
> 
>     class A(int):
>         pass
> 
>     class B(int):
>         def __add__(self, other):
>             return "B"
>         def __radd__(self, other):
>             return "B"
> 
>     print(B() + A())  # prints "B"
>     print(A() + B())  # prints 0 (does not dispatch to `B`)
> 
> In the above, `A` inherits from `int` which relies on the rule
> "subclasses before superclasses" to ensure that `B.__add__` is
> normally
> called.  However, this rule cannot establish a priority between `A`
> and
> `B`, and while `A` can decide to do the same as `int`, it cannot be
> sure what to do with `B`.
> 
> The solution, or correct(?) behaviour, is likely also described
> somewhere on python I got it from NumPy [1]:
> 
>     "The recommendation is that [a dunder-implementation] of a class
>     should generally `return NotImplemented` unless the inputs are
>     instances of the same class or superclasses."
> 
> By inheriting from `int`, this is not what we do! The `int`
> implementation does not defer to `B` even though `B` is not a
> superclass of `A`.
> 
> Now, you could fix this by replacing `int` with `strict_int`:
> 
>     class strict_int():
>         def __add__(self, other):
>             if not isinstance(self, type(other)):
>                 return NotImplemented
>             return "int"
>         def __radd__(self, other):
>             if not isinstance(self, type(other)):
>                 return NotImplemented
>             return "int"
> 
> or generally the `not isinstance(self, type(other))` pattern.  In
> that
> case `B` can choose to support `A`:
> 
>     class A(strict_int):
>         pass
> 
>     class B(strict_int):
>         def __add__(self, other):
>             return "B"
>         def __radd__(self, other):
>             return "B"
> 
>     # Both print "B", as `B` "supports" `A`
>     print(B() + A())
>     print(A() + B())
> 
> The other side effect of that is that one of the classes has to do
> this
> to avoid an error:
> 
>     class A(strict_int):
>          pass
>     class B(strict_int):
>          pass
> 
>     A() + B()  # raises TypeError
> 
> 
> Now, I doubt Python could change how `int.__add__` defers since that
> would modify behaviour in a non-backward compatible way.
> But, I am curious whether there is a reason why I do not recall ever
> reading the recommendation to use the pattern:
> 
>     class A:
>         def __add__(self, other):
>             if not isinstance(self, type(other)):
>                 return NotImplemented
>             return "result"
> 
> Rather than:
> 
>     class A:
>         def __add__(self, other):
>             if not isinstance(other, A):
>                 return NotImplemented
>             return "result"
> 
> The first one leads to a strict (error unless explicitly handled)
> behaviour for multiple independently derived subclasses.  I admit,
> the
> second pattern is much simpler to understand, so that might be a
> reason
> in itself.
> But I am curious whether I am missing an important reason why the
> first
> pattern is not the recommended one (e.g. in the "Numeric abstract
> base
> classes" docs [2].
> 
> Cheers,
> 
> Sebastian
> 
> 
> [1] 
> https://numpy.org/neps/nep-0013-ufunc-overrides.html#subclass-hierarchies
> [2] 
> https://docs.python.org/3/library/numbers.html?highlight=notimplemented#implementing-the-arithmetic-operations
> _______________________________________________
> 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/NDDYQG37YRRZCUWQGST4ALUYTZA62AVI/
> Code of Conduct: http://python.org/psf/codeofconduct/

Attachment: signature.asc
Description: This is a digitally signed message part

_______________________________________________
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/H6MEP3Y6QIRNC2YSTCNIV2Y7ZY5YPFXQ/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to