COW.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  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. # Please Note:
  7. # Be careful when using mutable types (ie Dict and Lists) - operations involving these are SLOW.
  8. # Assign a file to __warn__ to get warnings about slow operations.
  9. #
  10. import copy
  11. ImmutableTypes = (
  12. bool,
  13. complex,
  14. float,
  15. int,
  16. tuple,
  17. frozenset,
  18. str
  19. )
  20. MUTABLE = "__mutable__"
  21. class COWMeta(type):
  22. pass
  23. class COWDictMeta(COWMeta):
  24. __warn__ = False
  25. __hasmutable__ = False
  26. __marker__ = tuple()
  27. def __str__(cls):
  28. # FIXME: I have magic numbers!
  29. return "<COWDict Level: %i Current Keys: %i>" % (cls.__count__, len(cls.__dict__) - 3)
  30. __repr__ = __str__
  31. def cow(cls):
  32. class C(cls):
  33. __count__ = cls.__count__ + 1
  34. return C
  35. copy = cow
  36. __call__ = cow
  37. def __setitem__(cls, key, value):
  38. if value is not None and not isinstance(value, ImmutableTypes):
  39. if not isinstance(value, COWMeta):
  40. cls.__hasmutable__ = True
  41. key += MUTABLE
  42. setattr(cls, key, value)
  43. def __getmutable__(cls, key, readonly=False):
  44. nkey = key + MUTABLE
  45. try:
  46. return cls.__dict__[nkey]
  47. except KeyError:
  48. pass
  49. value = getattr(cls, nkey)
  50. if readonly:
  51. return value
  52. if not cls.__warn__ is False and not isinstance(value, COWMeta):
  53. print("Warning: Doing a copy because %s is a mutable type." % key, file=cls.__warn__)
  54. try:
  55. value = value.copy()
  56. except AttributeError as e:
  57. value = copy.copy(value)
  58. setattr(cls, nkey, value)
  59. return value
  60. __getmarker__ = []
  61. def __getreadonly__(cls, key, default=__getmarker__):
  62. """
  63. Get a value (even if mutable) which you promise not to change.
  64. """
  65. return cls.__getitem__(key, default, True)
  66. def __getitem__(cls, key, default=__getmarker__, readonly=False):
  67. try:
  68. try:
  69. value = getattr(cls, key)
  70. except AttributeError:
  71. value = cls.__getmutable__(key, readonly)
  72. # This is for values which have been deleted
  73. if value is cls.__marker__:
  74. raise AttributeError("key %s does not exist." % key)
  75. return value
  76. except AttributeError as e:
  77. if not default is cls.__getmarker__:
  78. return default
  79. raise KeyError(str(e))
  80. def __delitem__(cls, key):
  81. cls.__setitem__(key, cls.__marker__)
  82. def __revertitem__(cls, key):
  83. if key not in cls.__dict__:
  84. key += MUTABLE
  85. delattr(cls, key)
  86. def __contains__(cls, key):
  87. return cls.has_key(key)
  88. def has_key(cls, key):
  89. value = cls.__getreadonly__(key, cls.__marker__)
  90. if value is cls.__marker__:
  91. return False
  92. return True
  93. def iter(cls, type, readonly=False):
  94. for key in dir(cls):
  95. if key.startswith("__"):
  96. continue
  97. if key.endswith(MUTABLE):
  98. key = key[:-len(MUTABLE)]
  99. if type == "keys":
  100. yield key
  101. try:
  102. if readonly:
  103. value = cls.__getreadonly__(key)
  104. else:
  105. value = cls[key]
  106. except KeyError:
  107. continue
  108. if type == "values":
  109. yield value
  110. if type == "items":
  111. yield (key, value)
  112. return
  113. def iterkeys(cls):
  114. return cls.iter("keys")
  115. def itervalues(cls, readonly=False):
  116. if not cls.__warn__ is False and cls.__hasmutable__ and readonly is False:
  117. print("Warning: If you aren't going to change any of the values call with True.", file=cls.__warn__)
  118. return cls.iter("values", readonly)
  119. def iteritems(cls, readonly=False):
  120. if not cls.__warn__ is False and cls.__hasmutable__ and readonly is False:
  121. print("Warning: If you aren't going to change any of the values call with True.", file=cls.__warn__)
  122. return cls.iter("items", readonly)
  123. class COWSetMeta(COWDictMeta):
  124. def __str__(cls):
  125. # FIXME: I have magic numbers!
  126. return "<COWSet Level: %i Current Keys: %i>" % (cls.__count__, len(cls.__dict__) - 3)
  127. __repr__ = __str__
  128. def cow(cls):
  129. class C(cls):
  130. __count__ = cls.__count__ + 1
  131. return C
  132. def add(cls, value):
  133. COWDictMeta.__setitem__(cls, repr(hash(value)), value)
  134. def remove(cls, value):
  135. COWDictMeta.__delitem__(cls, repr(hash(value)))
  136. def __in__(cls, value):
  137. return repr(hash(value)) in COWDictMeta
  138. def iterkeys(cls):
  139. raise TypeError("sets don't have keys")
  140. def iteritems(cls):
  141. raise TypeError("sets don't have 'items'")
  142. # These are the actual classes you use!
  143. class COWDictBase(metaclass=COWDictMeta):
  144. __count__ = 0
  145. class COWSetBase(metaclass=COWSetMeta):
  146. __count__ = 0