Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

watching mutables?

4 views
Skip to first unread message

Anton Vredegoor

unread,
Sep 27, 2002, 10:12:05 AM9/27/02
to
Hello all,

here's a function I would like to have:

<hypothetical code>

>>> m = [1,2]
>>> m
>>> [1,2]
>>> from watcher import watch
>>> watch(m)
>>> m.append(3)
>>> "hey, you changed variable m!, new value is [1,2,3]"

</hypothetical code>

Is this possible? How would this be called?

Anton.

Mark McEahern

unread,
Sep 27, 2002, 10:34:55 AM9/27/02
to
> Subject: watching mutables?

class Watched(list):

def append(self, element):
print "appending %s" % element
list.append(self, element)

l = Watched()
l.append(1)

// m

Anton Vredegoor

unread,
Sep 27, 2002, 12:26:07 PM9/27/02
to
On Fri, 27 Sep 2002 09:34:55 -0500, "Mark McEahern"
<mark...@mceahern.com> wrote:

>l = Watched()
>l.append(1)

Thanks for reacting to my question. However, I am looking for
something that calls a defineable function if *any* mutable variable
in the current scope changes. Variables should be 'marked' to install
this function for them. For example 'watch(m,f)' indicates that
function f is called if mutable variable m is changed. I do not want
function f to be a method of object m since I want object m to be
*any* mutable python object and furthermore I do not want to change
the orginal object. The call of function f would be triggered by *any*
change of object m. This would make debugging (and some other things I
am tinkering with) a lot easier. Is it possible?

Regards,

Anton.

Chris Liechti

unread,
Sep 27, 2002, 1:24:53 PM9/27/02
to
an...@vredegoor.doge.nl (Anton Vredegoor) wrote in
news:an211j$oi$1...@news.hccnet.nl:

see EventObj:
http://oomadness.tuxfamily.org/p-pyobjtools.php

chris
--
Chris <clie...@gmx.net>

Thomas Heller

unread,
Sep 27, 2002, 1:55:16 PM9/27/02
to
Chris Liechti <clie...@gmx.net> writes:

This is what I get when clicking on the download link:

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL /downloads/PyObjTools-0.1.tar.gz was not found on this server.</p>
<p>Additionally, a 404 Not Found
error was encountered while trying to use an ErrorDocument to handle the request.</p>
</body></html>

Thomas

Skip Montanaro

unread,
Sep 27, 2002, 1:03:03 PM9/27/02
to

Anton> Variables should be 'marked' to install this function for
Anton> them. For example 'watch(m,f)' indicates that function f is
Anton> called if mutable variable m is changed.

You could implement a proxy object which delegates to the real object and
catches all __setattr__ and __setitem__ calls. Something like:

class MutableProxy:
def __init__(self, obj):
self.__dict__['obj'] = obj

def __setattr__(self, attr, val):
print "Hey! My %s attribute got diddled!" % attr
setattr(self.__dict__['obj'], attr, val)

def __getattr__(self, attr):
return getattr(self.__dict__['obj'], attr)

if __name__ == "__main__":
class Dummy:
pass

d = MutableProxy(Dummy())
d.x = 1
print d.x

__setitem__ and __getitem__ are similar, but you have to worry about ranges.
You'd probably have to wrap __call__ as well.

There's probably a way to do it with metaclasses, but ... ooh! I just got a
headache, better back off before my brain expl ... <* blam! *>

--
Skip Montanaro - sk...@pobox.com
"Airplanes don't fly until the paperwork equals the weight of the
aircraft. Same with i18N." - from the "Perl, Unicode and i18N FAQ"

Chris Liechti

unread,
Sep 27, 2002, 2:48:10 PM9/27/02
to
Thomas Heller <thomas...@ion-tof.com> wrote in
news:k7l7qp...@ion-tof.com:
> Chris Liechti <clie...@gmx.net> writes:
[triggered by *any* change of object m]
>> see EventObj:
>> http://oomadness.tuxfamily.org/p-pyobjtools.php

>>
> This is what I get when clicking on the download link:
> <title>404 Not Found</title>

works for me.

chris

--
Chris <clie...@gmx.net>

Michael Stenner

unread,
Sep 27, 2002, 3:26:50 PM9/27/02
to

I find that rather surprising since that file isn't there. Are you
sure you clicked on the download link? For your convenience, I
provide that link here:

http://oomadness.tuxfamily.org/downloads/EventObj-0.2.tar.gz

I am also interested in this module (curiosity, mostly) and would
appreciate a working link if you have one.

-Michael

--
Michael Stenner Office Phone: 919-660-2513
Duke University, Dept. of Physics mste...@phy.duke.edu
Box 90305, Durham N.C. 27708-0305

Thomas Heller

unread,
Sep 27, 2002, 4:06:41 PM9/27/02
to
"Michael Stenner" <mste...@phy.duke.edu> wrote in message news:mailman.1033154856...@python.org...

> On Fri, Sep 27, 2002 at 08:48:10PM +0200, Chris Liechti wrote:
> > Thomas Heller <thomas...@ion-tof.com> wrote in
> > news:k7l7qp...@ion-tof.com:
> > > Chris Liechti <clie...@gmx.net> writes:
> > [triggered by *any* change of object m]
> > >> see EventObj:
> > >> http://oomadness.tuxfamily.org/p-pyobjtools.php
> > >>
> > > This is what I get when clicking on the download link:
> > > <title>404 Not Found</title>
> >
> > works for me.
>
> I find that rather surprising since that file isn't there. Are you
> sure you clicked on the download link? For your convenience, I
> provide that link here:
>
> http://oomadness.tuxfamily.org/downloads/EventObj-0.2.tar.gz
>
> I am also interested in this module (curiosity, mostly) and would
> appreciate a working link if you have one.
>
Here's what seems to be available:
http://oomadness.tuxfamily.org/downloads/
and this is obviously where the EventObject is contained in:
http://oomadness.tuxfamily.org/downloads/EditObj-0.3.tar.gz

Thomas


Michael Stenner

unread,
Sep 27, 2002, 4:35:03 PM9/27/02
to
On Fri, Sep 27, 2002 at 10:06:41PM +0200, Thomas Heller wrote:
> "Michael Stenner" <mste...@phy.duke.edu> wrote ...

> > I am also interested in this module (curiosity, mostly) and would
> > appreciate a working link if you have one.
> >
> Here's what seems to be available:
> http://oomadness.tuxfamily.org/downloads/
> and this is obviously where the EventObject is contained in:
> http://oomadness.tuxfamily.org/downloads/EditObj-0.3.tar.gz

Well, I'm not sure what makes it obvious, but it is correct :)
Thanks!

Anton Vredegoor

unread,
Sep 27, 2002, 5:12:05 PM9/27/02
to
On Fri, 27 Sep 2002 22:06:41 +0200, "Thomas Heller"
<the...@python.net> wrote:

>Here's what seems to be available:
>http://oomadness.tuxfamily.org/downloads/
>and this is obviously where the EventObject is contained in:
>http://oomadness.tuxfamily.org/downloads/EditObj-0.3.tar.gz

Very interesting module. I might add that windows doesn't seem to like
the .cfg file so this should be moved to some other place before
installing with 'python setup.py install'.

But...

I specifically asked for something that doesn't touch the original
object. In eventobj.py's docstring there is the following warning:

<warning>
Caution : As event management is performed by changing the class of
the instance, you use eventobj with critical objects at your own
risk... !
</warning>

So, with a lot of thanks for the results sofar ...

Is it possible?

Anton.

Fernando PĂ©rez

unread,
Sep 27, 2002, 5:38:11 PM9/27/02
to
Anton Vredegoor wrote:

> But...
>
> I specifically asked for something that doesn't touch the original
> object. In eventobj.py's docstring there is the following warning:
>
> <warning>
> Caution : As event management is performed by changing the class of
> the instance, you use eventobj with critical objects at your own
> risk... !
> </warning>
>
> So, with a lot of thanks for the results sofar ...
>
> Is it possible?

Well, somehow you have to hook into the object to trap modifications to it,
no? The other option is via a debugger interface, which works at the bytecode
level with the interpreter itself. Look into the python debugger (pdb) for
this. Incidentally, ddd knows how to interface to pdb and can do what you are
asking about. It's been a long time since I've used it, but I've done exactly
that with ddd in the past.

Cheers,

f

Michael Stenner

unread,
Sep 27, 2002, 5:39:11 PM9/27/02
to
On Fri, Sep 27, 2002 at 11:12:05PM +0200, Anton Vredegoor wrote:
> On Fri, 27 Sep 2002 22:06:41 +0200, "Thomas Heller"
> >Here's what seems to be available:
> >http://oomadness.tuxfamily.org/downloads/
> >and this is obviously where the EventObject is contained in:
> >http://oomadness.tuxfamily.org/downloads/EditObj-0.3.tar.gz
>
> Very interesting module. I might add that windows doesn't seem to like
> the .cfg file so this should be moved to some other place before
> installing with 'python setup.py install'.
>
> But...
>
> I specifically asked for something that doesn't touch the original
> object. In eventobj.py's docstring there is the following warning:
>
> <warning>
> Caution : As event management is performed by changing the class of
> the instance, you use eventobj with critical objects at your own
> risk... !
> </warning>
>
> So, with a lot of thanks for the results sofar ...
>
> Is it possible?

Just to be clear, it doesn't so much affect the original object, but
rather REPLACES the class before the object ever gets created. If
this isn't satisfactory, then I think you're going to need to modify
the python interpreter.

Mark McEahern

unread,
Sep 27, 2002, 6:02:06 PM9/27/02
to
Someone (I think Skip?) mentioned the possibility of using metaclasses. The
OP has asked for a method that doesn't require modifying the original code.
This doesn't satisfy that request, but is interesting nonetheless (imho):

#!/usr/bin/env python

class Watched(type):

def __new__(cls, cls_name, bases, classdict):
# print "__new__(%s, %s, %s, %s)" % (cls, cls_name, bases,
classdict)
def w_setattr(self, name, value):
print "%s.__setattr__(%s, %s)" % (cls_name, name, value)
bases[0].__setattr__(self, name, value)

def w_setitem(self, i, el):
print "%s.__setitem__(%s, %s)" % (cls_name, i, el)
bases[0].__setitem__(self, i, el)

def w_getitem(self, i):
print "%s.__getitem__(%s)" % (cls_name, i)
return bases[0].__getitem__(self, i)

def w_getattribute(self, name):
print "%s.__getattribute__(%s)" % (cls_name, name)
return bases[0].__getattribute__(self, name)

classdict['__setattr__'] = w_setattr
classdict['__setitem__'] = w_setitem
classdict['__getitem__'] = w_getitem
classdict['__getattribute__'] = w_getattribute

return type.__new__(cls, cls_name, bases, classdict)

class WatchedDict(dict):

__metaclass__ = Watched

class WatchedList(list):

__metaclass__ = Watched

l = []
l.append(1)

l = WatchedList()
l.append(1)

d = WatchedDict()
d['a'] = 1

print d['a']

**
Output:

WatchedList.__getattribute__(append)
WatchedDict.__setitem__(a, 1)
WatchedDict.__getitem__(a)
1


Mark McEahern

unread,
Sep 27, 2002, 6:17:18 PM9/27/02
to
[I apologize for not providing context, but I've already deleted my copy of
the specific message I'm replying to.]

The reason a metaclass solution is not possible without modifying the
original code has to do with how the metaclass is determined:

http://www.python.org/2.2.1/descrintro.html#metaclasses

1. First, Python looks for '__metaclass__' within the class' dict.
2. Python uses the metaclass of any base class.
3. Python looks for a global __metaclass__.
4. Python uses the classic metaclass types.ClassType.

With builtin lists and dicts, Python never gets past step 2, so you have to
intervene forcefully with step 1 if you want to override the metaclass of a
builtin class, which means you have to first subclass it:

class WatchedDict(dict):

__metaclass__ = Watched

// m


Jiba

unread,
Sep 28, 2002, 4:24:31 PM9/28/02
to
Anton Vredegoor wrote:

Try EditObj (http://oomadness.tuxfamily.org/en/editobj) and look at the
module editobj.eventobj.

It does exactely what you need, but it is quite a hack...

Jiba

Samuele Pedroni

unread,
Sep 29, 2002, 11:23:35 AM9/29/02
to

"Jiba" <ji...@tuxfamily.org> ha scritto nel messaggio
news:3d96f25a$0$498$7a62...@news.club-internet.fr...

>
> Try EditObj (http://oomadness.tuxfamily.org/en/editobj) and look at the
> module editobj.eventobj.
>
> It does exactely what you need, but it is quite a hack...

FYI in Python 2.3: __class__ will be immutable for builtin types, and only
mutable for (user) subtypes.

See:

http://mail.python.org/pipermail/python-checkins/2002-August/028681.html

regards.


Bengt Richter

unread,
Sep 29, 2002, 9:05:17 PM9/29/02
to

I suspect you may have to adjust your concept of "variable"
to think about this in Python terms. I.e.,

m = [1, 2]

doesn't result in a "variable" m with value [1,2]. It results in
an object (a list instance [1, 2]) and a name "m" in a particular
name space, and a relationship between the name and the object,
called binding.

This gives you several things that can change: the binding of "m"
and the mutable features of the object it is bound to.

Watching for a change in the binding of a name is possible, but you
will somehow have to give the watching mechanism information about the
actual name and name space (e.g., watch('m', vars()), not watch(m) )
and provide a trigger for the checking operation. If the name is an attribute
of an object that you can mess with, you may be able to monitor all
attribute name setting via __setattr__, but you might then miss changes
made some other way like obj.__dict__['m'] = whatever, so you probably
want to use some kind of tracing hook to get control for polling the status.

Watching for a change in the object being referred to by m is a different thing.
For that, you don't need to tell the watcher about the name per se ('m') unless
you want the name in a message like the (somewhat misleading) one you specified.
For change in the object, you also have to decide what "change" is supposed to mean.
I.e., is it based on '==' or what? Also, you have to get control to the checking
mechanism again, either by hooking into the object's mutation facilities, or an
independent polling trigger.

I'd be surprised if some solutions to your problem didn't already exist, and I
expect someone will chime in. I mainly wanted to point out that the "variable"
concept does not apply to Python the way it does to C or C++.

Nevertheless, here's something that appears to track a single named binding.
I use change in marshalled value as the criterion for change detection.
(not tested beyond what you see ;-):

----< Watcher.py >-----
class Watcher:
from sys import settrace
from marshal import dumps
def __init__(self, name, namespace):
self.name = name
self.ns = namespace
obj = namespace.get(name)
self.objid = id(obj)
self.objs = self.dumps(obj)
self.settrace(self.watch)
def watch(self, frame, event, arg):
obj = self.ns.get(self.name)
objid = id(obj)
if objid != self.objid:
self.objid = objid
what = 'new'
else:
what = 'same but mutated'
objs = self.dumps(obj)
if self.objs != objs:
print '"%s" is bound to %s object: %s' % (self.name, what, `obj`)
self.objs = objs
elif what=='new':
what = 'new but equivalent'
print '"%s" is bound to %s object: %s' % (self.name, what, `obj`)

return self.watch
def finished(self):
self.settrace(None)
-----------------------
>>> import Watcher
>>> m=[1,2]
>>> w = Watcher.Watcher('m',locals())
>>> m.append(3)
"m" is bound to same but mutated object: [1, 2, 3]
>>> m=4
"m" is bound to new object: 4
>>> m=5
"m" is bound to new object: 5
>>> m=5
>>> m=[6]
"m" is bound to new object: [6]
>>> m=[6]
"m" is bound to new but equivalent object: [6]
>>> m[0]=7
"m" is bound to same but mutated object: [7]
>>> m = m
>>> x = m
>>> m = [7]
"m" is bound to new but equivalent object: [7]
>>> m = x
"m" is bound to new but equivalent object: [7]
>>> x.append(8)
"m" is bound to same but mutated object: [7, 8]

Notice that x.append(8) affected m's "value", but neither binding.

Regards,
Bengt Richter

Anton Vredegoor

unread,
Sep 30, 2002, 12:48:03 PM9/30/02
to
On 30 Sep 2002 01:05:17 GMT, bo...@oz.net (Bengt Richter) wrote:

<good explanation deleted>

I have slightly adjusted your code to result in the code below, this
works if imported by Idle like this:

Python 2.2 (#28, Dec 21 2001, 12:21:22) [MSC 32 bit (Intel)] on win32
Type "copyright", "credits" or "license" for more information.
IDLE 0.8 -- press F1 for help
>>> from watcher import Watcher
>>> w = Watcher('m',locals())
>>> m = 1
"m" is bound to new object: 1


So this seems to be ok. However if the script below is executed
itself, nothing happens. Also importing the module in another script
and then executing a test script gives no output. The watch function
isn't called at all. I guess the tracer has to know more about the
other arguments in:

def watch(self, frame, event, arg):

Could this please be elucidated.

Thanks for your insightful remarks and code.

Anton.


#watcher.py
from sys import settrace, modules
from marshal import dumps

class Watcher:

def __init__(self, name, namespace):
self.name = name
self.ns = namespace
obj = namespace.get(name)
self.objid = id(obj)

self.objs = dumps(obj)
settrace(self.watch)

def watch(self, frame, event, arg):
obj = self.ns.get(self.name)
objid = id(obj)
if objid != self.objid:
self.objid = objid
what = 'new'
else:
what = 'same but mutated'

objs = dumps(obj)


if self.objs != objs:
print '"%s" is bound to %s object: %s' % (self.name,
what, `obj`)
self.objs = objs

elif what == 'new':


what = 'new but equivalent'
print '"%s" is bound to %s object: %s' % (self.name,
what, `obj`)
return self.watch

def finished(self):
settrace(None)

def test():
# huh ?
w = Watcher('m', locals())
m = 1

if __name__=='__main__':
test()

0 new messages