Notify of change to list

A

Alan J. Salmoni

Hello everyone,

I searched through groups to find an appropriate answer to this one
but could only find these which didn't meet my program's needs:
http://groups.google.com/group/comp...&q=monitor+change+in+mutable#5e4b7119afa5f8df,
http://groups.google.com/group/comp...t&q=monitor+change+in+object#bccfd49a5c8a56e2.

My question is how can my program be notified of a change to a class
attribute that is a list? I have a class defined that has a list as an
attribute and the program needs to know when the user changes this
attribute either completely or elementwise via an interpreter built-in
to the program. Using __setattr__ I can be informed whenever it is
rebound (x.attr = [2,3,4]) but not when changes are made elementwise
(x.attr[1] = 99). I tried using properties to get changes but again
the same result occurs.

class c(object):
def __init__(self):
self._x = 0

def getx(self):
return self._x

def setx(self, x):
print "setting x: ", x
self._x = x

x = property(getx, setx)

# all of the following need to be caught
a.x = [1,2,3] # is rebound from 0 so is caught
a.x[1] = 99 # element-wise change only which isn't caught
a.x = [4,5,6] # is rebound so is caught
a.x[0:3] = [11,12,13] # though a "complete" change in terms of
elements, this is element-wise and is not rebound and is thus not
caughtsetting x: [1, 2, 3]
setting x: [4, 5, 6]

From what I understand, changes will only be caught if the attribute
is completely rebound (ie, completely re-assigned), but I cannot force
my users to do this. Short of hacking around with the interpreter to
catch element-wise reassignments (possible, but I'd rather avoid), can
anyone suggest a way to do this?

Alan
 
P

Paul Hankin

My question is how can my program be notified of a change to a class
attribute that is a list?

You can't. But you can replace lists inside your class with a list
that notifies changes.

Something like this:

class NotifyingList(list):
def __setitem__(self, i, v):
print 'setting', i, 'to', v
list.__setitem__(self, i, v)

[[You'll likely need to redefine __setslice__, __delitem__,
__delslice__, append and extend as well]].
x = NotifyingList([1, 2, 3])
x[1] = 4 setting 1 to 4
x
[1, 4, 3]

If users of your class are allowed to replace attributes with their
own lists, you'll have to catch these and convert to NotifyingLists;
and it may be somewhat messy.

I hope this is useful to you.
 
A

Alan J. Salmoni

Paul - thank you very much for your help, it was much appreciated. I
thought that this was the kind of thing I had to do.

I also messed around with properties to try and solve this problem,
but noticed a similar thing (ie, that changing a mutable attribute's
element is different to rebinding it): when a "propertied" attribute
is rebound, the set method is called as expected. However, when
setting an element of a mutable like a list, the get method is called
and the set is not. This seems to be a bit of a gotcha. I'm sure that
calling the get method is justified in the terms of Python internals,
but as a user it seems a little counter-intuitive. Without being an
expert on language design, my first thought was that changing an
element was a "set" operation. Here's a toy:

class tobj(object):
def __init__(self):
self._x = [0,1,2,3,4,5,6]

def setx(self, x):
print "At setx"
self._x = x

def getx(self):
print "At getx"
return self._x

d = property(getx, setx)

a = tobj()
print "setting one element only"
a.d[1] = 3
print "rebinding the attribute"
a.d = 99

which comes up with:

setting one element only
At getx
rebinding the attribute
At setx

Does anyone know why it works like this? I would guess that to "set"
one element, the attribute needs to be retrieved which means the get
method is called. I also guess that only rebinding calls the set
method which is why it isn't called here.

Alan

My question is how can my program be notified of a change to a class
attribute that is a list?

You can't. But you can replace lists inside your class with a list
that notifies changes.

Something like this:

class NotifyingList(list):
def __setitem__(self, i, v):
print 'setting', i, 'to', v
list.__setitem__(self, i, v)

[[You'll likely need to redefine __setslice__, __delitem__,
__delslice__, append and extend as well]].
x = NotifyingList([1, 2, 3])
x[1] = 4 setting 1 to 4
x

[1, 4, 3]

If users of your class are allowed to replace attributes with their
own lists, you'll have to catch these and convert to NotifyingLists;
and it may be somewhat messy.

I hope this is useful to you.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,780
Messages
2,569,608
Members
45,241
Latest member
Lisa1997

Latest Threads

Top