Subclassing complex with computed arguments

P

Peter Olsen

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.

This is probably very simple, but it hasn't been simple to me.

Please feel free to flame me with RTFM as long as you also tell me
where in TFM to R.

If you respond, please send a copy directly to me. I seldom have
access to this group. (My primary network isn't connected to the
internet.)

Peter
pcolsen-at-comcast.net
 
D

Dan Perl

I need a clarification. Does "sample" have to be an instance of complex or
can it be a subclass of complex? So can it be an instance of point as long
as point is a subclass of complex?

If "sample" has to be an instance of complex and not of a subclass, I think
you need to subclass point from complex and override the __new__() method to
return an instance of complex and override __init__() to use all your
arglist. In that case you also need yet another method to actually create
instances of point.

If "sample" can be an instance of a subclass of complex, I think you can
just subclass point from complex and override only __init__().

There is more in the details I guess, but I would like to first see an
answer to the above questions.

Dan
 
K

Kent Johnson

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
 
B

Bengt Richter

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.

This is probably very simple, but it hasn't been simple to me.

Please feel free to flame me with RTFM as long as you also tell me
where in TFM to R.

If you respond, please send a copy directly to me. I seldom have
access to this group. (My primary network isn't connected to the
internet.)
Well, let us know how it goes, and maybe tell the curious what your
application is ;-). BTW, as others have explained, the initialization
of values is done via complex.__new__, not via __init__. You can override that
too, but I just let it be inherited to do its normal thing.

The following is a way to automate wrapping complex so as to return point instances
from the various operations. Not tested beyond what you see, and it won't
be a speed demon, but might be something to explore your concept with.

If you want to add special methods, you can do it like __repr__ and __coerce__
in the example, or you could add them after. Or you could use a proper metaclass
instead of doing it the function way.

Python 2.4b1 (#56, Nov 3 2004, 01:47:27)
[GCC 3.2.3 (mingw special 20030504-1)] on win32
Type "help", "copyright", "credits" or "license" for more information. ... """complex wrapper subclass point"""
... def __metaclass__(cname, cbases, cdict):
... P = type(cname, cbases, cdict)
... def mkm(name, func):
... def m(*args): return P(func(*args))
... m.__name__ = name
... return m
... for name, func in complex.__dict__.items():
... if callable(func) and name not in ['__coerce__',
... '__doc__', '__float__', '__getattribute__', '__getnewargs__',
... '__hash__', '__int__', '__long__', '__new__', '__repr__',
... '__str__', 'imag', 'real']:
... setattr(P, name, mkm(name, func))
... def __coerce__(self, other): return self, P(other)
... P.__coerce__ = __coerce__
... def __repr__(self): return '<point %s>'% complex.__repr__(self)
... P.__repr__ = __repr__
... return P
... 3.0

You can explore further, and fix the bugs ;-)
I don't know what __getnewargs__ does, so I left it alone to be inherited.

Regards,
Bengt Richter
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,212
Messages
2,571,101
Members
47,695
Latest member
KayleneBee

Latest Threads

Top