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.
class Watched(list):
def append(self, element):
print "appending %s" % element
list.append(self, element)
l = Watched()
l.append(1)
// m
>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.
see EventObj:
http://oomadness.tuxfamily.org/p-pyobjtools.php
chris
--
Chris <clie...@gmx.net>
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
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"
works for me.
chris
--
Chris <clie...@gmx.net>
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
Well, I'm not sure what makes it obvious, but it is correct :)
Thanks!
>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.
> 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
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.
#!/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
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
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
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.
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
<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()