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