From 629e9df5c7384c5470e37470b0bfe7657d61a8d3 Mon Sep 17 00:00:00 2001 From: Tim Ansell Date: Mon, 24 Apr 2006 09:54:32 +0000 Subject: Delete now works. Other cleanup and fixes. --- lib/bb/COW.py | 243 +++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 198 insertions(+), 45 deletions(-) (limited to 'lib/bb/COW.py') diff --git a/lib/bb/COW.py b/lib/bb/COW.py index d34dd5f24..826d435f9 100644 --- a/lib/bb/COW.py +++ b/lib/bb/COW.py @@ -1,9 +1,11 @@ # ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- """ -This is a copy on write dictionary which abuses classes to be nice and fast. +This is a copy on write dictionary and set which abuses classes to try and be nice and fast. -Please Note: Be careful when using mutable types (ie Dict and Lists). The copy on write stuff only kicks in on Assignment. +Please Note: + Be careful when using mutable types (ie Dict and Lists) - operations involving these are SLOW. + Assign a file to __warn__ to get warnings about slow operations. """ from inspect import getmro @@ -21,57 +23,95 @@ types.ImmutableTypes = tuple([ \ sets.ImmutableSet] + \ list(types.StringTypes)) -MUTABLE = "_mutable__" +MUTABLE = "__mutable__" + +class COWMeta(type): + pass -class COWDictMeta(type): +class COWDictMeta(COWMeta): + __warn__ = False + __hasmutable__ = False + __marker__ = tuple() + def __str__(cls): - return "" % (cls.__count__, len(cls.__dict__)) + # FIXME: I have magic numbers! + return "" % (cls.__count__, len(cls.__dict__) - 3) __repr__ = __str__ def cow(cls): class C(cls): __count__ = cls.__count__ + 1 return C + copy = cow + __call__ = cow def __setitem__(cls, key, value): if not isinstance(value, types.ImmutableTypes): + if not isinstance(value, COWMeta): + cls.__hasmutable__ = True key += MUTABLE setattr(cls, key, value) - def __getmutable__(cls, key): - """ - This gets called when you do a "o.b" and b doesn't exist on o. - - IE When the type is mutable. - """ + def __getmutable__(cls, key, readonly=False): nkey = key + MUTABLE - # Do we already have a copy of this on us? - if nkey in cls.__dict__: + try: return cls.__dict__[nkey] + except KeyError: + pass - r = getattr(cls, nkey) + value = getattr(cls, nkey) + if readonly: + return value + + if not cls.__warn__ is False and not isinstance(value, COWMeta): + print >> cls.__warn__, "Warning: Doing a copy because %s is a mutable type." % key try: - r = r.copy() - except NameError, e: - r = copy.copy(r) - setattr(cls, nkey, r) - return r + value = value.copy() + except AttributeError, e: + value = copy.copy(value) + setattr(cls, nkey, value) + return value - def __getitem__(cls, key): + __getmarker__ = [] + def __getreadonly__(cls, key, default=__getmarker__): + """\ + Get a value (even if mutable) which you promise not to change. + """ + return cls.__getitem__(key, default, True) + + def __getitem__(cls, key, default=__getmarker__, readonly=False): try: try: - return getattr(cls, key) + value = getattr(cls, key) except AttributeError: - # Degrade to slow mode if type is not immutable, - # If the type is also a COW this will be cheap anyway - return cls.__getmutable__(key) + value = cls.__getmutable__(key, readonly) + + # This is for values which have been deleted + if value is cls.__marker__: + raise AttributeError("key %s does not exist." % key) + + return value except AttributeError, e: - raise KeyError(e) + if not default is cls.__getmarker__: + return default + + raise KeyError(str(e)) + + def __delitem__(cls, key): + cls.__setitem__(key, cls.__marker__) + + def __revertitem__(cls, key): + if not cls.__dict__.has_key(key): + key += MUTABLE + delattr(cls, key) def has_key(cls, key): - return (hasattr(cls, key) or hasattr(cls, key + MUTABLE)) + value = cls.__getreadonly__(key, cls.__marker__) + if value is cls.__marker__: + return False + return True - def iter(cls, type): + def iter(cls, type, readonly=False): for key in dir(cls): if key.startswith("__"): continue @@ -81,23 +121,36 @@ class COWDictMeta(type): if type == "keys": yield key + + try: + if readonly: + value = cls.__getreadonly__(key) + else: + value = cls[key] + except KeyError: + continue + if type == "values": - yield cls[key] + yield value if type == "items": - yield (key, cls[key]) + yield (key, value) raise StopIteration() def iterkeys(cls): return cls.iter("keys") - def itervalues(cls): - return cls.iter("values") - def iteritems(cls): - return cls.iter("items") - copy = cow + def itervalues(cls, readonly=False): + if not cls.__warn__ is False and cls.__hasmutable__ and readonly is False: + print >> cls.__warn__, "Warning: If you arn't going to change any of the values call with True." + return cls.iter("values", readonly) + def iteritems(cls, readonly=False): + if not cls.__warn__ is False and cls.__hasmutable__ and readonly is False: + print >> cls.__warn__, "Warning: If you arn't going to change any of the values call with True." + return cls.iter("items", readonly) class COWSetMeta(COWDictMeta): def __str__(cls): - return "" % (cls.__count__, len(cls.__dict__)) + # FIXME: I have magic numbers! + return "" % (cls.__count__, len(cls.__dict__) -3) __repr__ = __str__ def cow(cls): @@ -105,31 +158,51 @@ class COWSetMeta(COWDictMeta): __count__ = cls.__count__ + 1 return C - def add(cls, key, value): - cls.__setitem__(hash(key), value) + def add(cls, value): + COWDictMeta.__setitem__(cls, repr(hash(value)), value) + + def remove(cls, value): + COWDictMeta.__delitem__(cls, repr(hash(value))) + + def __in__(cls, value): + return COWDictMeta.has_key(repr(hash(value))) + + def iterkeys(cls): + raise TypeError("sets don't have keys") + + def iteritems(cls): + raise TypeError("sets don't have 'items'") +# These are the actual classes you use! class COWDictBase(object): __metaclass__ = COWDictMeta __count__ = 0 +class COWSetBase(object): + __metaclass__ = COWSetMeta + __count__ = 0 if __name__ == "__main__": - a = COWDictBase.copy() - print a + import sys + COWDictBase.__warn__ = sys.stderr + a = COWDictBase() + print "a", a + a['a'] = 'a' + a['b'] = 'b' a['dict'] = {} - print "ha" - hasattr(a, "a") - print "ha" - b = a.copy() - print b - b['b'] = 'b' + print "b", b + b['c'] = 'b' + + print + print "a", a for x in a.iteritems(): print x print "--" + print "b", b for x in b.iteritems(): print x print @@ -137,9 +210,11 @@ if __name__ == "__main__": b['dict']['a'] = 'b' b['a'] = 'c' + print "a", a for x in a.iteritems(): print x print "--" + print "b", b for x in b.iteritems(): print x print @@ -149,4 +224,82 @@ if __name__ == "__main__": except KeyError, e: print "Okay!" + a['set'] = COWSetBase() + a['set'].add("o1") + a['set'].add("o1") + a['set'].add("o2") + print "a", a + for x in a['set'].itervalues(): + print x + print "--" + print "b", b + for x in b['set'].itervalues(): + print x + print + + b['set'].add('o3') + + print "a", a + for x in a['set'].itervalues(): + print x + print "--" + print "b", b + for x in b['set'].itervalues(): + print x + print + + a['set2'] = set() + a['set2'].add("o1") + a['set2'].add("o1") + a['set2'].add("o2") + + print "a", a + for x in a.iteritems(): + print x + print "--" + print "b", b + for x in b.iteritems(readonly=True): + print x + print + + del b['b'] + try: + print b['b'] + except KeyError: + print "Yay! deleted key raises error" + + if b.has_key('b'): + print "Boo!" + else: + print "Yay - has_key with delete works!" + + print "a", a + for x in a.iteritems(): + print x + print "--" + print "b", b + for x in b.iteritems(readonly=True): + print x + print + + b.__revertitem__('b') + + print "a", a + for x in a.iteritems(): + print x + print "--" + print "b", b + for x in b.iteritems(readonly=True): + print x + print + + b.__revertitem__('dict') + print "a", a + for x in a.iteritems(): + print x + print "--" + print "b", b + for x in b.iteritems(readonly=True): + print x + print -- cgit 1.2.3-korg