Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com
Answertopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions
Privacy Policy

  




 

 

Numeric Type Special Methods

When creating a new numeric data type, you must provide definitions for the essential mathematical and logical operators. When we write an expression using the usual +, -, *, and /, Python transforms this to method function calls. Consider the following:

v1= MyClass(10,20)
v2= MyClass(20,40)
x = v1 + v2

In this case, Python will evaluate line 3 as if you had written:

x = v1.__add__( v2 )

Every arithmetic operator is transformed into a method function call. By defining the numeric special methods, your class willwork with the built-in arithmetic operators. There are, however, some subtleties to this.

Forward, Reverse and In-Place Method Functions. First, there are as many as three variant methods required to implement each operation. For example, * is implemented by __mul__, __rmul__ and __imul__. There are forward and reverse special methods so that you can assure that your operator is properly commutative. There is an in-place special method so that you can implement augmented assignment efficiently (see the section called “Augmented Assignment”).

You don't need to implement all three versions. If you implement just the forward version, and your program does nothing too odd or unusual, everything will work out well. The reverse name is used for special situations that involve objects of multiple classes.

Python makes two attempts to locate an appropriate method function for an operator. First, it tries a class based on the left-hand operand using the "forward" name. If no suitable special method is found, it tries the the right-hand operand, using the "reverse" name.

Consider the following:

v1= MyClass(10,20)
x = v1 * 14
y = 28 * v1

Both lines 2 and 3 require conversions between the built-in integer type and MyClass. For line 2, the forward name is used. The expression v1*14 is evaluated as if it was

x = v1.__mul__( 14 )

For line 3, the reverse name is used. The expression 28*v1 is evaluated as if it was

y = v1.__rmul__( 28 )

The Operator Algorithm. The algorithm for determing what happens with x op y is approximately as follows. Historically, as Python has evolved, so have the ins and outs of argument coercion. In the future, beginning with Python 3.0, the older notion of type coercion and the coerce function will be dropped altogether, so we'll focus on the enduring features that will be preserved. Section 3.4.8 of the Python Language Reference covers this in more detail; along with the caveat that the rules have gotten too complex.

Note than a special method function can return the value NotImplemented. This indicates that the operation can't work directly only the values, and another operation should be chosen. The rules provide for a number of alternative operations, this allows a class to be designed in a way that will cooperate successfully with potential future subclasses.

  1. The expression string % anything is a special case and is handled first. This assures us that the value of anything is left untouched by any other rules. Generally, it is a tuple or a dictionary, and should be left as such.

  2. If this is an augmented assignment statement (known as an in-place operator, e.g., a += b ) where the left operand implements __iop__, then the __iop__ special method is invoked without any coercion. These in-place operators permit you to do an efficient udpate the left operand object instead of creating a new object.

  3. As a special case, if an operator's left operand is an object of a superclass of the right operand's class, the right operand's __rop__ (subclass) method is tried first. If this is not implemented or returns NotImplemented, then the the left operand's __op__ (superclass) method is used. This is done so that a subclass can completely override binary operators, even for built-in types.

  4. Generally, x.__op__( y ) is tried first. If this is not implemented or returns NotImplemented, y.__rop__( x ) is tried second. In the case of

The following functions are the “forward” operations, used to implement the associated expressions.

method function original expression
__add__( self , other ) self + other
__sub__( self , other ) self - other
__mul__( self , other ) self * other
__div__( self , other ) self / other
__mod__( self , other ) self % other
__divmod__( self , other ) divmod ( self , other )
__pow__( self , other , [ modulo ] ) self ** other or pow ( self , other , [ modulo ] )
__lshift__( self , other ) self << other
__rshift__( self , other ) self >> other
__and__( self , other ) self and other
__xor__( self , other ) self xor other
__or__( self , other ) self or other

The method functions in this group are used to resolve operators using by attempting them using a reversed sense.

method function original expression
__radd__( self , other ) other + self
__rsub__( self , other ) other - self
__rmul__( self , other ) other * self
__rdiv__( self , other ) other / self
__rmod__( self , other ) other % self
__rdivmod__( self , other ) divmod ( other , self )
__rpow__( self , other ) other ** self or pow ( other , self )
__rlshift__( self , other ) other << self
__rrshift__( self , other ) other >> self
__rand__( self , other ) other and self
__rxor__( self , other ) other xor self
__ror__( self , other ) other or self

The method functions in the following group implement the basic unary operators.

method function original expression
__neg__( self ) - self
__pos__( self ) + self
__abs__( self ) abs ( self )
__invert__( self ) ~ self
__complex__( self ) complex ( self )
__int__( self ) int ( self )
__long__( self ) long ( self )
__float__( self ) float ( self )
__oct__( self ) oct ( self )
__hex__( self ) hex ( self )

Rational Number Example. Consider a small example of a number-like class. The the section called “Rational Numbers” exercise in Chapter 21, Classes describes the basic structure of a class to handle rational math, where every number is represented as a fraction. We'll add some of the special methods required to make this a proper numeric type. We'll finish this in the exercises.

class Rational( object ):
    def __init__( self, num, denom= 1L ):
        self.n= long(num)
        self.d= long(denom)
    def __add__( self, other ):
        return Rational( self.n*other.d + other.n*self.d, 
        self.d*other.d )
    def __str__( self ):
        return "%d/%d" % ( self.n, self.d )

This class has enough methods defined to allow us to add fractions as follows:

>>> 

x = Rational( 3, 4 )

>>> 

y = Rational( 1, 3 )

>>> 

print x+y

7/12
>>> 

In order to complete this class, we would need to provide most of the rest of the basic special method names (there is no need to provide a definition for __del__). We would also complete the numeric special method names.

Additionally, we would have to provide correct algorithms that reduced fractions, plus an additional conversion to respond with a mixed number instead of an improper fraction. We'll revisit this in the exercises.

Conversions From Other Types. For your class to be used successfully, your new numeric type should work in conjunction with existing Python types. You will need to use the isinstance function to examine the arguments and make appropriate conversions.

Consider the following expressions:

x = Rational( 22, 7 )
y = x+3
z = x+0.5

Variables y and z should be created as Rational fractions. However, our initial __add__ function assumed that the other object is a Rational object. Generally, numeric classes must be implemented with tests for various other data types and appropriate conversions.

We have to use the isinstance function to perform checks like the following: isinstance( other, int ). This allows us to detect the various Python built-in types.

If the result of isinstance( other , factory ) is true in any of the following cases, some type of simple conversion should be done, if possible.

  • isinstance( other, complex ). You may want to raise an exception here, since it's hard to see how to make rational fractions and complex numbers conformable. If this is a common situation in your application, you might need to write an even more sophisticated class that implements complex numbers as a kind of rational fraction. Another choice is to write a version of the abs function of the complex number, which creates a proper rational fraction for the complex magnitude of the given value.

  • isinstance( other, float ). One choice is to truncate the value of other to long, using the built-in long function and treat it as a whole number, the other choice is to determine a fraction that approximates the floating point value.

  • isinstance( other, (int,long) ). Any of these means that the other value is clearly the numerator of a fraction, with a denominator of 1.

  • isinstance( other, str ) or isinstance( other, unicode ) or isinstance( other, basestring ). Any of these might convert the other value to a long using the built-in long function. If the conversion fails, an exception will be thrown, which will make the error obvious. The basestring type, by the way, is the superclass for ASCII strings (str) and Unicode strings (unicode).

  • isinstance( other, Rational ). This indicates that the other value is an instance of our Rational class; we can do the processing as expected, knowing that the object has all the methods and attributes we need.

Here is a version of __sub__ with an example of type checking. If the other argument is an instance of the class Rational, we can perform the subtract operation. Otherwise, we attempt to convert the other argument to an instance of Rational and attempt the subtraction between two Rationals.

def __sub__( self, other ):
    if isinstance(other,Rational):
        return Rational( self.n*other.d - other.n*self.d, 
        self.d*other.d )
    else:
        return self - Rational(long(other))

An alternative to the last line of code is the following.

        return Rational( self.n-long(other)*self.d, self.d )

While this second version performs somewhat quicker, it expresses the basic rational addition algorithm twice, once in the if: suite and again in the else: suite. A principle of object oriented programming is to maximize reuse and minimize restating an algorithm. My preference is to state the algorithm exactly once and reuse it as much as possible.

Reverse Operators. In many cases, Python will reverse the two operands, and use a function like __rsub__ or __rdiv__. For example:

def __rsub__( self, other ):
    if isinstance(other,Rational):
       return Rational( other.n*self.d - self.n*other.d, 
       self.d*other.d )
    else:
        return Rational(long(other)) - self

You can explore this behavior with short test programs like the following:

x = Rational( 3,4 )
print x-5
print 5-x

 
 
  Published under the terms of the Open Publication License Design by Interspire