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 # FIXME: I have magic numbers! 40 return "<COWDict Level: %i Current Keys: %i>" % (cls.__count__, len(cls.__dict__) - 3) 41 42 __repr__ = __str__ 43 44 def cow(cls): 45 class C(cls): 46 __count__ = cls.__count__ + 1 47 48 return C 49 50 copy = cow 51 __call__ = cow 52 53 def __setitem__(cls, key, value): 54 if value is not None and not isinstance(value, ImmutableTypes): 55 if not isinstance(value, COWMeta): 56 cls.__hasmutable__ = True 57 key += MUTABLE 58 setattr(cls, key, value) 59 60 def __getmutable__(cls, key, readonly=False): 61 nkey = key + MUTABLE 62 try: 63 return cls.__dict__[nkey] 64 except KeyError: 65 pass 66 67 value = getattr(cls, nkey) 68 if readonly: 69 return value 70 71 if not cls.__warn__ is False and not isinstance(value, COWMeta): 72 print("Warning: Doing a copy because %s is a mutable type." % key, file=cls.__warn__) 73 try: 74 value = value.copy() 75 except AttributeError as e: 76 value = copy.copy(value) 77 setattr(cls, nkey, value) 78 return value 79 80 __getmarker__ = [] 81 82 def __getreadonly__(cls, key, default=__getmarker__): 83 """ 84 Get a value (even if mutable) which you promise not to change. 85 """ 86 return cls.__getitem__(key, default, True) 87 88 def __getitem__(cls, key, default=__getmarker__, readonly=False): 89 try: 90 try: 91 value = getattr(cls, key) 92 except AttributeError: 93 value = cls.__getmutable__(key, readonly) 94 95 # This is for values which have been deleted 96 if value is cls.__marker__: 97 raise AttributeError("key %s does not exist." % key) 98 99 return value 100 except AttributeError as e: 101 if not default is cls.__getmarker__: 102 return default 103 104 raise KeyError(str(e)) 105 106 def __delitem__(cls, key): 107 cls.__setitem__(key, cls.__marker__) 108 109 def __revertitem__(cls, key): 110 if key not in cls.__dict__: 111 key += MUTABLE 112 delattr(cls, key) 113 114 def __contains__(cls, key): 115 return cls.has_key(key) 116 117 def has_key(cls, key): 118 value = cls.__getreadonly__(key, cls.__marker__) 119 if value is cls.__marker__: 120 return False 121 return True 122 123 def iter(cls, type, readonly=False): 124 for key in dir(cls): 125 if key.startswith("__"): 126 continue 127 128 if key.endswith(MUTABLE): 129 key = key[:-len(MUTABLE)] 130 131 if type == "keys": 132 yield key 133 134 try: 135 if readonly: 136 value = cls.__getreadonly__(key) 137 else: 138 value = cls[key] 139 except KeyError: 140 continue 141 142 if type == "values": 143 yield value 144 if type == "items": 145 yield (key, value) 146 return 147 148 def iterkeys(cls): 149 return cls.iter("keys") 150 151 def itervalues(cls, readonly=False): 152 if not cls.__warn__ is False and cls.__hasmutable__ and readonly is False: 153 print("Warning: If you aren't going to change any of the values call with True.", file=cls.__warn__) 154 return cls.iter("values", readonly) 155 156 def iteritems(cls, readonly=False): 157 if not cls.__warn__ is False and cls.__hasmutable__ and readonly is False: 158 print("Warning: If you aren't going to change any of the values call with True.", file=cls.__warn__) 159 return cls.iter("items", readonly) 160 161 162class COWSetMeta(COWDictMeta): 163 def __str__(cls): 164 # FIXME: I have magic numbers! 165 return "<COWSet Level: %i Current Keys: %i>" % (cls.__count__, len(cls.__dict__) - 3) 166 167 __repr__ = __str__ 168 169 def cow(cls): 170 class C(cls): 171 __count__ = cls.__count__ + 1 172 173 return C 174 175 def add(cls, value): 176 COWDictMeta.__setitem__(cls, repr(hash(value)), value) 177 178 def remove(cls, value): 179 COWDictMeta.__delitem__(cls, repr(hash(value))) 180 181 def __in__(cls, value): 182 return repr(hash(value)) in COWDictMeta 183 184 def iterkeys(cls): 185 raise TypeError("sets don't have keys") 186 187 def iteritems(cls): 188 raise TypeError("sets don't have 'items'") 189 190 191# These are the actual classes you use! 192class COWDictBase(metaclass=COWDictMeta): 193 __count__ = 0 194 195 196class COWSetBase(metaclass=COWSetMeta): 197 __count__ = 0 198