Question

Running this program

import pygame
pygame.init()

r = pygame.Rect((0, 0), (100, 100)) 
r.center = (455, 455)

print r

gives <rect(405, 405, 100, 100)> as output. If I were to check other attributes such as topleft, midbottom or centery, they would be updated as well.

This is very useful, but I have no idea how this is done. I'm updating the members directly without using any member functions of the Rect class. Does Python have some sort of "hook" that can be executed every time a member variable of a class is updated? Can I do this with my own classes, or this is a particular trick used by Pygame?

Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top