This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: surprise overriding __radd__ in subclass of complex
Type: Stage:
Components: Documentation Versions: Python 2.3
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: fdrake Nosy List: aj_siegel, fdrake, gvanrossum, nnorwitz
Priority: low Keywords:

Created on 2002-03-18 14:32 by aj_siegel, last changed 2022-04-10 16:05 by admin. This issue is now closed.

Messages (9)
msg9762 - (view) Author: Arthur Siegel (aj_siegel) Date: 2002-03-18 14:32
I had presented this on tutor and python-list:

class Complex(complex):
    def __mul__(self,other):
       other=Complex(other)
       t = complex.__mul__(self,other)
       return Complex(t.real,t.imag)
    __rmul__ = __mul__

    def __add__(self,other):
       other=Complex(other)
       return Complex(self.real.__add__
(other.real),self.imag.__add__(other.imag))
    __radd__ = __add__

Then:

print type(Complex(5,4) * 7)
>><class '__main__.Complex'>
print type(7 * Complex(5,4))
>><class '__main__.Complex'>
print type(Complex(5,4) + 7)
>><class '__main__.Complex'>

But:

print type(7 + Complex(5,4))
>><type 'complex'>


Danny Yoo, after looking into it pretty deeply - 
and going to the docs and source gace me this
advice.

"In any case, this is definitely a bug in the 
documentation.  Arthur, bring
it up on comp.lang.python and Sourceforge again.  
Someone should really
look at your example, since it does seem serious... if 
not a little
obscure.  *grin*  "

msg9763 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2002-03-18 14:49
Logged In: YES 
user_id=6380

You're right, something's weird. I'll add this to my list.
msg9764 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2002-03-23 03:18
Logged In: YES 
user_id=6380

It seems that * is the exception -- all other operators (+,
-, /, **) return the type of the left operand.  It gets even
weirder if we change the left operand from 7 to 7.0 -- then
* is no longer an exception and returns a complex like all
other operators!

The weirdness is in int_mul. If the right argument is not an
int but a sequence that implements the sq_repeat slot, it
invokes that. But if you create a class that defines
__mul__, that implements the sequence repeat slot! (Python
doesn't know if you intend to implement a seqence or a
number, when you define a __mul__ method, so it maps both
the nb_multiply and the sq_repeat slots to the __mul__
method).

So 7 + Complex() takes the path through int_mul that calls
the Complex.__mul__ , and that's why you get a Complex value
back. Floats can't be used in the sequence repeat operator,
so its multiply is "normal".

But wait...! There's another mystery. Ints don't know how to
add themselves to complex numbers! So why wouldn't
7+Complex() use the normal pattern of (a) try the left
operand, (b) if that returns NotImplemented, try the right
operand? The answer is that complex numbers implement
coercion! And in that case, step (b) is modified to (b) ask
either operand to perform a coercion, and if it succeeds,
ask the left operand of the resulting coerced pair to
perform the operation. So complex coerces the int to a plain
complex, and then invokes the plain complex's add/mul/etc.
operation.

Solution: add the following to your Complex class:

    def __coerce__(self, other):
        x = complex.__coerce__(self, other)
        if x is NotImplemented:
            return x
        a, b = x
        return Complex(a), Complex(b)

I don't think I want to do anything to fix this, although I
think I'll pass it on to Fred for documentation (not that I
expect him to be in a hurry to fix it either, given the
subtlety of the issues).

In the long run, I think complex should stop using coercion
and start behaving like a "new-style" number like all the
other numeric types. That should fix the second mystery.

Also, in the long run, the sq_repeat and sq_concat slots
should be deprecated and instead sequences should implement
multiply and add operations. Then the funny business in
int_mul could be taken out and the first mystery would
disappear.
msg9765 - (view) Author: Arthur Siegel (aj_siegel) Date: 2002-03-25 00:24
Logged In: YES 
user_id=248775

Guido - 

Thanks for your attention to my report.

Another bit of suprise with operator overriding
in a subclass of complex:
  
from __future__ import division
class Complex(complex):
    def __div__(self,other):
        t=complex.__div__(self,other)       
        return Complex(t.real,t.imag)
a=Complex(5,4)
b=Complex(1,7)

>> <type 'complex'>

But:

everything the same *without* calling
from __future__ import division  

print type(a/b)
>><class '__main__.Complex'>

Based your analysis of the previous
issue(which I follow only
in broad outline), I have a sense of 
what's happening.   

My general sense, though, is that this issue
is in some sense more of a problem than
the other.

Don't think anyone will expect a change in
behavior here based on the from __future__
call.  Not that subbing complex is a common pursuit -
but couldn't this actually break working code
in a very unanticpated way, eventually.

Obviously not a big issue if the other changes
you see happening with complex coercian kick in
first, and in fact those changes - as I suspect - 
would prevent a different return type based
on which way int/int is toggled.

Art  
msg9766 - (view) Author: Arthur Siegel (aj_siegel) Date: 2002-03-25 01:11
Logged In: YES 
user_id=248775

Have just re-read my submission - and
in addition to my usual quota of typos
I see I left out something that could 
confuse what I am trying to bring to 
your attention.

The gist, I am sure you will see, is that
type(a/b), where a and b are of a class derived
from complex, will change based on whether
from __future__ import division is or is not
called.

And I am assuming that behavior is unanticipated.

And I assuming there is no reason to submit this a
a bug report separate from this thread.

Art
msg9767 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2002-03-25 15:13
Logged In: YES 
user_id=6380

If you want to override division in a complex subclass, you
should define both __truediv__ and __div__, as well as
__rtruediv__ and __rdiv__.  You can do this by saying
__truediv__ = __div__, of course.
msg9768 - (view) Author: Arthur Siegel (aj_siegel) Date: 2002-03-25 15:20
Logged In: YES 
user_id=248775

Knew I'd embarass myself soon enough in this
company.

Found the __truediv__ answer on the train into
work.  Came here hoping to catch it, before you caught
me.

Too late.

Thanks again - oops for the false alarm.

Art 
msg9769 - (view) Author: Neal Norwitz (nnorwitz) * (Python committer) Date: 2002-09-06 22:52
Logged In: YES 
user_id=33168

Guido, Fred, is there anything to be done or should this be
closed?
msg9770 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2002-09-06 23:35
Logged In: YES 
user_id=6380

This is fixed in 2.3. Can't remember if it was fixed in 2.2
branch, don't care.
History
Date User Action Args
2022-04-10 16:05:06adminsetgithub: 36275
2002-03-18 14:32:27aj_siegelcreate