Peter said:
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.
point(2j)
point(2+4j)
A point can be used where a complex is expected - though the result
is a complex,
not a point:
(1.272019649514069+0.78615137775742328j)
This doesn't work correctly - it calls complex.__add__() directly
and the result doesn't get wrapped:
(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.
point(1-2j)
point(2+4j)
Since point does not subclass complex, it must be explicitly coerced
to a complex when one is needed:
Traceback (most recent call last):
(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