Subclassing complex with computed arguments

Kent Johnson kent3737 at yahoo.com
Sat Nov 27 06:52:27 EST 2004


Peter Olsen wrote:
> I want to define a class "point" as a subclass of complex.  
> 
> When I create an instance 
> 
> sample = point(<arglist>) 
> 
> I want "sample" to "be" a complex number, but with its real and
> imaginary parts computed in point()'s __init__ function with their
> values based on the arglist.  I want to compute with point instances
> as though they were native complex numbers, but I want to be able to
> define some new methods and over-ride some of complex's existing ones.

I have come up with two solutions to this. They both have warts.

Disclaimer: I have not extensively tested either of these. In 
particular, I have not tested all the numeric operations to make sure 
they give correct results!

The first attempt was to subclass complex directly, overriding __new__() 
to process the input parameters, and delegating all the numeric special 
methods to the base class implementation. The delegation wrapper 
converts the returned result back to a point.

The trick to getting this to work was to realize that, for example, 
complex.__add__(self, y) requires an actual complex instance for self. 
If self is a subclass of complex it returns NotImplemented. So my 
delegation wrapper constructs a new complex object before calling the 
base class method.

The wart in this implementation is that mixed operations with complex 
don't always return a point. If the complex is the left operand, 
complex.__op__ is called directly and the result is not wrapped back to 
a point.

Here is the implementation:

############## point.py ################
''' A subclass of complex that supports the full range of complex 
numeric operations
     by subclassing complex.

     >>> p=point(1,2)
     >>> p
     point(1+2j)
     >>> p+1
     point(2+2j)
     >>> 1+p
     (2+2j)
     >>> p-1
     point(2j)

     >>> c=complex(1,2)
     >>> p+c
     point(2+4j)

     A point can be used where a complex is expected - though the result 
is a complex,
     not a point:

     >>> import cmath
     >>> cmath.sqrt(p)
     (1.272019649514069+0.78615137775742328j)

     This doesn't work correctly - it calls complex.__add__() directly
     and the result doesn't get wrapped:

     >>> c+p
     (2+4j)
'''

class point(complex):
     def __new__(cls, *args, **kwds):
         if len(args) == 2:
#            return complex.__new__(cls, args[0]*2, args[1]*2) # Special 
args processing goes here
             return complex.__new__(cls, args[0], args[1])
         return complex.__new__(cls, *args, **kwds)

     def __str__(self):
         return self.__repr__()

     def __repr__(self):
         s = complex.__repr__(self)
         if s.startswith('('):
             return 'point' + s
         return 'point(%s)' %s


def makeWrapper(attr):
     def wrapper(self, *args):
         val = getattr(complex, attr)(complex(self), *args)
#        print attr, args, val
         return point(val)
     return wrapper

for special in ['__abs__', '__add__', '__div__', '__divmod__',
         '__floordiv__', '__mod__', '__mul__', '__neg__',
         '__pos__', '__pow__', '__radd__', '__rdiv__',
         '__rdivmod__', '__reduce__', '__reduce_ex__',
         '__rfloordiv__', '__rmod__', '__rmul__',
         '__rpow__', '__rsub__', '__rtruediv__',
         '__sub__', '__truediv__']:
     setattr(point, special, makeWrapper(special))


def _test():
     import doctest, point
     return doctest.testmod(point)

if __name__ == "__main__":
     _test()

###########################################

My second attempt uses delegation rather than subclassing. point 
instances have a complex attribute and delegate operations to it.

The trick to getting this one to work was to realize that I had to call 
coerce() explicitly in my wrapper function.

The wart in this implementation is that points are not instances of 
complex, so to pass a point to e.g. cmath.sqrt(), you have to explicitly 
convert to a complex.

I think this implementation is probably safer, since it fails noisily 
and failures are easy to fix, whereas the first implementation could 
silently give errors (depending on how point and complex actually differ).

Here is the implementation:

################# point2.py ####################

''' A class that supports a full range of complex numeric operations by 
delegating
     to an instance of complex.

     >>> p=point(1,2)
     >>> p
     point(1+2j)
     >>> p+1
     point(2+2j)
     >>> 1+p
     point(2+2j)
     >>> p-1
     point(2j)
     >>> p-4j
     point(1-2j)

     >>> c=complex(1,2)
     >>> p+c
     point(2+4j)
     >>> c+p
     point(2+4j)

     Since point does not subclass complex, it must be explicitly coerced
     to a complex when one is needed:

     >>> import cmath
     >>> cmath.sqrt(p)
     Traceback (most recent call last):
       File "<stdin>", line 1, in ?
     TypeError: a float is required

     >>> cmath.sqrt(complex(p))
     (1.272019649514069+0.78615137775742328j)
'''

class point(object):
     def __init__(self, *args, **kwds):
         if len(args) == 1 and isinstance(args[0], complex):
             self.c = args[0]
         if len(args) == 2:
#            self.c = complex(args[0]*2, args[1]*2) # Special args 
processing goes here
             self.c = complex(args[0], args[1])
         else:
             self.c = complex(*args, **kwds)

     def __str__(self):
         return self.__repr__()

     def __repr__(self):
         s = repr(self.c)
         if s.startswith('('):
             return 'point' + s
         return 'point(%s)' %s

     def __complex__(self):
         return self.c


def makeWrapper(attr):
     def wrapper(self, y):
         x, y = coerce(self.c, y)
         val = getattr(x, attr)(y)
#        print attr, x, y, val
         return point(val)
     return wrapper


for special in ['__abs__', '__add__', '__div__', '__divmod__',
         '__floordiv__', '__mod__', '__mul__', '__neg__',
         '__pos__', '__pow__', '__radd__', '__rdiv__',
         '__rdivmod__', '__reduce__', '__reduce_ex__',
         '__rfloordiv__', '__rmod__', '__rmul__',
         '__rpow__', '__rsub__', '__rtruediv__',
         '__sub__', '__truediv__']:
     setattr(point, special, makeWrapper(special))


def _test():
     import doctest, point2
     return doctest.testmod(point2)

if __name__ == "__main__":
     _test()

####################################

HTH,
Kent



More information about the Python-list mailing list