0

How is it possible that the rectangle-object listens to the Points attributes x and y and if they change, the rectangle-object recalculates the area?

If I do it with setters and getters, every time I access the area attribute, the area will be recalculated. If the calculation is very expensive (I do some more stuff here) this is not an optimal solution for me. Is it possible to listen to the Points, only recalculating the area if they change?

I have a class called Rectangle and a class called Point:

class Point(object):

    def __init__(self, x, y):
        self.x = x
        self.y = y


class Rectangle(object):

    def __init__(self, points=None):
        self.points = [] if points is None else points
        self.area = self.calc_area()

    def calc_area(self):
        return (self.points[0].x - self.points[1].x) * (self.points[0].y - self.points[1].y)

Then I create two points and a rectangle with the two points:

# create the points:
points = list()
points.append(Point(0,0))
points.append(Point(1,1))

# create the rectangle:
rect = Rectangle(points)
print(rect.area)

Now I change the coordinates of the first point:

# change the points coordinates:
points[0].x = 0.5
points[0].y = 0.5

# Now the area should be recalculated.
print(rect.area)

MaxxiKing
  • 83
  • 6
  • Take a loop at [property setters](https://stackoverflow.com/questions/6618002/using-property-versus-getters-and-setters), that might help you. – Jens May 16 '19 at 10:06

3 Answers3

2

Solution:

You can declare area as property.

class Rectangle(object):
    def __init__(self, points=list()):
        self.points = points
        # self.area = self.calc_area() -- removed

    @property
    def area(self):
        return = (self.points[0].x - self.points[1].x) * (self.points[0].y - self.points[1].y)

It will solve problem.

Upd.

If you want area to be recalculated only if values changed, you can use custom flag and set it on property setter.

Code:

class Point(object):
    def __init__(self, x, y):
        self._x = x
        self._y = y
        self.updated = True

    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, value):
        self.updated = True
        self._x = value

    @property
    def y(self):
        return self._y

    @y.setter
    def y(self, value):
        self.updated = True
        self._y = value


class Rectangle(object):
    def __init__(self, points=None):
        self.points = [] if points is None else points
        self._area = 0

    @property
    def area(self):
        if any(point.updated for point in self.points):
            self._area = (self.points[0].x - self.points[1].x) * (self.points[0].y - self.points[1].y)
            for point in self.points:
                point.updated = False
            print("recalculated") # delete it, it's just for test
        return self._area


points = [Point(0, 0), Point(1, 1)]

rect = Rectangle(points)
print(rect.area)
print(rect.area)

points[0].x = 0.5
points[0].y = 0.5

print(rect.area)

Output:

recalculated
1
1
recalculated
0.25
Olvin Roght
  • 7,677
  • 2
  • 16
  • 35
  • Thank you very much, your solution works! But do you know a way without modifying the `Point` class? – MaxxiKing May 16 '19 at 10:57
  • @MaxxiKing, there's a way to declare class, based on `list` and check for modifications in this class. There's also a way to backup last values and check them. There're lot of other ways how to do this, but main idea is the same - you should somehow save that variable have been modified and check for "midified" state. – Olvin Roght May 16 '19 at 12:31
1

You could either:

  • make the variables x and y private, and have getters and setters to access them, and in the setters, also update the area
  • instead of accessing rect.area, just call rect.calc_area() when you want the area
Ollie
  • 1,641
  • 1
  • 13
  • 31
  • Thank you for your suggestion! But if I do it with setters and getters, every time I access the area attribute, the area will be recalculated. If the calculation is very expensive (I do some more stuff here) this is not an optimal solution for me. Is it possible to listen to the Points, only recalculating the area if they change? – MaxxiKing May 16 '19 at 10:13
  • If you use setters and getters for x and y, it will only update the area if x or y change... – Ollie May 16 '19 at 11:14
0

Thank you @OlvinRoght, I think the question in your link is the best solution to do this. So now I implemented a observer-pattern. Here I can bind every point in Rectangle points list to the update function.

class Point(object):

    def __init__(self, x, y):
        self._x = x
        self._y = y
        self._observers = []

    @property
    def x(self):
        return self._x

    @property
    def y(self):
        return self._y

    @x.setter
    def x(self, value):
        self._x = value
        for callback in self._observers:
            print('announcing change')
            callback()

    @y.setter
    def y(self, value):
        self._y = value
        for callback in self._observers:
            print('announcing change')
            callback()

    def bind_to(self, callback):
        print('bound')
        self._observers.append(callback)


class Rectangle(object):

    def __init__(self, points=None):
        self.points = [] if points is None else points
        self.area = []

        for point in self.points:
            point.bind_to(self.update_area)
        self.area = (self.points[0].x - self.points[1].x) * (self.points[0].y - self.points[1].y)

    def update_area(self):
        print('updating area')
        self.area = (self.points[0].x - self.points[1].x) * (self.points[0].y - self.points[1].y)


if __name__ == '__main__':

    # create points:
    points = list()
    points.append(Point(0, 0))
    points.append(Point(1, 1))

    # create the rectangle:
    rect = Rectangle(points)
    print('Area = {}'.format(rect.area))

    # change point coordinates
    points[0].x = 0.5
    points[0].y = 0.5
    print('Area = {}'.format(rect.area))

    # change point coordinates again:
    points[0].x = 0.25
    points[0].y = 0.25
    print('Area = {}'.format(rect.area))

    # just print the area; the area is not recalculated:
    print('Area = {}'.format(rect.area))

Output:

Area = 1
announcing change
updating area
announcing change
updating area
Area = 0.25
announcing change
updating area
announcing change
updating area
Area = 0.5625
Area = 0.5625
MaxxiKing
  • 83
  • 6