watching mutables?

Bengt Richter bokr at
Sun Sep 29 21:05:17 EDT 2002

On Fri, 27 Sep 2002 16:12:05 +0200, anton at (Anton Vredegoor) wrote:

>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?
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 ;-):

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

    def finished(self):
 >>> 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.

Bengt Richter

More information about the Python-list mailing list