Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

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

12 views
Skip to first unread message

Paul Rubin

unread,
Sep 23, 2001, 1:44:52 AM9/23/01
to
Say I have a class Polygon, with subclasses Rectangle (4 sides)
and Triangle (3 sides) which have some special operations.

I'd like to be able to call the Polygon constructor with a "sides"
argument and get a subclass instance if appropriate, e.g. I'd like to
call Polygon(sides=3) and get back a triangle, e.g. something like
class Polygon:
def __init__(sides=None):
if sides==3: return Triangle(...)

but of course class initializers in Python don't return values.

Is wanting that bad OO practice in the first place? Is there a way to
do it in Python? A way to do it in other OO languages? I'm not too
up on this type of thing.

The obvious workaround is to define a function that calls the appropriate
constructor, e.g.
def newpoly(sides):
if sides==3: return Triangle(...)
...
but this isn't so great, because it doesn't fit in with multiple base
classes:
class RotatingMixin:
def rotate(): ... # make object rotate on the screen

class RotatingPolygon(Polygon, RotatingMixin): pass

Ideally I'd like a definition like that to automatically result in
rotating triangles and rectangles as well as polygons. I think CLOS
and/or Flavors can do things like that, but I've never used them.

Yet another workaround is to create a container object that has a
polygon as an instance variable:

class PolygonWrapper:
def __init__(self,sides):
if sides==3: self.mypoly=Triangle(...)
elif sides==4: self.mypoly=Rectangle(...)
else: self.mypoly=Triangle

but now the wrapper class has to intercept all the operations that can
be done on any of the shapes, and pass them to the internal object.

Hmm, is something like
def __getattr__(self,sym): return getattr(self.mypoly,sym)
in the PolygonWrapper class a reasonable solution?

I'm wondering how you OO wizards deal with this type of question.
It would seem to come up pretty often in OO designs.

Regards from caveman C programming land,

--phr

Terry Reedy

unread,
Sep 23, 2001, 4:39:14 AM9/23/01
to

"> 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.

Terry J. Reedy


Paul Rubin

unread,
Sep 23, 2001, 4:43:39 AM9/23/01
to

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?

Roeland Rengelink

unread,
Sep 23, 2001, 5:10:40 AM9/23/01
to

Hi Paul,

I hope this is not the only answer you get, because I've been struggling
with the same problem. I really don't know if it's a good idea to use
the solution I present below.

Paul Rubin wrote:
>
> Say I have a class Polygon, with subclasses Rectangle (4 sides)
> and Triangle (3 sides) which have some special operations.
>
> I'd like to be able to call the Polygon constructor with a "sides"
> argument and get a subclass instance if appropriate, e.g. I'd like to
> call Polygon(sides=3) and get back a triangle, e.g. something like
> class Polygon:
> def __init__(sides=None):
> if sides==3: return Triangle(...)
>
> but of course class initializers in Python don't return values.

> The obvious workaround is to define a function that calls the appropriate


> constructor, e.g.
> def newpoly(sides):
> if sides==3: return Triangle(...)
> ...

> but this isn't so great, because it doesn't fit in with multiple base
> classes:
> class RotatingMixin:
> def rotate(): ... # make object rotate on the screen
>
> class RotatingPolygon(Polygon, RotatingMixin): pass
>

> Ideally I'd like a definition like that to automatically result in
> rotating triangles and rectangles as well as polygons.

You could use metaclasses to get exactly this.

Metaclasses (MC's) allow you to do the two things you're asking for:
1. Use the MC's __call__ to implement a factory that plays nice with
inheritance
2. Use the MC's __init__ to automatically make square and triangle
versions of the RotatingPolygon class

In 2.2 it would be something like:

class _Polygon(object):
def __init__(self, sides):
self.sides = sides

class _Triangle(_Polygon):
def __init__(self):
self.sides = 3

class _Square(_Polygon):
def __init__(self):
self.sides = 4


class _PolygonMetaclass(object):
def __init__(self, name, bases, attributes):
if bases == ():
# class Polygon
self._pclass = _Polygon
self._tclass = _Triangle
self._sclass = _Square
else:
# class decending from Polygon
self._pclass = type(name,
(bases[0]._pclass,)+bases[1:],
attributes)
self._tclass = type(name,
(bases[0]._tclass,)+bases[1:],
attributes)
self._sclass = type(name,
(bases[0]._sclass,)+bases[1:],
attributes)

def __call__(self, sides, *args, **kwargs):
if sides == 3:
return self._tclass(*args, **kwargs)
if sides == 4:
return self._sclass(*args, **kwargs)
return self._pclass(sides, *args, **kwargs)

class Polygon:
__metaclass__ = _PolygonMetaclass

t = Polygon(3)
s = Polygon(4)
p = Polygon(5)

print t.__class__, t.sides
print s.__class__, s.sides
print p.__class__, p.sides

class RotationMixin(object):
pass

class RotatingPolygon(Polygon, RotationMixin):
pass

t = RotatingPolygon(3)
s = RotatingPolygon(4)
p = RotatingPolygon(5)

print t.__class__.__bases__, t.sides
print s.__class__.__bases__, s.sides
print p.__class__.__bases__, p.sides

Running this gives me:

<type '__main__._Triangle'> 3
<type '__main__._Square'> 4
<type '__main__._Polygon'> 5
(<type '__main__._Triangle'>, <type '__main__.RotationMixin'>) 3
(<type '__main__._Square'>, <type '__main__.RotationMixin'>) 4
(<type '__main__._Polygon'>, <type '__main__.RotationMixin'>) 5

I think most people would consider your proposed PolygonWrapper with
delegation through __getattr__ a very nice alternative ;)

--
r.b.ri...@chello.nl

"Half of what I say is nonsense. Unfortunately I don't know which half"

Stefan Schwarzer

unread,
Sep 23, 2001, 11:39:58 AM9/23/01
to
Hello Paul

Paul Rubin wrote:
> > 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?

I would use the factory funtion approach:

# polygon_test
class Polygon:
def identify(self):
print self.__class__

class Triangle(Polygon):
def triangle_method(self):
pass

class Rectangle(Polygon):
def rectangle_method(self):
pass

def factory(sides=None):
special_polygons = {3: Triangle,
4: Rectangle}
if sides in special_polygons.keys():
return special_polygons[sides]()
else:
return Polygon()

Python 2.1.1 (#0, Aug 5 2001, 15:48:16) [EMX GCC 2.8.1] on os2emx
Type "copyright", "credits" or "license" for more information.
>>> import polygon_test
>>> p = polygon_test.factory()
>>> p.identify()
polygon_test.Polygon
>>> t = polygon_test.factory(3)
>>> t.identify()
polygon_test.Triangle
>>> t.triangle_method()
>>>

> The obvious workaround is to define a function that calls the appropriate
> constructor, e.g.
> def newpoly(sides):
> if sides==3: return Triangle(...)
> ...

> but this isn't so great, because it doesn't fit in with multiple base
> classes:
> class RotatingMixin:
> def rotate(): ... # make object rotate on the screen
>
> class RotatingPolygon(Polygon, RotatingMixin): pass
>
> Ideally I'd like a definition like that to automatically result in
> rotating triangles and rectangles as well as polygons. I think CLOS

Your first model,

> class Polygon:
> def __init__(sides=None):
> if sides==3: return Triangle(...)

wouldn't do that either, even if

> but of course class initializers in Python don't return values.

would not be true. You would have:

Polygon
/ | \
Triangle | RotatingPolygon
|
Rectangle

so Triangle and Rectangle wouldn't inherit the rotate method. However, you
could add the rotate method directly to the Polygon class. (Chuck Esterbrook
has written an article about this in Linux Journal, if I remember correctly.)
And in that case, the factory function would also do the job.

Stefan

Corran Webster

unread,
Sep 23, 2001, 1:04:34 PM9/23/01
to
In article <7xofo2l...@ruckus.brouhaha.com>, Paul Rubin
<phr-...@nightsong.com> wrote:

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

Chris Tavares

unread,
Sep 23, 2001, 4:40:52 PM9/23/01
to
"Paul Rubin" <phr-...@nightsong.com> wrote in message
news:7xofo2l...@ruckus.brouhaha.com...

I've got a possibly sick solution. Assuming that the only parameter you're
dealing with is the sides, then you can use a dictionary and the fact that
classes are callable objects, something like this:

class GenericPolygon:
pass

class Triangle:
pass

class Rectangle:
pass

def newPolygon( sides ):
lookup = { 3 : Triangle, 4 : Rectangle }
creator = lookup.get( sides, GenericPolygon )
return creator()

Then to update your class list all you need to do is add new entries to
lookup. If you've got more complex criteria (more than one parameter, for
example) you can either a) if the criteria are simple constants you can use
tuples as the dict keys rather than numbers or b) replace lookup with a list
of predicate functions that test if the parameters match criteria for each
class.

-Chris

John Roth

unread,
Sep 23, 2001, 11:15:01 PM9/23/01
to

"Paul Rubin" <phr-...@nightsong.com> wrote in message
news:7xofo2l...@ruckus.brouhaha.com...

The thing is, that's exactly what a factory function is for - it does
whatever
special case logic is required to get the right class instantiated, so that
the code doesn't have to be replicated elsewhere in the system.

John Roth


Paul Rubin

unread,
Sep 24, 2001, 12:05:17 AM9/24/01
to
"John Roth" <john...@ameritech.net> writes:
> > 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?
>
> The thing is, that's exactly what a factory function is for - it
> does whatever special case logic is required to get the right class
> instantiated, so that the code doesn't have to be replicated
> elsewhere in the system.

Yes, however it doesn't completely solve the problem. Roeland's
post using metaclasses does what I need--all I have to do now is
spend a few more hours poring over his post trying to understand it :).

James_...@i2.com

unread,
Sep 24, 2001, 1:04:02 PM9/24/01
to pytho...@python.org

Paul,

I'm not sure if I understand everything you are looking for. But here is
an example of how to do instance creation using the new class methods in
Python 2.2 (using a Smalltalk-ish OO idiom). The basic idea is to use
class methods for your factory functions. The *big* advantage to using
class methods (for factory functions) instead of using module functions is
that you can define "subclass" factory functions and take advantage of
inheritance and method override.

Hope this helps.

Jim

==================================

class Polygon:

subclassDict = {}

## class methods

def registerSubclass(myclass,classObj,sides):
myclass.subclassDict[sides] = classObj

registerSubclass = classmethod(registerSubclass)

def getSubclassWithSides(myclass,sides): # we will override this in
subclasses
return myclass.subclassDict.get(sides)

getSubclassWithSides = classmethod(getSubclassWithSides)

def newInstanceWithSides(myclass,sides,*pargs,**kargs): # main factory
function
instance = None
classObj = myclass.getSubclassWithSides(sides)
if classObj:
instance = classObj(*pargs,**kargs)
return instance

newInstanceWithSides = classmethod(newInstanceWithSides)

## add instance methods here if needed


class Triangle: pass

Polygon.registerSubclass(Triangle,3)


class Square: pass

Polygon.registerSubclass(Square,4)


class Rotating: pass

class RotatingPolygon(Polygon,Rotating):

## class methods

def getSubclassWithSides(myclass,sides): # override superclass class
method
classObj = Polygon.getSubclassWithSides(sides)
if classObj:
class mixinClass(classObj,Rotating): pass
mixinClass.__name__ = classObj.__name__ + Rotating.__name__ #
so that print will verify
classObj = mixinClass
return classObj

getSubclassWithSides = classmethod(getSubclassWithSides)

## add instance methods here if needed


triangle = Polygon.newInstanceWithSides(3)
print triangle

square = Polygon.newInstanceWithSides(4)
print square

rotatingTriangle = RotatingPolygon.newInstanceWithSides(3)
print rotatingTriangle

rotatingSquare = RotatingPolygon.newInstanceWithSides(4)
print rotatingSquare

C:\>python
Python 2.2a1 (#21, Jul 18 2001, 04:25:46) [MSC 32 bit (Intel)] on win32


Type "copyright", "credits" or "license" for more information.
>>>
>>>

>>> import example1
<example1.Triangle instance at 007FA8CC>
<example1.Square instance at 007FA77C>
<example1.TriangleRotating instance at 007FA67C>
<example1.SquareRotating instance at 007FA1CC>
>>>


0 new messages