/pypy/objspace/thunk.py

https://github.com/alemacgo/pypy
Python | 223 lines | 188 code | 17 blank | 18 comment | 25 complexity | ca4dc3ca5f37d86a7aea662c73b4e5c6 MD5 | raw file
  1. """Example usage:
  2. $ py.py -o thunk
  3. >>> from __pypy__ import thunk, lazy, become
  4. >>> def f():
  5. ... print 'computing...'
  6. ... return 6*7
  7. ...
  8. >>> x = thunk(f)
  9. >>> x
  10. computing...
  11. 42
  12. >>> x
  13. 42
  14. >>> y = thunk(f)
  15. >>> type(y)
  16. computing...
  17. <pypy type 'int'>
  18. >>> @lazy
  19. ... def g(n):
  20. ... print 'computing...'
  21. ... return n + 5
  22. ...
  23. >>> y = g(12)
  24. >>> y
  25. computing...
  26. 17
  27. """
  28. from pypy.objspace.proxy import patch_space_in_place
  29. from pypy.interpreter import gateway, baseobjspace, argument
  30. from pypy.interpreter.error import OperationError
  31. from pypy.interpreter.function import Method
  32. # __________________________________________________________________________
  33. # 'w_obj.w_thunkalias' points to another object that 'w_obj' has turned into
  34. baseobjspace.W_Root.w_thunkalias = None
  35. # adding a name in __slots__ after class creation doesn't "work" in Python,
  36. # but in this case it has the effect of telling the annotator that this
  37. # attribute is allowed to be moved up to this class.
  38. baseobjspace.W_Root.__slots__ += ('w_thunkalias',)
  39. class W_Thunk(baseobjspace.W_Root, object):
  40. def __init__(w_self, w_callable, args):
  41. w_self.w_callable = w_callable
  42. w_self.args = args
  43. w_self.operr = None
  44. # special marker to say that w_self has not been computed yet
  45. w_NOT_COMPUTED_THUNK = W_Thunk(None, None)
  46. W_Thunk.w_thunkalias = w_NOT_COMPUTED_THUNK
  47. def _force(space, w_self):
  48. w_alias = w_self.w_thunkalias
  49. while w_alias is not None:
  50. if w_alias is w_NOT_COMPUTED_THUNK:
  51. assert isinstance(w_self, W_Thunk)
  52. if w_self.operr is not None:
  53. raise w_self.operr
  54. w_callable = w_self.w_callable
  55. args = w_self.args
  56. if w_callable is None or args is None:
  57. raise OperationError(space.w_RuntimeError,
  58. space.wrap("thunk is already being computed"))
  59. w_self.w_callable = None
  60. w_self.args = None
  61. try:
  62. w_alias = space.call_args(w_callable, args)
  63. except OperationError, operr:
  64. w_self.operr = operr
  65. raise
  66. if _is_circular(w_self, w_alias):
  67. operr = OperationError(space.w_RuntimeError,
  68. space.wrap("circular thunk alias"))
  69. w_self.operr = operr
  70. raise operr
  71. w_self.w_thunkalias = w_alias
  72. # XXX do path compression?
  73. w_self = w_alias
  74. w_alias = w_self.w_thunkalias
  75. return w_self
  76. def _is_circular(w_obj, w_alias):
  77. assert (w_obj.w_thunkalias is None or
  78. w_obj.w_thunkalias is w_NOT_COMPUTED_THUNK)
  79. while 1:
  80. if w_obj is w_alias:
  81. return True
  82. w_next = w_alias.w_thunkalias
  83. if w_next is None:
  84. return False
  85. if w_next is w_NOT_COMPUTED_THUNK:
  86. return False
  87. w_alias = w_next
  88. def force(space, w_self):
  89. if w_self.w_thunkalias is not None:
  90. w_self = _force(space, w_self)
  91. return w_self
  92. def thunk(w_callable, __args__):
  93. """thunk(f, *args, **kwds) -> an object that behaves like the
  94. result of the call f(*args, **kwds). The call is performed lazily."""
  95. return W_Thunk(w_callable, __args__)
  96. app_thunk = gateway.interp2app(thunk)
  97. def is_thunk(space, w_obj):
  98. """Check if an object is a thunk that has not been computed yet."""
  99. while 1:
  100. w_alias = w_obj.w_thunkalias
  101. if w_alias is None:
  102. return space.w_False
  103. if w_alias is w_NOT_COMPUTED_THUNK:
  104. return space.w_True
  105. w_obj = w_alias
  106. app_is_thunk = gateway.interp2app(is_thunk)
  107. def become(space, w_target, w_source):
  108. """Globally replace the target object with the source one."""
  109. w_target = force(space, w_target)
  110. if not _is_circular(w_target, w_source):
  111. w_target.w_thunkalias = w_source
  112. return space.w_None
  113. app_become = gateway.interp2app(become)
  114. def lazy(space, w_callable):
  115. """Decorator to make a callable return its results wrapped in a thunk."""
  116. meth = Method(space, space.w_fn_thunk,
  117. w_callable, space.type(w_callable))
  118. return space.wrap(meth)
  119. app_lazy = gateway.interp2app(lazy)
  120. # __________________________________________________________________________
  121. nb_forcing_args = {}
  122. def setup():
  123. nb_forcing_args.update({
  124. 'setattr': 2, # instead of 3
  125. 'setitem': 2, # instead of 3
  126. 'get': 2, # instead of 3
  127. # ---- irregular operations ----
  128. 'wrap': 0,
  129. 'str_w': 1,
  130. 'int_w': 1,
  131. 'float_w': 1,
  132. 'uint_w': 1,
  133. 'unicode_w': 1,
  134. 'bigint_w': 1,
  135. 'interpclass_w': 1,
  136. 'unwrap': 1,
  137. 'is_true': 1,
  138. 'is_w': 2,
  139. 'newtuple': 0,
  140. 'newlist': 0,
  141. 'newdict': 0,
  142. 'newslice': 0,
  143. 'call_args': 1,
  144. 'marshal_w': 1,
  145. 'log': 1,
  146. })
  147. for opname, _, arity, _ in baseobjspace.ObjSpace.MethodTable:
  148. nb_forcing_args.setdefault(opname, arity)
  149. for opname in baseobjspace.ObjSpace.IrregularOpTable:
  150. assert opname in nb_forcing_args, "missing %r" % opname
  151. setup()
  152. del setup
  153. # __________________________________________________________________________
  154. def proxymaker(space, opname, parentfn):
  155. nb_args = nb_forcing_args[opname]
  156. if nb_args == 0:
  157. proxy = None
  158. elif nb_args == 1:
  159. def proxy(w1, *extra):
  160. w1 = force(space, w1)
  161. return parentfn(w1, *extra)
  162. elif nb_args == 2:
  163. def proxy(w1, w2, *extra):
  164. w1 = force(space, w1)
  165. w2 = force(space, w2)
  166. return parentfn(w1, w2, *extra)
  167. elif nb_args == 3:
  168. def proxy(w1, w2, w3, *extra):
  169. w1 = force(space, w1)
  170. w2 = force(space, w2)
  171. w3 = force(space, w3)
  172. return parentfn(w1, w2, w3, *extra)
  173. elif nb_args == 4:
  174. def proxy(w1, w2, w3, w4, *extra):
  175. w1 = force(space, w1)
  176. w2 = force(space, w2)
  177. w3 = force(space, w3)
  178. w4 = force(space, w4)
  179. return parentfn(w1, w2, w3, w4, *extra)
  180. else:
  181. raise NotImplementedError("operation %r has arity %d" %
  182. (opname, nb_args))
  183. return proxy
  184. def Space(*args, **kwds):
  185. # for now, always make up a wrapped StdObjSpace
  186. from pypy.objspace import std
  187. space = std.Space(*args, **kwds)
  188. patch_space_in_place(space, 'thunk', proxymaker)
  189. space.resolve_target = lambda w_arg: _force(space, w_arg)
  190. w___pypy__ = space.getbuiltinmodule("__pypy__")
  191. space.w_fn_thunk = space.wrap(app_thunk)
  192. space.setattr(w___pypy__, space.wrap('thunk'),
  193. space.w_fn_thunk)
  194. space.setattr(w___pypy__, space.wrap('is_thunk'),
  195. space.wrap(app_is_thunk))
  196. space.setattr(w___pypy__, space.wrap('become'),
  197. space.wrap(app_become))
  198. space.setattr(w___pypy__, space.wrap('lazy'),
  199. space.wrap(app_lazy))
  200. return space