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