fork download
  1. from itertools import chain
  2. from weakref import WeakSet
  3. import operator
  4.  
  5. class Dynamic:
  6. @classmethod
  7. def args(self, *args, **kw):
  8. return lambda f: Dynamic(f, *args, **kw)
  9.  
  10. def __init__(self, f, *args, **kw):
  11. self._ismodified = True
  12. self._cache = None
  13.  
  14. self.thunk = lambda: None, (), {}
  15. self.children = WeakSet() # тут надо немного посидеть с дебагером
  16. self.parents = set() # и убедиться что нет утечек памяти
  17.  
  18. self.update(f, *args, **kw)
  19.  
  20. def update(self, f, *args, **kw):
  21. try:
  22. thunk = f.thunk
  23. parents = set(f.parents)
  24.  
  25. except AttributeError:
  26. thunk = f, args, kw
  27. parents = set(chain(args, kw.values()))
  28.  
  29. if self in parents: # возможно это можно обработать лучше
  30. parents.discard(self)
  31. oldself = Dynamic(self) # fork self
  32.  
  33. f, args, kw = thunk
  34. args = list(args)
  35.  
  36. for i, a in enumerate(args): # patch thunk
  37. if a is self:
  38. args[i] = oldself
  39.  
  40. for k, v in kw.items():
  41. if v is self:
  42. kw[k] = oldself
  43.  
  44. thunk = f, tuple(args), kw
  45. parents.add(oldself)
  46.  
  47. for p in parents:
  48. p.children.add(self)
  49.  
  50. for p in self.parents:
  51. p.children.discard(self)
  52.  
  53. self.thunk = thunk
  54. self.parents = parents
  55. self.setmodified()
  56.  
  57. def setmodified(self):
  58. if not self._ismodified:
  59. self._ismodified = True
  60.  
  61. for c in self.children:
  62. c.setmodified()
  63.  
  64. def compose(self, f, d):
  65. return Dynamic(f, self, d)
  66.  
  67. def __get__(self, obj, type=None):
  68. return self
  69.  
  70. def __set__(self, obj, d):
  71. self.update(d)
  72.  
  73. def __call__(self):
  74. if self._ismodified:
  75. self._ismodified = False
  76. f, args, kw = self.thunk
  77.  
  78. self._cache = f(
  79. *[d() for d in args],
  80. **{k: v() for k, v in kw}
  81. )
  82.  
  83. return self._cache
  84.  
  85. def __hash__(self):
  86. return id(self)
  87.  
  88. def __repr__(self):
  89. return 'Dynamic({}:{})'.format(hash(self), repr(self()))
  90.  
  91. def __str__(self):
  92. return str(self())
  93.  
  94. def __add__(self, d):
  95. return self.compose(operator.add, d)
  96.  
  97. def __mul__(self, d):
  98. return self.compose(operator.mul, d)
  99.  
  100. def __truediv__(self, d):
  101. return self.compose(operator.div, d)
  102.  
  103. def __iadd__(self, d):
  104. self.update(self + d)
  105. return self
  106.  
  107. def __imul__(self, d):
  108. self.update(self * d)
  109. return self
  110.  
  111. def __itruediv__(self, d):
  112. self.update(self / d)
  113. return self
  114. # и т.д...
  115. # это всё конечно можно сгенерировать на этапе создания экземпляра класса,
  116. # но мне влом.
  117.  
  118. x = Dynamic(lambda: 2)
  119. a = Dynamic(lambda: 5)
  120. b = Dynamic(lambda: 2)
  121.  
  122. c = a * b
  123. print('c:', c)
  124.  
  125. a += x
  126. print('c:', c)
  127.  
  128. x.update(lambda: 0)
  129. print('c:', c)
  130.  
  131. @Dynamic.args(c, b)
  132. def d(c, b):
  133. return c / b
  134.  
  135. print('d:', d)
  136.  
  137. class Table:
  138. q = Dynamic(lambda: 9)
  139. w = Dynamic(lambda: 8)
  140. e = q + w
  141.  
  142. z = Table()
  143. print('z.e', z.e)
  144.  
  145. z.q = Dynamic(lambda: 0)
  146. print('z.e', z.e)
  147.  
Success #stdin #stdout 0.03s 9440KB
stdin
Standard input is empty
stdout
c: 10
c: 14
c: 10
d: 5.0
z.e 17
z.e 8