I am trying to figure out a way of having the setter of a property of a property trigger some action on the top level class.
As a dummy example, lets say my top level class is a Segment
. All is good if I store the coordinates of its endpoints directly as properties of this object x0
, y0
, x1
and y1
, and have the setters of each one trigger the selected action.
But if I want to group them into two Point
members as properties p0
and p1
, each with properties x
and y
, whenever one of these coordinates is modified, there is no obvious way of telling the Segment
to do something. This is what I would like to be able to do:
>>> segment = Segment(Point(0, 0), Point(3, 3))
>>> segment.p0
Point(0, 0)
>>> segment.p0.x
0
>>> segment.p1.y = 4
Length of segment changed to 5.0! # This can only be printed by segment, not p1!
The problem is that the line segment.p1.y = 4
first calls the getter of p1
on the segment
instance, and then the setter of y
on the return of the previous call, at which point there is no simple way of letting the segment
instance know that a change has been made.
The best I can think of right now is something along the lines of the following:
class Point(object):
def __init__(self, x, y, parent=None, name=None):
self.parent, self.name = parent, name
self._x, self._y = x, y
@property
def x(self):
return self._x
@x.setter
def x(self, value):
self._x = value
if parent is not None:
setattr(self.parent, self.name, self)
# Similar code for y goes here...
class Segment(object):
def __init__(self, p0, p1):
self.p0, self.p1 = p0, p1
@property
def p0(self):
return self._p0
@p0.setter
def p0(self, point):
self._p0 = point
self.p0.parent = self
self.p0.name = 'p0'
if not self._silent:
self.do_something() # This would print the length in the above example
# Similar code for p1 goes here...
While this does what I want it to, I don't quite like having to manually add that link back to the parent, nor how I would either have to make lots of redundant copies of the Point
objects, or risk interesting bugs if doing something like:
p0, p1, p2 = Point(0, 0), Point(1, 1), Point(2, 2)
seg0 = Segment(p0, p1)
seg1 = Segment(p0, p2)
# The following line changes the value on both seg0 and seg1, but triggers
# the do_something call on seg1 only!
seg0.p0.x = 6
Is there some ready-made recipe for this? Anyone can come up with a better way of doing it?