6

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 ColoredRectangle of the same dimensions to be its own new Rectangle. So when I initiate ColoredRectangle I would pass it an already instantiated Rectangle.

Ie., I want

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

But now r_red and r_blue should be able to get all rectangle methods and attributes. For example suppose Rectangle had an area() attribute.

r.area
2
r_red.area
2

r_red and r_blue should "point" to the same Rectangle. 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

 r_red.rectangle.area

which is ugly.

Sebastian Wozny
17.6k7 gold badges55 silver badges73 bronze badges
asked Nov 1, 2015 at 14:28

3 Answers 3

7

Inheritance

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:

crect.width=10
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:

kain.width=10
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
answered Nov 2, 2015 at 0:11
Sign up to request clarification or add additional context in comments.

5 Comments

awesome! I was just looking into getattr and setattr and wondering about the class dictionary and how to use it. What's the difference between using __dict__ and __getattr__ and/or __setattr__? And thanks for the talk link. I'm always looking for good references.
It's three hours but Beazley is an amazing speaker. It's got the entire fun package: Regex, XML, exec, JIT code generation, import hacking, etc...
The __dict__ example does not work if the Rectangle class defines __slots__ or is written in C.
@SebastianWozny Am I not allowed to point out additional downsides. Not all python types have a\ __dict__ attribute and expose all their data in it. In fact, the proxy ColoredRectangle is such a non-compliant type.
Why does forcing class dictionaries have the side effect you mention? Does the line self.__dict__ = rect.__dict__ have the effect of setting the two dictionaries equal to each other in memory. So if you add one attribute to either dictionary you add it to both?
4

What you seem to be asking to do is to redirect attribute accesses to the underlying Rectangle object. The __getattr__ method can do this for you.

class ColoredRectangle(object):
 def __init__(self, rectangle, color):
 self.color = color
 self.rectangle = rectangle
 def __getattr__(self,attr):
 return getattr(self.rectangle,attr)
answered Nov 1, 2015 at 14:40

9 Comments

I think it's the best solution.
Maybe you should explain what that rectangle class attribute is for.
The reason for the rectangle class attribute is to server as a default, so that the __setattr__ magic method knows that it being called from __init__ and the class is still being initialized.
Sure, but information like that belongs in the answer itself, not just a comment. FWIW, I would've said something like "The rectangle class attribute forces __setattr__ to call the default object.__setattr__ when the instance attributes .color and .rectangle are being set during __init__, when there isn't yet a .rectangle instance attribute to re-direct __setattr__ calls to. And maybe you should also explicitly mention that if any new attributes are added to a ColoredRectangle instance they will actually get attached to the underlying Rectangle instance.
This behaviour seems like something I'd prefer to avoid. I shouldn't be able to change attributes of Rectangle from ColoredRectangle. Is it possible to prevent this?
|
0

Owerwrite all attributes by rectangle attributes. They are in __dict__ property.

import copy
class Rectangle(object):
 def __init__(self, area):
 self.area = area
class ColoredRectangle(Rectangle):
 def __init__(self, rectangle, color):
 self.__dict__ = copy.deepcopy(rectangle.__dict__)
 self.color = color
Perry
3,90326 gold badges40 silver badges51 bronze badges
answered Nov 1, 2015 at 14:38

11 Comments

I was going for a simple example. Suppose there are many many attributes and methods corresponding to Rectangle. I don't want to overwrite them all.
@Ben then use self.__dict__ = rectangle.__dict__. I improve answer
@TomaszJakubRup. Then where would you put your color attribute?
@ppperry self.color = color?
When you run this code with two ColoredRectangles with the same base Rectangle, the first one's color is lost.
|

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.