Ben Ben - 1 year ago 62
Python Question

Subclassing in python of instantiated superclass

Is it possible to subclass in Python using an already instantiated superclass?

I don't exactly know how to frame the question so let me give an example. Suppose I have a class Rectangle and I want to build another class ColoredRectangle. But I don't want each

of the same dimensions to be its own new
. So when I initiate
I would pass it an already instantiated

Ie., I want

r = Rectangle([1,2])
r_red = ColoredRectangle(r, "red")
r_blue = ColoredRectangle(r, "blue")

But now
should be able to get all rectangle methods and attributes. For example suppose
had an


should "point" to the same
. I know I could do this by writing:

class ColoredRectangle(Rectangle):

def __init__(self, rectangle, color):
self.color = color
self.rectangle = rectangle

But then I'd have to write


which is ugly.

Answer Source


Inheritance is such a nice thing in python, and I don't think you have to resort to getattr hacks, if you want those, scroll down.

You can force the class dictionary to refer to another object:

class Rectangle(object):
    def __init__(self, width, height):
        self.width = width
        self.height = height
    def area(self):
        return self.width * self.height

class ColoredRectangle(Rectangle):
    def __init__(self, rect, color):
        self.__dict__ = rect.__dict__
        self.color = color

rect = Rectangle(3, 5)
crect = ColoredRectangle(rect, color="blue")
print crect.width, crect.height, crect.color
#3 5 blue

These two will refer to the same Rectangle object:

print rect.width, rect.height
#10 5

This is an exellent talk on metaprogramming, and while it's title implies Python3 a lot of it also applies to python 2.x: David Beazley - Python3 Metaprogramming

getattr hacking

If for any reason however, you would want to have multiple ColoredRectangle refer to the same base Rectangle then these will conflict with each other:

eve = Rectangle(3, 5)
kain = ColoredRectangle(eve, color="blue")
abel = ColoredRectangle(eve, color="red")
print eve.color, kain.color, abel.color
#red red red

If you'd like different "proxy objects", which can get attributes from the base Rectangle but not interfere with each other, you have to resort to getattr hacking, which is fun too:

class ColoredRectangle(Rectangle):
    def __init__(self, rect, color):
        self.rect = rect
        self.color = color
    def __getattr__(self,attr):
        return getattr(self.rect,attr)
eve = Rectangle(3, 5)

This will avoid the interference:

kain = ColoredRectangle(eve, color="blue")
abel = ColoredRectangle(eve, color="red")
print kain.color, abel.color
#blue red

About __getattr__ versus __getattribute__:

A key difference between getattr and getattribute is that getattr is only invoked if the attribute wasn't found the usual ways. It's good for implementing a fallback for missing attributes, and is probably the one of two you want. source

Because only non found attributes will be handled by __getattr__ you can also partially update your proxies, which might be confusing:

print eve.area(), kain.area(), abel.area()
# 15 50 15

To avoid this you can override __setattr__:

def __setattr__(self, attr, value):
    if attr == "color":
        return super(ColoredRectangle,self).setattr(attr,value)
    raise YourFavoriteException