operator overloading

Lenard Lindstrom len-l at telus.net
Wed Apr 4 19:59:49 EDT 2007


looping wrote:
> Hi,
> for the fun I try operator overloading experiences and I didn't
> exactly understand how it works.
> 
> Here is my try:
>>>> class myint(int):
> 	def __pow__(self, value):
> 		return self.__add__(value)
> 
>>>> a = myint(3)
>>>> a ** 3
> 6
> 
> OK, it works. Now I try different way to achieve the same result but
> without much luck:
> 
>>>> class myint(int):
> 	pass
>>>> myint.__pow__ = myint.__add__
> 
> or:
>>>> class myint(int):
> 	__pow__ = int.__add__
> 
> or:
>>>> class myint(int):
> 	pass
>>>> a.__pow__ = a.__add__
> 
> but for every try the result was the same:
>>>> a = myint(3)
>>>> a ** 3
> 27
> 
> Why it doesn't works ?
> 

This may, or may not be a bug, depending on ones interpretation. 
int.__pow__ is a ternary operation while int.__add__ is binary. For a 
Python function, like def __pow__(self, ...), the interpreter can't 
properly check if the method takes the correct number of arguments, so 
accepts it without question. But since int.__add__ is a slot wrapper the 
interpreter can tell, and chokes. So it just falls back to using 
int.__pow__.

Now for a more detailed explanation. Each Python type, and class, has a 
vtable of C function pointers for all the operations the interpreter 
understands. A type only defines functions for those operations it 
supports. The remaining pointer are null. These functions are known as 
slot functions, a slot being a C structure field in a Python object 
instance (Types are instances too!). The int type has slot functions for 
binary + and ternary ** for instance, but none for container [].

To make the slot mechanism visible to Python, allowing operator 
overloading, generic wrapper slot functions and slot wrapper methods are 
used. When a special method, such as __add__ or __pow__, is added to a 
class, either in the class declaration or later by attribute assignment, 
the corresponding slot is set to point to a generic wrapper that will 
actually call the special method. When a builin type, like int, has a 
type specific slot function for a particular operation, a corresponding 
slot wrapper is added to the type that can call the slot function. So 
not only can one define addition for a class by having an __add__ method 
(via generic wrappers), but one can call the __add__ method on an int 
(via slot wrappers).

Now for the specific case of myint. Not all slot functions are created 
equal. The binary + slot function takes two arguments, the ternary ** 
slot function three. So int's corresponding __add__ and __pow__ slot 
wrapper methods also differ. When subclassing int all goes well:

class myint(int):
     pass

The myint class inherits int's slot functions for arithmetic, and those 
functions can be called by int's slot wrappers, such as __add__ and 
__pow__. But the following is unexpected:

myint.__pow__ = myint.__add__

Here __pow__ calls a slot function taking three arguments, __add__ calls 
one taking two. What follows is probably unplanned, no exception is 
raised. myint's ** slot function is not replaced with a generic wrapper 
that will call the __pow__ method. Instead myint keeps the slot function 
inherited from int. So myint may have a __pow__ method that adds, but it 
is never called when doing a **.


Curiously C PyPy does what was expected:

 >pypy-c.exe
Python 2.4.1 (pypy 1.0.0 build 41438) on win32
Type "help", "copyright", "credits" or "license" for more information.
 >>>> class MyInt(int):
....     pass
....
 >>>> MyInt.__pow__ = MyInt.__add__
 >>>> mi=MyInt(12)
 >>>> mi**2
14
 >>>>


Lenard Lindstrom



More information about the Python-list mailing list