The documentation calls these virtual attributes.
In the C code that makes up the Rect class, topleft looks like this:
/*topleft*/
static PyObject*
rect_gettopleft (PyRectObject *self, void *closure)
{
return Py_BuildValue ("(ii)", self->r.x, self->r.y);
}
static int
rect_settopleft (PyRectObject *self, PyObject* value, void *closure)
{
int val1, val2;
if (!TwoIntsFromObj (value, &val1, &val2))
{
RAISE (PyExc_TypeError, "invalid rect assignment");
return -1;
}
self->r.x = val1;
self->r.y = val2;
return 0;
}
...amnd topright like this...
/*topright*/
static PyObject*
rect_gettopright (PyRectObject *self, void *closure)
{
return Py_BuildValue ("(ii)", self->r.x+self->r.w, self->r.y);
}
static int
rect_settopright (PyRectObject *self, PyObject* value, void *closure)
{
int val1, val2;
if (!TwoIntsFromObj (value, &val1, &val2))
{
RAISE (PyExc_TypeError, "invalid rect assignment");
return -1;
}
self->r.x = val1-self->r.w;
self->r.y = val2;
return 0;
}
As you can see in rect_gettopright
it is generated on the fly; it adds self->r.x
and self->r.w
to return the total value of the right 'x' value of the topright corner.
Basically, yes, they're convenience hooks.
EDIT: one could do a pure Python
version of this using properties:
class PyRect(object):
def __init__(self, x, y, w, h):
self.x = x
self.y = y
self.w = w
self.h = h
self._topright = None
@property
def topright(self):
if self._topright is None:
self._topright = (self.x + self.w, self.y)
return self._topright
@topright.setter
def topright(self, newx, newy):
self.x = newx - self.w
self.y = newy
self._topright = (newx, newy)
That's really quick and dirty, but it has the same overall effect.