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

Roeland Rengelink r.b.rigilink at chello.nl
Sun Sep 23 05:10:40 EDT 2001


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.rigilink at chello.nl

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



More information about the Python-list mailing list