Discussion in 'Python' started by Mark Dickinson, Oct 25, 2003.

1. ### Mark DickinsonGuest

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"

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:

>>> i = Gaussian(0, 1)
>>> i * 3

0+3*i
>>> 3 * i # surprise!

0+3*i
>>> i + 3

3+1*i
>>> 3 + i

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

Mark Dickinson, Oct 25, 2003

2. ### Emile van SebilleGuest

"Mark Dickinson" <> wrote in message
news:...
> 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"
>
> 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:
>
> >>> i = Gaussian(0, 1)
> >>> i * 3

> 0+3*i
> >>> 3 * i # surprise!

> 0+3*i
> >>> i + 3

> 3+1*i
> >>> 3 + i

> 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
return self.val + other
def __mul__(self, other):
return self.val * other

In typeobject.c, I find:

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

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

Emile van Sebille, Oct 25, 2003

3. ### Bruce WolkGuest

Mark Dickinson wrote:
> 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"
>
> 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:
>
>
>>>>i = Gaussian(0, 1)
>>>>i * 3

>
> 0+3*i
>
>>>>3 * i # surprise!

>
> 0+3*i
>
>>>>i + 3

>
> 3+1*i
>
>>>>3 + i

>
> 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.

Bruce Wolk, Oct 25, 2003
4. ### John RothGuest

"Mark Dickinson" <> wrote in message
news:...
> 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"
>
> 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:
>
> >>> i = Gaussian(0, 1)
> >>> i * 3

> 0+3*i
> >>> 3 * i # surprise!

> 0+3*i
> >>> i + 3

> 3+1*i
> >>> 3 + i

> 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

John Roth

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

John Roth, Oct 25, 2003
5. ### Alex MartelliGuest

John Roth wrote:
...
> "Mark Dickinson" <> wrote in message

...
> 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

Alex Martelli, Oct 25, 2003
6. ### Alex MartelliGuest

Emile van Sebille wrote:
...
> In typeobject.c, I find:
>
> 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

Right (though incomplete).

>
> 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

Alex Martelli, Oct 25, 2003