[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--