1eb8dc403SDave Cobbley# 2eb8dc403SDave Cobbley# This is a copy on write dictionary and set which abuses classes to try and be nice and fast. 3eb8dc403SDave Cobbley# 4eb8dc403SDave Cobbley# Copyright (C) 2006 Tim Ansell 5eb8dc403SDave Cobbley# 6*92b42cb3SPatrick Williams# SPDX-License-Identifier: GPL-2.0-only 7*92b42cb3SPatrick Williams# 8eb8dc403SDave Cobbley# Please Note: 9eb8dc403SDave Cobbley# Be careful when using mutable types (ie Dict and Lists) - operations involving these are SLOW. 10eb8dc403SDave Cobbley# Assign a file to __warn__ to get warnings about slow operations. 11eb8dc403SDave Cobbley# 12eb8dc403SDave Cobbley 13eb8dc403SDave Cobbley 14eb8dc403SDave Cobbleyimport copy 15c9f7865aSAndrew Geissler 16eb8dc403SDave CobbleyImmutableTypes = ( 17eb8dc403SDave Cobbley bool, 18eb8dc403SDave Cobbley complex, 19eb8dc403SDave Cobbley float, 20eb8dc403SDave Cobbley int, 21eb8dc403SDave Cobbley tuple, 22eb8dc403SDave Cobbley frozenset, 23eb8dc403SDave Cobbley str 24eb8dc403SDave Cobbley) 25eb8dc403SDave Cobbley 26eb8dc403SDave CobbleyMUTABLE = "__mutable__" 27eb8dc403SDave Cobbley 28c9f7865aSAndrew Geissler 29eb8dc403SDave Cobbleyclass COWMeta(type): 30eb8dc403SDave Cobbley pass 31eb8dc403SDave Cobbley 32c9f7865aSAndrew Geissler 33eb8dc403SDave Cobbleyclass COWDictMeta(COWMeta): 34eb8dc403SDave Cobbley __warn__ = False 35eb8dc403SDave Cobbley __hasmutable__ = False 36eb8dc403SDave Cobbley __marker__ = tuple() 37eb8dc403SDave Cobbley 38eb8dc403SDave Cobbley def __str__(cls): 39eb8dc403SDave Cobbley # FIXME: I have magic numbers! 40eb8dc403SDave Cobbley return "<COWDict Level: %i Current Keys: %i>" % (cls.__count__, len(cls.__dict__) - 3) 41c9f7865aSAndrew Geissler 42eb8dc403SDave Cobbley __repr__ = __str__ 43eb8dc403SDave Cobbley 44eb8dc403SDave Cobbley def cow(cls): 45eb8dc403SDave Cobbley class C(cls): 46eb8dc403SDave Cobbley __count__ = cls.__count__ + 1 47c9f7865aSAndrew Geissler 48eb8dc403SDave Cobbley return C 49c9f7865aSAndrew Geissler 50eb8dc403SDave Cobbley copy = cow 51eb8dc403SDave Cobbley __call__ = cow 52eb8dc403SDave Cobbley 53eb8dc403SDave Cobbley def __setitem__(cls, key, value): 54eb8dc403SDave Cobbley if value is not None and not isinstance(value, ImmutableTypes): 55eb8dc403SDave Cobbley if not isinstance(value, COWMeta): 56eb8dc403SDave Cobbley cls.__hasmutable__ = True 57eb8dc403SDave Cobbley key += MUTABLE 58eb8dc403SDave Cobbley setattr(cls, key, value) 59eb8dc403SDave Cobbley 60eb8dc403SDave Cobbley def __getmutable__(cls, key, readonly=False): 61eb8dc403SDave Cobbley nkey = key + MUTABLE 62eb8dc403SDave Cobbley try: 63eb8dc403SDave Cobbley return cls.__dict__[nkey] 64eb8dc403SDave Cobbley except KeyError: 65eb8dc403SDave Cobbley pass 66eb8dc403SDave Cobbley 67eb8dc403SDave Cobbley value = getattr(cls, nkey) 68eb8dc403SDave Cobbley if readonly: 69eb8dc403SDave Cobbley return value 70eb8dc403SDave Cobbley 71eb8dc403SDave Cobbley if not cls.__warn__ is False and not isinstance(value, COWMeta): 72eb8dc403SDave Cobbley print("Warning: Doing a copy because %s is a mutable type." % key, file=cls.__warn__) 73eb8dc403SDave Cobbley try: 74eb8dc403SDave Cobbley value = value.copy() 75eb8dc403SDave Cobbley except AttributeError as e: 76eb8dc403SDave Cobbley value = copy.copy(value) 77eb8dc403SDave Cobbley setattr(cls, nkey, value) 78eb8dc403SDave Cobbley return value 79eb8dc403SDave Cobbley 80eb8dc403SDave Cobbley __getmarker__ = [] 81c9f7865aSAndrew Geissler 82eb8dc403SDave Cobbley def __getreadonly__(cls, key, default=__getmarker__): 83c9f7865aSAndrew Geissler """ 84eb8dc403SDave Cobbley Get a value (even if mutable) which you promise not to change. 85eb8dc403SDave Cobbley """ 86eb8dc403SDave Cobbley return cls.__getitem__(key, default, True) 87eb8dc403SDave Cobbley 88eb8dc403SDave Cobbley def __getitem__(cls, key, default=__getmarker__, readonly=False): 89eb8dc403SDave Cobbley try: 90eb8dc403SDave Cobbley try: 91eb8dc403SDave Cobbley value = getattr(cls, key) 92eb8dc403SDave Cobbley except AttributeError: 93eb8dc403SDave Cobbley value = cls.__getmutable__(key, readonly) 94eb8dc403SDave Cobbley 95eb8dc403SDave Cobbley # This is for values which have been deleted 96eb8dc403SDave Cobbley if value is cls.__marker__: 97eb8dc403SDave Cobbley raise AttributeError("key %s does not exist." % key) 98eb8dc403SDave Cobbley 99eb8dc403SDave Cobbley return value 100eb8dc403SDave Cobbley except AttributeError as e: 101eb8dc403SDave Cobbley if not default is cls.__getmarker__: 102eb8dc403SDave Cobbley return default 103eb8dc403SDave Cobbley 104eb8dc403SDave Cobbley raise KeyError(str(e)) 105eb8dc403SDave Cobbley 106eb8dc403SDave Cobbley def __delitem__(cls, key): 107eb8dc403SDave Cobbley cls.__setitem__(key, cls.__marker__) 108eb8dc403SDave Cobbley 109eb8dc403SDave Cobbley def __revertitem__(cls, key): 110eb8dc403SDave Cobbley if key not in cls.__dict__: 111eb8dc403SDave Cobbley key += MUTABLE 112eb8dc403SDave Cobbley delattr(cls, key) 113eb8dc403SDave Cobbley 114eb8dc403SDave Cobbley def __contains__(cls, key): 115eb8dc403SDave Cobbley return cls.has_key(key) 116eb8dc403SDave Cobbley 117eb8dc403SDave Cobbley def has_key(cls, key): 118eb8dc403SDave Cobbley value = cls.__getreadonly__(key, cls.__marker__) 119eb8dc403SDave Cobbley if value is cls.__marker__: 120eb8dc403SDave Cobbley return False 121eb8dc403SDave Cobbley return True 122eb8dc403SDave Cobbley 123eb8dc403SDave Cobbley def iter(cls, type, readonly=False): 124eb8dc403SDave Cobbley for key in dir(cls): 125eb8dc403SDave Cobbley if key.startswith("__"): 126eb8dc403SDave Cobbley continue 127eb8dc403SDave Cobbley 128eb8dc403SDave Cobbley if key.endswith(MUTABLE): 129eb8dc403SDave Cobbley key = key[:-len(MUTABLE)] 130eb8dc403SDave Cobbley 131eb8dc403SDave Cobbley if type == "keys": 132eb8dc403SDave Cobbley yield key 133eb8dc403SDave Cobbley 134eb8dc403SDave Cobbley try: 135eb8dc403SDave Cobbley if readonly: 136eb8dc403SDave Cobbley value = cls.__getreadonly__(key) 137eb8dc403SDave Cobbley else: 138eb8dc403SDave Cobbley value = cls[key] 139eb8dc403SDave Cobbley except KeyError: 140eb8dc403SDave Cobbley continue 141eb8dc403SDave Cobbley 142eb8dc403SDave Cobbley if type == "values": 143eb8dc403SDave Cobbley yield value 144eb8dc403SDave Cobbley if type == "items": 145eb8dc403SDave Cobbley yield (key, value) 1461a4b7ee2SBrad Bishop return 147eb8dc403SDave Cobbley 148eb8dc403SDave Cobbley def iterkeys(cls): 149eb8dc403SDave Cobbley return cls.iter("keys") 150c9f7865aSAndrew Geissler 151eb8dc403SDave Cobbley def itervalues(cls, readonly=False): 152eb8dc403SDave Cobbley if not cls.__warn__ is False and cls.__hasmutable__ and readonly is False: 153c9f7865aSAndrew Geissler print("Warning: If you aren't going to change any of the values call with True.", file=cls.__warn__) 154eb8dc403SDave Cobbley return cls.iter("values", readonly) 155c9f7865aSAndrew Geissler 156eb8dc403SDave Cobbley def iteritems(cls, readonly=False): 157eb8dc403SDave Cobbley if not cls.__warn__ is False and cls.__hasmutable__ and readonly is False: 158c9f7865aSAndrew Geissler print("Warning: If you aren't going to change any of the values call with True.", file=cls.__warn__) 159eb8dc403SDave Cobbley return cls.iter("items", readonly) 160eb8dc403SDave Cobbley 161c9f7865aSAndrew Geissler 162eb8dc403SDave Cobbleyclass COWSetMeta(COWDictMeta): 163eb8dc403SDave Cobbley def __str__(cls): 164eb8dc403SDave Cobbley # FIXME: I have magic numbers! 165eb8dc403SDave Cobbley return "<COWSet Level: %i Current Keys: %i>" % (cls.__count__, len(cls.__dict__) - 3) 166c9f7865aSAndrew Geissler 167eb8dc403SDave Cobbley __repr__ = __str__ 168eb8dc403SDave Cobbley 169eb8dc403SDave Cobbley def cow(cls): 170eb8dc403SDave Cobbley class C(cls): 171eb8dc403SDave Cobbley __count__ = cls.__count__ + 1 172c9f7865aSAndrew Geissler 173eb8dc403SDave Cobbley return C 174eb8dc403SDave Cobbley 175eb8dc403SDave Cobbley def add(cls, value): 176eb8dc403SDave Cobbley COWDictMeta.__setitem__(cls, repr(hash(value)), value) 177eb8dc403SDave Cobbley 178eb8dc403SDave Cobbley def remove(cls, value): 179eb8dc403SDave Cobbley COWDictMeta.__delitem__(cls, repr(hash(value))) 180eb8dc403SDave Cobbley 181eb8dc403SDave Cobbley def __in__(cls, value): 182eb8dc403SDave Cobbley return repr(hash(value)) in COWDictMeta 183eb8dc403SDave Cobbley 184eb8dc403SDave Cobbley def iterkeys(cls): 185eb8dc403SDave Cobbley raise TypeError("sets don't have keys") 186eb8dc403SDave Cobbley 187eb8dc403SDave Cobbley def iteritems(cls): 188eb8dc403SDave Cobbley raise TypeError("sets don't have 'items'") 189eb8dc403SDave Cobbley 190c9f7865aSAndrew Geissler 191eb8dc403SDave Cobbley# These are the actual classes you use! 192c9f7865aSAndrew Geisslerclass COWDictBase(metaclass=COWDictMeta): 193eb8dc403SDave Cobbley __count__ = 0 194eb8dc403SDave Cobbley 195c9f7865aSAndrew Geissler 196c9f7865aSAndrew Geisslerclass COWSetBase(metaclass=COWSetMeta): 197eb8dc403SDave Cobbley __count__ = 0 198