0

I'm trying to create a simple Rectangle-class in python, but I also need to use points and sizes in my code, so I'm trying to inherit my Rectangle from Point and Size. The problem is, my Rectangle's initialize method looks awful and I'm not sure if it's even suitable for any code at all. Here's what I got:

class Size:
    def __init__(self, width=0, height=0):
        self.width = width
        self.height = height

class Point:
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

class Rectangle(Size, Point):
    def __init__(self, size=None, position=None): #I'd rather not use 4 variables
        if size:
            super(Size, self).__init__(size.width, size.height)
        else:
            super(Size, self).__init__()
        if position:
            super(Point, self).__init__(position.x, position.y)
        else:
            super(Point, self).__init__()

However, it looks awful and it doesn't even work: TypeError: object.__init__() takes no parameters

Is there a cleaner way to do this? I could of course just force my rectangle to take size and position (not to make them optional) but I would rather not. I could also define my Rectangle to have a has-a relationship with Size and Point rather than is-a relationship, but that's not even proper OOP and I'm mostly learning here so I'd rather not do that either.

3 Answers 3

3

You're using super() wrong. You specify your own class in the call, to get to the next parent class. All your other classes need to do the same thing. The way you're doing it now, you're saying to call e.g. Size's parent class, which is object.

This being Python 3, you can just do super() without any arguments and it'll figure all that out for you.

Of course this means that you can't explicitly call both parent classes using super(), as you want to do, because a given class has only one super(). If you really want to do that, you'll have to call them as Size.__init__(self, ...) and so on.

But I have to say, the inheritance scheme you have cooked up does not make any sense. A rectangle is neither a kind of size nor a kind of point. Instead, it has those things, which indicates encapsulation (where a Rectangle has size and position attributes which are instances of those particular classes, or, really, namedtuples) rather than inheritance. Encapsulation is certainly "proper OOP."

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks, no reason not to accept this one too, but I can't accept two answers and I think Markus' answer was just slightly better :P Totally gave my upvote tho!
1

First I'm just gonna answer your question: Go with has-a relationship.

class Rectangle:
    def __init__(self, size=None, position=None):
        self.size = size or Size()
        self.position = position or Point()

Think about it: is rectangle a position and a size, or does rectangle have a position and a size? If you're coding a game which constructs of only 2D rectangles, then there's your answer, stop reading.


...however, if you're going to have more shapes (such as Circle), you should think twice before doing anything. When coding OOP, always start by thinking what you need. So, eventually we might need a Circle class too. We should think of that and other shapes before coding our Rectangle-class.

Circle and Rectangle both have a common attribute called position. This is why you should have a base class called Shape or Geometry which defines atleast position.

class Geometry:
    def __init__(self, position=None):
        self.position = position or Point()

class Rectangle(Geometry):
    def __init__(self, size=None, position=None):
        self.size = size or Size()
        super().__init__(position)

class Circle(Geometry):
    def __init__(self, radius=0, position=None):
        self.radius = radius
        super().__init__(position)

Also think about what kind of other classes you might have, and see if they have any common attributes:

  • Triangle
  • Pentagon
  • Other polygons
  • Line
  • etc.

You'll soon realize they all got position, which is why we have Geometry. They also have somekind of size (length for line, radius for circle, width&height for triangle...), so you can create few base classes for different size's too (such as width&height for triangle and rectangle).

1 Comment

Thanks for the answer, you've answered my other question earlier on and I really like the way you approach OOP, accepted! :)
0

super() is meant for a very specific usecase, namely when you have a hierarchy of objects with the same __init__() variables, and where you are not in complete control over the object hierarchy, so you don't know exactly how it looks.

Then, you use super().

This case is in practice when you are writing a library with mixin classes. Although it's possible to do that here in theory, it breaks down because you can't have the same __init__() parameters, and then super() simply does not work very well.

Therefore, forget about super(). Do it like this:

class Size(object):
    def __init__(self, width=0, height=0):
        self.width = width
        self.height = height

class Point(object):
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

class Rectangle(Size, Point):
    def __init__(self, x=0, y=0, width=0, height=0):
        Point.__init__(self, x, y)
        Size.__init__(self, width, height)

No super in sight. (Note that I subclass everything from object, it's not needed in Python 3, but it's good practice as long as Python 2 is still in widespread use.)

But yes, this uses four parameters, which you don't want, for some reason. There are two solutions to that. One is Markus proposal, to use containment instead:

class Rectangle(object):
    def __init__(self, size=None, position=None):
        self.size = size or Size()
        self.position = position or Point()

But that means you have to stick all the methods of Point() and Size() onto Rectangle as well. Another solution is to be a bit clever, and use a very useful builtin type: complex!

class Point(object):
    def __init__(self, pos=0j):
        self.pos = pos

class Size(object):
    def __init__(self, size=0j):
        self.size = size

class Rectangle(Size, Point):
    def __init__(self, pos=0j, size=0j):
        Point.__init__(self, pos)
        Size.__init__(self, size)

Using complex numbers for 2D graphics simplifies a lot. I wouldn't even bother having a Point and Size class, I'd just use complex directly:

class Rectangle(object):
    def __init__(self, pos=0j, size=0j):
        self.pos = pos
        self.size = size

    def endpos(self):
        return self.pos + self.size


>>> r = Rectangle(5+3j, 3+7j)
>>> print(r.endpos())
(8+10j)

This is also better because just as the containment suggestion, is has a size and position, instead of being a size and position, which makes much more sense.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.