Addition and multiplication puzzle

M

Mark Dickinson

Can anyone either reproduce or explain the following
apparently inconsistent behaviours of __add__ and
__mul__? The class Gaussian provides a sub-bare-bones
implementation of Gaussian integers (a Gaussian
integer is a complex number x+yi for which both x and
y are
integers):

class Gaussian(object):
"""class representing Gaussian integers"""

def __init__(self, x, y = 0):
self.real, self.imag = x, y

def __repr__(self):
return repr(self.real) + "+" + repr(self.imag)
+ "*i"

def __add__(self, other):
if type(other) != Gaussian:
other = Gaussian(other)
return Gaussian(self.real + other.real,
self.imag + other.imag)

def __mul__(self, other):
if type(other) != Gaussian:
other = Gaussian(other)
return Gaussian(self.real * other.real -
self.imag * other.imag, \
self.real * other.imag +
self.imag * other.real)

Under Python 2.3.2 I get:
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: unsupported operand type(s) for +: 'int'
and 'Gaussian'

In other words, I can *multiply* an int by a Gaussian
in either order, but I can only *add* a Gaussian to an
int, not the other way around. The surprise is that
multiplying an int by a Gaussian works---I'd expect it
to complain since there's no __rmul__ method defined,
in just the same way that 3+i produced an exception
above. Why do addition and multiplication behave
differently?

Yours hoping for enlightenment,

Mark


__________________________________
Do you Yahoo!?
The New Yahoo! Shopping - with improved product search
http://shopping.yahoo.com
 
E

Emile van Sebille

Mark Dickinson said:
Can anyone either reproduce or explain the following
apparently inconsistent behaviours of __add__ and
__mul__? The class Gaussian provides a sub-bare-bones
implementation of Gaussian integers (a Gaussian
integer is a complex number x+yi for which both x and
y are
integers):

class Gaussian(object):
"""class representing Gaussian integers"""

def __init__(self, x, y = 0):
self.real, self.imag = x, y

def __repr__(self):
return repr(self.real) + "+" + repr(self.imag)
+ "*i"

def __add__(self, other):
if type(other) != Gaussian:
other = Gaussian(other)
return Gaussian(self.real + other.real,
self.imag + other.imag)

def __mul__(self, other):
if type(other) != Gaussian:
other = Gaussian(other)
return Gaussian(self.real * other.real -
self.imag * other.imag, \
self.real * other.imag +
self.imag * other.real)

Under Python 2.3.2 I get:

Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: unsupported operand type(s) for +: 'int'
and 'Gaussian'

In other words, I can *multiply* an int by a Gaussian
in either order, but I can only *add* a Gaussian to an
int, not the other way around. The surprise is that
multiplying an int by a Gaussian works---I'd expect it
to complain since there's no __rmul__ method defined,
in just the same way that 3+i produced an exception
above. Why do addition and multiplication behave
differently?

I think new style classes create this, as old style classes exhibit the same
behaviour for multiplication and division:

class Test:
def __init__(self, val):
self.val = val
def __add__(self, other):
return self.val + other
def __mul__(self, other):
return self.val * other

In typeobject.c, I find:

SQSLOT("__add__", sq_concat, slot_sq_concat, wrap_binaryfunc,
"x.__add__(y) <==> x+y"),
SQSLOT("__mul__", sq_repeat, slot_sq_repeat, wrap_intargfunc,
"x.__mul__(n) <==> x*n"),
SQSLOT("__rmul__", sq_repeat, slot_sq_repeat, wrap_intargfunc,
"x.__rmul__(n) <==> n*x"),

My guess is that this explains why multiplication works and addition
doesn't, as there is no corresponding SQSLOT("__radd__"... but adding

SQSLOT("__radd__", sq_concat, slot_sq_concat, wrap_binaryfunc,
"x.__radd__(y) <==> y+x"),

and recompiling didn't fix the problem, so I'm either on the wrong track or
missed a connection along the way.

Emile van Sebille
(e-mail address removed)
 
B

Bruce Wolk

Mark said:
Can anyone either reproduce or explain the following
apparently inconsistent behaviours of __add__ and
__mul__? The class Gaussian provides a sub-bare-bones
implementation of Gaussian integers (a Gaussian
integer is a complex number x+yi for which both x and
y are
integers):

class Gaussian(object):
"""class representing Gaussian integers"""

def __init__(self, x, y = 0):
self.real, self.imag = x, y

def __repr__(self):
return repr(self.real) + "+" + repr(self.imag)
+ "*i"

def __add__(self, other):
if type(other) != Gaussian:
other = Gaussian(other)
return Gaussian(self.real + other.real,
self.imag + other.imag)

def __mul__(self, other):
if type(other) != Gaussian:
other = Gaussian(other)
return Gaussian(self.real * other.real -
self.imag * other.imag, \
self.real * other.imag +
self.imag * other.real)

Under Python 2.3.2 I get:



Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: unsupported operand type(s) for +: 'int'
and 'Gaussian'

In other words, I can *multiply* an int by a Gaussian
in either order, but I can only *add* a Gaussian to an
int, not the other way around. The surprise is that
multiplying an int by a Gaussian works---I'd expect it
to complain since there's no __rmul__ method defined,
in just the same way that 3+i produced an exception
above. Why do addition and multiplication behave
differently?

Yours hoping for enlightenment,

Mark


__________________________________
Do you Yahoo!?
The New Yahoo! Shopping - with improved product search
http://shopping.yahoo.com
3*i gives the expected error under Python 2.2.2. Curious indeed.
 
J

John Roth

Mark Dickinson said:
Can anyone either reproduce or explain the following
apparently inconsistent behaviours of __add__ and
__mul__? The class Gaussian provides a sub-bare-bones
implementation of Gaussian integers (a Gaussian
integer is a complex number x+yi for which both x and
y are
integers):

class Gaussian(object):
"""class representing Gaussian integers"""

def __init__(self, x, y = 0):
self.real, self.imag = x, y

def __repr__(self):
return repr(self.real) + "+" + repr(self.imag)
+ "*i"

def __add__(self, other):
if type(other) != Gaussian:
other = Gaussian(other)
return Gaussian(self.real + other.real,
self.imag + other.imag)

def __mul__(self, other):
if type(other) != Gaussian:
other = Gaussian(other)
return Gaussian(self.real * other.real -
self.imag * other.imag, \
self.real * other.imag +
self.imag * other.real)

Under Python 2.3.2 I get:

Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: unsupported operand type(s) for +: 'int'
and 'Gaussian'

In other words, I can *multiply* an int by a Gaussian
in either order, but I can only *add* a Gaussian to an
int, not the other way around. The surprise is that
multiplying an int by a Gaussian works---I'd expect it
to complain since there's no __rmul__ method defined,
in just the same way that 3+i produced an exception
above. Why do addition and multiplication behave
differently?

Yours hoping for enlightenment,

Mark

I vaguely remember a discussion a while back about
getting rid of the 'r' operators. I don't remember what
ever came of it, though, and I agree that the inconsistent
treatment of __mul__ and __add__ is strange. I suspect
something may have gotten lost in the boolean implementation.

The Python Reference Manual doesn't say anything
about it, though.

John Roth
 
A

Alex Martelli

John Roth wrote:
...
I vaguely remember a discussion a while back about
getting rid of the 'r' operators. I don't remember what
ever came of it, though, and I agree that the inconsistent
treatment of __mul__ and __add__ is strange. I suspect
something may have gotten lost in the boolean implementation.

It's not that -- it's a different bug (good catch guys!).

Objects/typeobject.c takes BOTH __mul__ AND __rmul__ as implying
a "sq_repeat" (sequence repeat) slot, and then function
Objects/abstract.c checks both operand for sq_repeat slots --
thus, it will (erroneously) tread a __mul__ AS IF it was an
__rmul__... BUT only when multiplying by an integer, because
that's what sequence-repeat involves.

Try 3.0 * i rather than 3 * i and you'll see it correctly
raise a TypeError (although the msg is a good hint that the
bug is still at work: "can't multiply sequence to non-int"
rather than "unsupported operand type(s)").


Unfortunately the fix (for Python 2.3.3) isn't as obvious to
me as the diagnosis. Anyway, I've reported this as a bug on
SF, its "Request ID" is 830261, and I hope I'll get advice
from somebody to whom the fix _is_ obvious:).


Again, thanks for the bug report!


Alex
 
A

Alex Martelli

Emile van Sebille wrote:
...
In typeobject.c, I find:

SQSLOT("__add__", sq_concat, slot_sq_concat, wrap_binaryfunc,
"x.__add__(y) <==> x+y"),
SQSLOT("__mul__", sq_repeat, slot_sq_repeat, wrap_intargfunc,
"x.__mul__(n) <==> x*n"),
SQSLOT("__rmul__", sq_repeat, slot_sq_repeat, wrap_intargfunc,
"x.__rmul__(n) <==> n*x"),

My guess is that this explains why multiplication works and addition
doesn't, as there is no corresponding SQSLOT("__radd__"... but adding

Right (though incomplete).
SQSLOT("__radd__", sq_concat, slot_sq_concat, wrap_binaryfunc,
"x.__radd__(y) <==> y+x"),

and recompiling didn't fix the problem, so I'm either on the wrong track
or missed a connection along the way.

Yep: the fact that PyNumber_Add only tries the concatenate-sequences
route if the LEFT operand has a sq_concat slot -- it needs not try
BOTH operands for the purpose, as PyNumber_Multiply, alas, must
(because seq*int and int*seq are both valid). Both functions are in
Objects/abstract.c , btw.

Unfortunately, even with this complete diagnosis, the fix is NOT
obvious to me. Anyway, I've opened bug #830261 to see if I can
get advice from somebody to which the fix IS obvious (btw, the fix
involves NOT having 3*i succeed and 3.0*i fail with a weird msg --
it's not acceptable to "fix" by making 3+i succeed and 3.0+i fail
weirdly...:).


Alex
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,764
Messages
2,569,566
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top