Newbie: question regarding references and class relationships

Ian Kelly ian.g.kelly at gmail.com
Mon Jun 10 15:56:15 EDT 2013


On Mon, Jun 10, 2013 at 7:57 AM, Rui Maciel <rui.maciel at gmail.com> wrote:
> # Case A: this works
> model.points[0].position = [2,3,4]
> line.points
>
> # Case B: this doesn't work
> test.model.points[0] = test.Point(5,4,7)
> line.points
> </code>
>
>
> Is there a Python way of getting the same effect with Case B?


It's informative to think about what object is actually being mutated
in each of those examples.  In A, you are mutating the Point instance
by replacing its "position" attribute with a new list.  Since the Line
object references the same Point instance, it "sees" the change.  In
B, you are actually mutating a list *containing* the Point instance,
by replacing the Point in the list with some new Point.  The replaced
Point itself is not changed at all (apart from having one fewer
reference), and so from the perspective of the Line nothing has
changed.

There are a couple of ways you might get this to work the way you
want.  One is by adding as an extra layer a proxy object, which could
be as simple as:

class Proxy(object):
    def __init__(self, ref):
        self.ref = ref

Instead of adding points to the model, add instances of Proxy that
contain the points.  When adding points to the lines, add the same
Proxy objects instead.  Later, when you want to replace a point in the
model, leave the Proxy in place but change the Point it contains by
modifying the "ref" attribute.  Since the Line only directly
references the Proxy, it must follow the same ref attribute to access
the Point, and so in that way it will "see" the change.  The downsides
to this approach are that you have to then use the Proxy objects all
over the place and explicitly "dereference" them by using the ref
attribute all over the place.

Another possibility is to subclass the list used to store the points,
in order to modify the way that it replaces items.  That could look
something like this:

class UpdateList(list):
    def __setitem__(self, index, item):
        if isinstance(index, slice):
            indices = range(* index.indices(len(self)))
            items = list(item)
            if len(indices) != len(items):
                raise TypeError("Slice length does not match sequence length")
            for i, x in zip(indices, items):
                self[i].update(x)
        else:
            self[index].update(item)

Then you just need to add an "update" method to the Point class (and
any other class you might want to use this with) that would update the
Point's attributes to match those of another point.  The downside here
is that this is not very Pythonic, in that somebody familiar with
Python who is reading the code would be surprised by the way the model
updates work.

A third possibility of course would be to just not try to do case B.

Cheers,
Ian



More information about the Python-list mailing list