[Python-Dev] PEP 208 and __coerce__

Tim Peters tim.one@home.com
Sat, 23 Dec 2000 23:04:47 -0500


[Neil Schemenauer
 Saturday, December 09, 2000 6:30 AM]

> While working on the implementation of PEP 208, I discovered that
> __coerce__ has some surprising properties.  Initially I
> implemented __coerce__ so that the numberic operation currently
> being performed was called on the values returned by __coerce__.
> This caused test_class to blow up due to code like this:
>
>     class Test:
>         def __coerce__(self, other):
>             return (self, other)
>
> The 2.0 "solves" this by not calling __coerce__ again if the
> objects returned by __coerce__ are instances.

If C.__coerce__ doesn't *know* it can do the full job, it should return
None.   This is what's documented, too:  a coerce method should return a
pair consisting of objects of the same type, or return None.

It's always going to be somewhat clumsy since what you really want is double
(or, in the case of pow, sometimes triple) dispatch.

Now there's a deliberate cheat that may not have gotten documented
comprehensibly:  when __coerce__ returns a pair, Python does not check to
verify both elements are of the same class.  That's because "a pair
consisting of objects of the same type" is often not what you *want* from
coerce.  For example, if I've got a matrix class M, then in

    M() + 42

I really don't want M.__coerce__ "promoting" 42 to a multi-gigabyte matrix
matching the shape and size of M().  M.__add__ can deal with that much more
efficiently if it gets 42 directly.  OTOH, M.__coerce__ may want to coerce
types other than scalar numbers to conform to the shape and size of self, or
fiddle self to conform to some other type.

What Python accepts back from __coerce__ has to be flexible enough to allow
all of those without further interference from the interpreter (just ask MAL
<wink>:  the *real* problem in practice is making coerce more of a help than
a burden to the end user; outside of int->long->float->complex (which is
itself partly broken, because long->float can lose precision or even fail
outright), "coercion to a common type" is almost never quite right; note
that C99 introduces distinct imaginary and complex types, because even
auto-conversion of imaginary->complex can be a royal PITA!).

> This has the effect of making code like:
>
>     class A:
>         def __coerce__(self, other):
>             return B(), other
>
>     class B:
>         def __coerce__(self, other):
>             return 1, other
>
>     A() + 1
>
> fail to work in the expected way.

I have no idea how you expected that to work.  Neither coerce() method looks
reasonable:  they don't follow the rules for coerce methods.  If A thinks it
needs to create a B() and have coercion "start over from scratch" with that,
then it should do so explicitly:

    class A:
        def __coerce__(self, other):
            return coerce(B(), other)

> The question is: how should __coerce__ work?

This can't be answered by a one-liner:  the intended behavior is documented
by a complex set of rules at the bottom of Lang Ref 3.3.6 ("Emulating
numeric types").  Alternatives should be written up as a diff against those
rules, which Guido worked hard on in years past -- more than once, too
<wink>.