Is there a way to instantiate a subclass from a class constructor?

Corran Webster cwebster at nevada.edu
Sun Sep 23 13:04:34 EDT 2001


In article <7xofo2l2kk.fsf at ruckus.brouhaha.com>, Paul Rubin
<phr-n2001 at nightsong.com> wrote:

> "Terry Reedy" <tjreedy at home.com> writes:
> > "> The obvious workaround is to define a function that calls the
> > appropriate
> > > constructor, e.g.
> > >   def newpoly(sides):
> > >     if sides==3: return Triangle(...)
> > >     ...
> > 
> > A factory function like this is a standard solution that has been
> > discussed in several posts on this list.  Don't knock it.
> 
> I'm not knocking it, I just don't see how to extend the class from
> additional base classes without having to add more special code to
> the factory function, which is against the OO spirit.  Any thoughts?

It would seem that you would have to write special code for additional
base classes anyway, since if you had something like:

class Polygon:
    def __init__(self, sides):
        if sides == 3:
            return Triangle()
        elif sides == 4:
            return Rectangle()
        return self

then you would need to re-write the __init__ method of a new, related
class (like RotatedPolygon, which adds a Rotated mixin class to the
Polygon class) to return the new base classes (RotatedTriagnle and
RotatedSquare).  This seems like the same amount of work as re-writing a
module factory function for the new situation.

One way to get what you want is to keep information about the special-case
subclasses in a class variable of the base class, something like this:

class Polygon:
    pass

class Rectangle(Polygon):
    pass

class Triangle(Polygon):
    pass

Polygon.special_cases = {3: Triangle, 4: Rectangle}

class Rotated:
    pass

class RotatedPolygon(Rotated, Polygon):
    pass

class RotatedRectangle(Rotated, Rectangle):
    pass

class RotatedTriangle(Rotated, Triangle):
    pass

RotatedPolygon.special_cases = {3: RotatedTriangle, 4: RotatedRectangle}

def newpolygon(sides, base=Polygon):
    polyclass = base.special_cases.get(sides, base)
    return polyclass(sides)

This allows the factory function to determine what subclass it should use
by passing  it the appropriate base class.  If you need more sophisticated
logic to determine what base class you want to use, you could instead hold
the special cases as class variables and put the logic in the factory
function:

Polygon.Triangle = Triangle
Polygon.Rectangle = Rectangle

RotatedPolygon.Triangle = RotatedTriangle
RotatedPolygon.Rectangle = RotatedRectangle

def newpolygon(sides, base=Polygon):
    if sides == 3:
        return base.Triangle()
    elif sides == 4:
        return base.Rectangle()
    return base(sides)

mytriangle = newpolygon(3, RotatedPolygon)

Notice that most of the power of this approach flows from the fact that
everything is an object in Python, and so you can sling around and store
classes and functions just as easily as you can numbers and strings.

I hope that this gives you some ideas for how you can elegantly approach
the situation in Python.

channelling-an-OO-spiritly-yrs,
Corran



More information about the Python-list mailing list