PEP 208 and __coerce__

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. 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. The question is: how should __coerce__ work? One option is to leave it work the way it does in 2.0. Alternatively, I could change it so that if coerce returns (self, *) then __coerce__ is not called again. Neil

Neil Schemenauer wrote:
+0 -- the idea behind the PEP 208 is to get rid off the centralized coercion mechanism, so fixing it to allow yet more obscure variants should be carefully considered. I see __coerce__ et al. as old style mechanisms -- operator methods have much more information available to do the right thing than the single bottelneck __coerce__. -- Marc-Andre Lemburg ______________________________________________________________________ Company: http://www.egenix.com/ Consulting: http://www.lemburg.com/ Python Pages: http://www.lemburg.com/python/

[Neil Schemenauer Saturday, December 09, 2000 6:30 AM]
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!).
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>.

Neil Schemenauer wrote:
+0 -- the idea behind the PEP 208 is to get rid off the centralized coercion mechanism, so fixing it to allow yet more obscure variants should be carefully considered. I see __coerce__ et al. as old style mechanisms -- operator methods have much more information available to do the right thing than the single bottelneck __coerce__. -- Marc-Andre Lemburg ______________________________________________________________________ Company: http://www.egenix.com/ Consulting: http://www.lemburg.com/ Python Pages: http://www.lemburg.com/python/

[Neil Schemenauer Saturday, December 09, 2000 6:30 AM]
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!).
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>.
participants (3)
-
M.-A. Lemburg
-
Neil Schemenauer
-
Tim Peters