[Python-Dev] "incriminated" module (was: buitlins instance have modifiable __class__?)
Samuele Pedroni
pedronis@bluewin.ch
Sun, 29 Sep 2002 15:03:01 +0200
This is a multi-part message in MIME format.
------=_NextPart_000_0149_01C267C9.4E13C0C0
Content-Type: text/plain;
charset="iso-8859-1"
Content-Transfer-Encoding: 7bit
[me]
> Maybe others will be less so, I have seen a module referred on c.l.p using
the
> "feature" escaped from the lab to make builtins observable <wink>.
>
the thread on c.l.p is google: watching mutables? group:comp.lang.python
the package is at
http://oomadness.tuxfamily.org/en/editobj/
author: Jean-Baptiste LAMY -- jiba@tuxfamily
I have directly attached the "incriminated" module for the curious.
Should I give them the bad news?
regards.
------=_NextPart_000_0149_01C267C9.4E13C0C0
Content-Type: text/plain;
name="eventobj.py"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
filename="eventobj.py"
# EventObj=0A=
# Copyright (C) 2001-2002 Jean-Baptiste LAMY -- jiba@tuxfamily=0A=
#=0A=
# This program is free software; you can redistribute it and/or modify=0A=
# it under the terms of the GNU General Public License as published by=0A=
# the Free Software Foundation; either version 2 of the License, or=0A=
# (at your option) any later version.=0A=
#=0A=
# This program is distributed in the hope that it will be useful,=0A=
# but WITHOUT ANY WARRANTY; without even the implied warranty of=0A=
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the=0A=
# GNU General Public License for more details.=0A=
#=0A=
# You should have received a copy of the GNU General Public License=0A=
# along with this program; if not, write to the Free Software=0A=
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 =
USA=0A=
=0A=
# This module is certainly my best hack ;-)=0A=
# It is nothing else than a sequence of hacks, from the beginning to the =
end !!!!!=0A=
=0A=
"""EventObj -- Allow to add attribute-change, content-change or =
hierarchy-change event to any Python instance.=0A=
=0A=
Provides the following functions (view their doc for more info):=0A=
dumper_event(obj, attr, oldvalue, newvalue)=0A=
addevent(obj, event)=0A=
hasevent(obj[, event])=0A=
removeevent(obj, event)=0A=
addevent_rec(obj, event)=0A=
removeevent_rec(obj, event)=0A=
And the following constant for addition/removal events:=0A=
ADDITION=0A=
REMOVAL=0A=
=0A=
Events : An event is any callable object that take 4 parameters (=3D the =
listened instance, the changed attribute name, the new value, the old =
value).=0A=
After registration with addevent, it will be called just after any =
attribute is change on the instance.=0A=
You can add many events on the same instance, and use the same event =
many times.=0A=
An instance can implement __addevent__(event, copiable =3D 0), =
__hasevent__(event =3D None) and __removeevent__(event) to allow a =
custom event system.=0A=
=0A=
Notice that the event is weakref'ed, so if it is no longer accessible, =
the event is silently removed.=0A=
=0A=
Caution : As event management is performed by changing the class of the =
instance, you use eventobj with critical objects at your own risk... !=0A=
=0A=
Quick example :=0A=
>>> from editobj.eventobj import *=0A=
>>> class C: pass=0A=
>>> c =3D C()=0A=
>>> def event(obj, attr, value, oldvalue):=0A=
... print "c." + attr, "was", `oldvalue` + ", is now", `value`=0A=
>>> addevent(c, event)=0A=
>>> c.x =3D 1=0A=
c.x was None, is now 1=0A=
=0A=
Addition/Removal events : if you add them to a list / UserList or a dict =
/ UserDict, or a subclass, events are also called for addition or =
removal in the list/dict.=0A=
Addition or removal can be performed by any of the methods of =
UserList/UserDict (e.g. append, extend, remove, __setitem__,...)=0A=
In this case, name will be the ADDITION or REMOVAL constant, value the =
added object for a list and the key-value pair for a dict, and oldvalue =
None.=0A=
=0A=
Quick example :=0A=
>>> from editobj.eventobj import *=0A=
>>> c =3D []=0A=
>>> def event(obj, attr, value, oldvalue):=0A=
... if attr is ADDITION: # Only for list / dict=0A=
... print `value`, "added to c"=0A=
... elif attr is REMOVAL: # Only for list / dict=0A=
... print `value`, "removed from c"=0A=
... else:=0A=
... print "c." + attr, "was", `oldvalue` + ", is now", `value`=0A=
>>> addevent(c, event)=0A=
>>> c.append(0)=0A=
0 added to c=0A=
=0A=
Hierachy events : such events are used with UserList or UserDict, and =
are usefull to listen an entire hierarchy (e.g. a list that contains =
other lists that can contain other lists...).=0A=
The event apply to the registred instance, and any other item it =
contains, and so on if the list/dict is a deeper hierarchy.=0A=
If you want to automatically add or remove the event when the hierarchy =
change (addition or removal at any level), use a HierarchyEvent (see =
example below).=0A=
=0A=
Quick example :=0A=
>>> from editobj.eventobj import *=0A=
>>> c =3D []=0A=
>>> def event(obj, attr, value, oldvalue):=0A=
... if attr is ADDITION:=0A=
... print `value`, "added to", `obj`=0A=
... elif attr is REMOVAL:=0A=
... print `value`, "removed from", `obj`=0A=
>>> addevent_rec(c, event)=0A=
>>> c.append([]) # This sub-list was not in c when we add =
the event...=0A=
[] added to [[]] # [[]] is c=0A=
>>> c[0].append(12) # ...but the hierarchical event has been =
automatically added !=0A=
12 added to [12] # [12] is c[0]=0A=
"""=0A=
=0A=
__all__ =3D [=0A=
"addevent",=0A=
"hasevent",=0A=
"removeevent",=0A=
"addevent_rec",=0A=
"removeevent_rec",=0A=
"dumper_event",=0A=
"ADDITION",=0A=
"REMOVAL",=0A=
]=0A=
=0A=
import new, weakref, types #, copy=0A=
from UserList import UserList=0A=
from UserDict import UserDict=0A=
=0A=
=0A=
def to_list(obj):=0A=
if isinstance(obj, list) or isinstance(obj, UserList): return obj=0A=
if hasattr(obj, "children"):=0A=
items =3D obj.children=0A=
if callable(items): return items()=0A=
return items=0A=
if hasattr(obj, "items"):=0A=
items =3D obj.items=0A=
if callable(items): return items()=0A=
return items=0A=
if hasattr(obj, "__getitem__"): return obj=0A=
return None=0A=
=0A=
def to_dict(obj):=0A=
if isinstance(obj, dict) or isinstance(obj, UserDict): return obj=0A=
return None=0A=
=0A=
def to_dict_or_list(obj):=0A=
content =3D to_dict(obj) # Try dict...=0A=
if content is None:=0A=
content =3D to_list(obj) # Try list...=0A=
if content is None: return None, None=0A=
return list, content=0A=
else:=0A=
return dict, content=0A=
=0A=
def to_content(obj):=0A=
content =3D to_dict(obj) # Try dict...=0A=
if content is None: return to_list(obj) or () # Try list...=0A=
return content.values()=0A=
=0A=
=0A=
def dumper_event(obj, attr, value, old):=0A=
"""dumper_event -- an event that dump a stacktrace when called. =
Usefull for debugging !=0A=
=0A=
dumper_event is the default value for add_event.=0A=
"""=0A=
import traceback=0A=
traceback.print_stack()=0A=
=0A=
if attr is ADDITION: print "%s now contains %s." % (obj, value)=0A=
elif attr is REMOVAL : print "%s no longer contains %s." % (obj, value)=0A=
else: print "%s.%s was %s, is now %s." % (obj, attr, =
old, value)=0A=
=0A=
=0A=
def addevent(obj, event =3D dumper_event):=0A=
"""addevent(obj, event =3D dumper_event)=0A=
Add the given attribute-change event to OBJ. OBJ must be a class =
instance (old or new style) or a list / dict, and EVENT a function that =
takes 4 args: (obj, attr, value, old).=0A=
EVENT will be called when any attribute of obj will be changed; OBJ is =
the object, ATTR the name of the attribute, VALUE the new value of the =
attribute and OLD the old one.=0A=
=0A=
EVENT defaults to the "dumper" event, which print each object =
modification.=0A=
=0A=
Raise eventobj.NonEventableError if OBJ cannot support event.=0A=
"""=0A=
=0A=
event =3D _wrap_event(obj, event)=0A=
=0A=
try:=0A=
obj.__addevent__(event)=0A=
except:=0A=
if hasattr(obj, "__dict__"):=0A=
# Store the event and the old class of obj in an instance of =
_EventObj_stuff (a class we use to contain that).=0A=
# Do that BEFORE we link the event (else we'll get a change event =
for the "_EventObj_stuff" attrib) !=0A=
obj._EventObj_stuff =3D _EventObj_stuff(obj.__class__)=0A=
=0A=
# Change the class of the object.=0A=
obj.__class__ =3D _create_class(obj, obj.__class__)=0A=
else:=0A=
# Change the class of the object.=0A=
old_class =3D obj.__class__=0A=
obj.__class__ =3D _create_class(obj, obj.__class__)=0A=
=0A=
stuff_for_non_dyn_objs[id(obj)] =3D _EventObj_stuff(old_class)=0A=
=0A=
obj.__addevent__(event)=0A=
=0A=
def hasevent(obj, event =3D None):=0A=
"""hasevent(obj[, event]) -> Boolean=0A=
Return wether the obj instance has the given event (or has any event, if =
event is None)."""=0A=
return hasattr(obj, "__hasevent__") and obj.__hasevent__(event)=0A=
=0A=
def removeevent(obj, event =3D dumper_event):=0A=
"""removeevent(obj, event =3D dumper_event)=0A=
Remove the given event from obj."""=0A=
hasattr(obj, "__removeevent__") and obj.__removeevent__(event)=0A=
=0A=
ADDITION =3D "__added__"=0A=
REMOVAL =3D "__removed__"=0A=
=0A=
NonEventableError =3D "NonEventableError"=0A=
=0A=
# Private stuff :=0A=
=0A=
# A dict to store the created classes.=0A=
#_classes =3D weakref.WeakKeyDictionary() # old-style class cannot be =
weakref'ed !=0A=
_classes =3D {}=0A=
=0A=
# Create a with-event class for "clazz". the returned class is a "mixin" =
that will extends clazz and _EventObj (see below).=0A=
def _create_class(obj, clazz):=0A=
try: return _classes[clazz]=0A=
except:=0A=
if hasattr(obj, "__dict__"):=0A=
# The name of the new class is the same name of the original class =
(mimetism !)=0A=
if issubclass(clazz, list) or issubclass(clazz, UserList): cl =
=3D new.classobj(clazz.__name__, (_EventObj_List, _EventObj, clazz), {})=0A=
elif issubclass(clazz, dict) or issubclass(clazz, UserDict): cl =
=3D new.classobj(clazz.__name__, (_EventObj_Dict, _EventObj, clazz), {})=0A=
else:=0A=
if issubclass(clazz, object): cl =
=3D new.classobj(clazz.__name__, (_EventObj, clazz), {})=0A=
else: cl =
=3D new.classobj(clazz.__name__, (_EventObj_OldStyle, clazz), {})=0A=
=0A=
else:=0A=
# list and dict were added in _classes at module initialization=0A=
# Other types are not supported yet !=0A=
raise NonEventableError, obj=0A=
=0A=
# Change also the module name.=0A=
cl.__module__ =3D clazz.__module__=0A=
_classes[clazz] =3D cl=0A=
return cl=0A=
=0A=
=0A=
# A container for _EventObj attribs.=0A=
class _EventObj_stuff:=0A=
def __init__(self, clazz):=0A=
self.clazz =3D clazz=0A=
self.events =3D []=0A=
=0A=
def __call__(self, obj, attr, value, oldvalue):=0A=
# Clone the list, since executing an event function may add or =
remove some events.=0A=
for event in self.events[:]: event(obj, attr, value, oldvalue)=0A=
=0A=
def remove_event(self, event): self.events.remove(event)=0A=
=0A=
def has_event(self, event): return event in self.events=0A=
=0A=
def _wrap_event(obj, event, hi =3D 0):=0A=
if not isinstance(event, WrappedEvent):=0A=
#dump =3D repr(event)=0A=
=0A=
try: obj =3D weakref.proxy(obj) # Avoid cyclic ref=0A=
except TypeError: pass=0A=
=0A=
def callback(o):=0A=
#print "attention !", dump, "est mourant !"=0A=
# This seems buggy since it is called when some objects are being =
destructed=0A=
try:=0A=
ob =3D obj=0A=
if ob:=0A=
if removeevent and hasevent(ob, event): removeevent(ob, event)=0A=
except: pass=0A=
if hi:=0A=
if type(event) is types.MethodType: event =3D WeakHiMethod(event, =
callback)=0A=
else: event =3D WeakHiFunc (event, =
callback)=0A=
else:=0A=
if type(event) is types.MethodType: event =3D WeakMethod(event, =
callback)=0A=
else: event =3D WeakFunc (event, =
callback)=0A=
=0A=
return event=0A=
=0A=
=0A=
class WrappedEvent: pass=0A=
=0A=
class WeakFunc(WrappedEvent):=0A=
def __init__(self, func, callback =3D None):=0A=
if callback: self.func =3D weakref.ref(func, callback)=0A=
else: self.func =3D weakref.ref(func)=0A=
=0A=
def original(self): return self.func()=0A=
=0A=
def __call__(self, *args): self.func()(*args)=0A=
=0A=
def __eq__(self, other):=0A=
return (self.func() =3D=3D other) or (isinstance(other, WeakFunc) =
and (self.func() =3D=3D other.func()))=0A=
=0A=
def __repr__(self): return "<WeakFunc for %s>" % self.func()=0A=
=0A=
class WeakMethod(WrappedEvent):=0A=
def __init__(self, method, callback =3D None):=0A=
if callback: self.obj =3D weakref.ref(method.im_self, callback)=0A=
else: self.obj =3D weakref.ref(method.im_self)=0A=
self.func =3D method.im_func=0A=
=0A=
def original(self):=0A=
obj =3D self.obj()=0A=
return new.instancemethod(self.func, obj, obj.__class__)=0A=
=0A=
def __call__(self, *args): self.func(self.obj(), *args)=0A=
=0A=
def __eq__(self, other):=0A=
return ((type(other) is types.MethodType) and (self.obj() is =
other.im_self) and (self.func is other.im_func)) or (isinstance(other, =
WeakMethod) and (self.obj() is other.obj()) and (self.func is =
other.func))=0A=
=0A=
def __repr__(self): return "<WeakMethod for %s>" % self.original()=0A=
=0A=
class HierarchyEvent:=0A=
def __call__(self, obj, attr, value, oldvalue):=0A=
if attr is ADDITION:=0A=
try: =0A=
if isinstance(obj, _EventObj_List): addevent_rec(value, =
self.original())=0A=
else: addevent_rec(value[1], =
self.original())=0A=
except NonEventableError: pass=0A=
elif attr is REMOVAL:=0A=
try: =0A=
if isinstance(obj, _EventObj_List): removeevent_rec(value, =
self.original())=0A=
else: removeevent_rec(value[1], =
self.original())=0A=
except NonEventableError: pass=0A=
=0A=
class WeakHiFunc(HierarchyEvent, WeakFunc):=0A=
def __call__(self, obj, attr, value, oldvalue):=0A=
HierarchyEvent.__call__(self, obj, attr, value, oldvalue)=0A=
WeakFunc.__call__(self, obj, attr, value, oldvalue)=0A=
=0A=
class WeakHiMethod(HierarchyEvent, WeakMethod):=0A=
def __call__(self, obj, attr, value, oldvalue):=0A=
HierarchyEvent.__call__(self, obj, attr, value, oldvalue)=0A=
WeakMethod.__call__(self, obj, attr, value, oldvalue)=0A=
=0A=
=0A=
# Mixin class used as base class for any with-event class.=0A=
class _EventObj:=0A=
stocks =3D []=0A=
def __setattr__(self, attr, value):=0A=
# Get the old value of the changing attrib.=0A=
oldvalue =3D getattr(self, attr, None)=0A=
if attr =3D=3D "__class__":=0A=
newclass =3D _create_class(self, value)=0A=
self._EventObj_stuff.clazz.__setattr__(self, "__class__", newclass)=0A=
self._EventObj_stuff.clazz =3D value=0A=
else:=0A=
# If a __setattr__ is defined for obj's old class, call it. Else, =
just set the attrib in obj's __dict__=0A=
if hasattr(self._EventObj_stuff.clazz, "__setattr__"): =
self._EventObj_stuff.clazz.__setattr__(self, attr, value)=0A=
else: =
self.__dict__[attr] =3D value=0A=
=0A=
# Comparison may fail=0A=
try:=0A=
if value =3D=3D oldvalue: return=0A=
except: pass=0A=
=0A=
# Call registered events, if needed=0A=
for event in self._EventObj_stuff.events:=0A=
event(self, attr, value, oldvalue)=0A=
=0A=
def __addevent__(self, event):=0A=
self._EventObj_stuff.events.append(event)=0A=
l =3D to_list(self)=0A=
if (not l is None) and (not l is self): addevent(l, event)=0A=
def __hasevent__(self, event =3D None):=0A=
return (event is None) or (self._EventObj_stuff.has_event(event))=0A=
def __removeevent__(self, event):=0A=
self._EventObj_stuff.remove_event(event)=0A=
l =3D to_list(self)=0A=
if (not l is None) and (not l is self): removeevent(l, event)=0A=
if len(self._EventObj_stuff.events) =3D=3D 0: self.__restore__()=0A=
=0A=
def __restore__(self):=0A=
# If no event left, reset obj to its original class.=0A=
if hasattr(self._EventObj_stuff.clazz, "__setattr__"):=0A=
self._EventObj_stuff.clazz.__setattr__(self, "__class__", =
self._EventObj_stuff.clazz)=0A=
else:=0A=
self.__class__ =3D self._EventObj_stuff.clazz=0A=
# And delete the _EventObj_stuff.=0A=
del self._EventObj_stuff=0A=
=0A=
# Called at pickling time=0A=
def __getstate__(self):=0A=
try:=0A=
dict =3D self._EventObj_stuff.clazz.__getstate__(self)=0A=
=0A=
if dict is self.__dict__: dict =3D dict.copy()=0A=
except: dict =3D self.__dict__.copy()=0A=
=0A=
try:=0A=
del dict["_EventObj_stuff"] # Remove what we have added.=0A=
if dict.has_key("children"): dict["children"] =3D =
list(dict["children"])=0A=
elif dict.has_key("items" ): dict["items" ] =3D =
list(dict["items" ])=0A=
except: pass # Not a dictionary ??=0A=
=0A=
return dict=0A=
=0A=
def __reduce__(self):=0A=
def rec_check(t):=0A=
if t is self.__class__: return self._EventObj_stuff.clazz=0A=
if type(t) is tuple: return tuple(map(rec_check, t))=0A=
return t=0A=
=0A=
red =3D self._EventObj_stuff.clazz.__reduce__(self)=0A=
=0A=
if len(red) =3D=3D 2: return red[0], tuple(map(rec_check, red[1]))=0A=
else: return red[0], tuple(map(rec_check, red[1])), =
red[2]=0A=
=0A=
class _EventObj_OldStyle(_EventObj):=0A=
def __deepcopy__(self, memo):=0A=
if hasattr(self._EventObj_stuff.clazz, "__deepcopy__"):=0A=
clone =3D self._EventObj_stuff.clazz.__deepcopy__(self, memo)=0A=
if clone.__class__ is self.__class__:=0A=
clone.__class__ =3D self._EventObj_stuff.clazz=0A=
if hasattr(clone, "_EventObj_stuff"): del clone._EventObj_stuff=0A=
return clone=0A=
else:=0A=
import copy=0A=
=0A=
if hasattr(self, '__getinitargs__'):=0A=
args =3D self.__getinitargs__()=0A=
copy._keep_alive(args, memo)=0A=
args =3D copy.deepcopy(args, memo)=0A=
y =3D apply(self._EventObj_stuff.clazz, args)=0A=
else:=0A=
y =3D copy._EmptyClass()=0A=
y.__class__ =3D self._EventObj_stuff.clazz=0A=
memo[id(self)] =3D y=0A=
if hasattr(self, '__getstate__'):=0A=
state =3D self.__getstate__()=0A=
copy._keep_alive(state, memo)=0A=
else:=0A=
state =3D self.__dict__=0A=
state =3D copy.deepcopy(state, memo)=0A=
if hasattr(y, '__setstate__'): y.__setstate__(state)=0A=
else: y.__dict__.update(state)=0A=
return y=0A=
=0A=
=0A=
class _EventObj_List(_EventObj):=0A=
def __added__ (self, value): self._EventObj_stuff(self, ADDITION, =
value, None)=0A=
def __removed__(self, value): self._EventObj_stuff(self, REMOVAL , =
value, None)=0A=
=0A=
def append(self, value):=0A=
self._EventObj_stuff.clazz.append(self, value)=0A=
self.__added__(value)=0A=
def insert(self, before, value):=0A=
self._EventObj_stuff.clazz.insert(self, before, value)=0A=
self.__added__(value)=0A=
def extend(self, list):=0A=
self._EventObj_stuff.clazz.extend(self, list)=0A=
for value in list: self.__added__(value)=0A=
=0A=
def remove(self, value):=0A=
self._EventObj_stuff.clazz.remove(self, value)=0A=
self.__removed__(value)=0A=
def pop(self, index =3D -1):=0A=
value =3D self._EventObj_stuff.clazz.pop(self, index)=0A=
self.__removed__(value)=0A=
return value=0A=
=0A=
def __setitem__(self, index, new):=0A=
old =3D self[index]=0A=
self._EventObj_stuff.clazz.__setitem__(self, index, new)=0A=
self.__removed__(old)=0A=
self.__added__ (new)=0A=
def __delitem__(self, index):=0A=
value =3D self[index]=0A=
self._EventObj_stuff.clazz.__delitem__(self, index)=0A=
self.__removed__(value)=0A=
def __setslice__(self, i, j, slice):=0A=
olds =3D self[i:j]=0A=
self._EventObj_stuff.clazz.__setslice__(self, i, j, slice)=0A=
for value in olds : self.__removed__(value)=0A=
for value in slice: self.__added__ (value)=0A=
def __delslice__(self, i, j):=0A=
olds =3D self[i:j]=0A=
self._EventObj_stuff.clazz.__delslice__(self, i, j)=0A=
for value in olds : self.__removed__(value)=0A=
def __iadd__(self, list):=0A=
self._EventObj_stuff.clazz.__iadd__(self, list)=0A=
for value in list: self.__added__(value)=0A=
return self=0A=
def __imul__(self, n):=0A=
olds =3D self[:]=0A=
self._EventObj_stuff.clazz.__imul__(self, n)=0A=
if n =3D=3D 0:=0A=
for value in olds: self.__removed__(value)=0A=
else:=0A=
for value in olds * (n - 1): self.__added__(value)=0A=
return self=0A=
=0A=
=0A=
class _EventObj_Dict(_EventObj):=0A=
def __added__ (self, key, value): self._EventObj_stuff(self, =
ADDITION, (key, value), None)=0A=
def __removed__(self, key, value): self._EventObj_stuff(self, REMOVAL =
, (key, value), None)=0A=
=0A=
def update(self, dict):=0A=
old =3D {}=0A=
for key, value in dict.items():=0A=
if self.has_key(key): old[key] =3D value=0A=
self._EventObj_stuff.clazz.update(self, dict)=0A=
for key, value in old .items(): self.__removed__(key, value)=0A=
for key, value in dict.items(): self.__added__ (key, value)=0A=
def popitem(self):=0A=
old =3D self._EventObj_stuff.clazz.popitem(self)=0A=
self.__removed__(old[0], old[1])=0A=
return old=0A=
def clear(self):=0A=
old =3D self.items()=0A=
self._EventObj_stuff.clazz.clear(self)=0A=
for key, value in old: self.__removed__(key, value)=0A=
=0A=
def __setitem__(self, key, new):=0A=
if self.has_key(key):=0A=
old =3D self[key]=0A=
self._EventObj_stuff.clazz.__setitem__(self, key, new)=0A=
self.__removed__(key, old)=0A=
else:=0A=
self._EventObj_stuff.clazz.__setitem__(self, key, new)=0A=
self.__added__(key, new)=0A=
def __delitem__(self, key):=0A=
value =3D self[key]=0A=
self._EventObj_stuff.clazz.__delitem__(self, key)=0A=
self.__removed__(key, value)=0A=
=0A=
# EventObj class for plain list (e.g. []) and plain dict :=0A=
=0A=
# EventObj stuff is not stored in the object's dict (because no such =
dict...)=0A=
# but in this dictionary :=0A=
#stuff_for_non_dyn_objs =3D weakref.WeakKeyDictionary()=0A=
stuff_for_non_dyn_objs =3D {}=0A=
=0A=
class _EventObj_PlainList(_EventObj_List, list):=0A=
__slots__ =3D []=0A=
=0A=
#__hash__ =3D object.__hash__ # Allows to hash it ! (needed to use =
"self" as a dict key)=0A=
=0A=
def _get_EventObj_stuff(self): return stuff_for_non_dyn_objs[id(self)]=0A=
def _set_EventObj_stuff(self, stuff): stuff_for_non_dyn_objs[id(self)] =
=3D stuff=0A=
_EventObj_stuff =3D property(_get_EventObj_stuff, _set_EventObj_stuff)=0A=
=0A=
def __restore__(self):=0A=
# If no event left, delete the _EventObj_stuff and reset obj to its =
original class.=0A=
# Bypass the _EventObj.__setattr__ (it would crash since =
_EventObj_stuff is no longer available after the class change)=0A=
self._EventObj_stuff.clazz.__setattr__(self, "__class__", =
self._EventObj_stuff.clazz)=0A=
del stuff_for_non_dyn_objs[id(self)]=0A=
=0A=
def __getstate__(self): return None=0A=
=0A=
_classes[list] =3D _EventObj_PlainList=0A=
=0A=
class _EventObj_PlainDict(_EventObj_Dict, dict):=0A=
__slots__ =3D []=0A=
=0A=
def _get_EventObj_stuff(self): return stuff_for_non_dyn_objs[id(self)]=0A=
def _set_EventObj_stuff(self, stuff): stuff_for_non_dyn_objs[id(self)] =
=3D stuff=0A=
_EventObj_stuff =3D property(_get_EventObj_stuff, _set_EventObj_stuff)=0A=
=0A=
def __restore__(self):=0A=
# If no event left, delete the _EventObj_stuff and reset obj to its =
original class.=0A=
# Bypass the _EventObj.__setattr__ (it would crash since =
_EventObj_stuff is no longer available after the class change)=0A=
self._EventObj_stuff.clazz.__setattr__(self, "__class__", =
self._EventObj_stuff.clazz)=0A=
del stuff_for_non_dyn_objs[id(self)]=0A=
=0A=
def __getstate__(self): return None=0A=
=0A=
_classes[dict] =3D _EventObj_PlainDict=0A=
=0A=
=0A=
# Hierarchy stuff :=0A=
=0A=
def addevent_rec(obj, event =3D dumper_event):=0A=
"""addevent_rec(obj, event =3D dumper_event)=0A=
Add event for obj, like addevent, but proceed recursively in all the =
hierarchy : if obj is a UserList/UserDict, event will be added to each =
instance obj contains, recursively.=0A=
If the hierarchy is changed, the newly added items will DO have the =
event, and the removed ones will no longuer have it."""=0A=
if not hasevent(obj, event): # Avoid problem with cyclic list/dict=0A=
# Wrap event in a hierarchy event=0A=
if not isinstance(event, HierarchyEvent): wevent =3D =
_wrap_event(obj, event, 1)=0A=
=0A=
addevent(obj, wevent)=0A=
=0A=
for o in to_content(obj):=0A=
try: addevent_rec(o, event)=0A=
except NonEventableError: pass=0A=
=0A=
def removeevent_rec(obj, event =3D dumper_event):=0A=
"""removeevent_rec(obj, event =3D dumper_event)=0A=
Remove event for obj, like removeevent, but proceed recursively."""=0A=
if hasevent(obj, event): # Avoid problem with cyclic list/dict=0A=
removeevent(obj, event)=0A=
=0A=
for o in to_content(obj):=0A=
if isinstance(o, _EventObj): removeevent_rec(o, event)=0A=
=0A=
def change_class(obj, newclass):=0A=
"""Change the class of OBJ to NEWCLASS, but keep the events it may =
have."""=0A=
events =3D obj._EventObj_stuff.events[:]=0A=
for event in events: removeevent(obj, event)=0A=
obj.__class__ =3D newclass=0A=
for event in events: addevent(obj, event)=0A=
=0A=
------=_NextPart_000_0149_01C267C9.4E13C0C0--