PageRenderTime 44ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/pypy/interpreter/generator.py

https://bitbucket.org/pypy/pypy/
Python | 263 lines | 209 code | 28 blank | 26 comment | 47 complexity | cd6d76b8f2b64443c9ab6e87599d33c9 MD5 | raw file
Possible License(s): AGPL-3.0, BSD-3-Clause, Apache-2.0
  1. from pypy.interpreter.baseobjspace import W_Root
  2. from pypy.interpreter.error import OperationError, oefmt
  3. from pypy.interpreter.pyopcode import LoopBlock
  4. from pypy.interpreter.pycode import CO_YIELD_INSIDE_TRY
  5. from rpython.rlib import jit
  6. class GeneratorIterator(W_Root):
  7. "An iterator created by a generator."
  8. _immutable_fields_ = ['pycode']
  9. def __init__(self, frame):
  10. self.space = frame.space
  11. self.frame = frame # turned into None when frame_finished_execution
  12. self.pycode = frame.pycode
  13. self.running = False
  14. if self.pycode.co_flags & CO_YIELD_INSIDE_TRY:
  15. self.register_finalizer(self.space)
  16. def descr__repr__(self, space):
  17. if self.pycode is None:
  18. code_name = '<finished>'
  19. else:
  20. code_name = self.pycode.co_name
  21. addrstring = self.getaddrstring(space)
  22. return space.wrap("<generator object %s at 0x%s>" %
  23. (code_name, addrstring))
  24. def descr__reduce__(self, space):
  25. from pypy.interpreter.mixedmodule import MixedModule
  26. w_mod = space.getbuiltinmodule('_pickle_support')
  27. mod = space.interp_w(MixedModule, w_mod)
  28. new_inst = mod.get('generator_new')
  29. w = space.wrap
  30. if self.frame:
  31. w_frame = self.frame._reduce_state(space)
  32. else:
  33. w_frame = space.w_None
  34. tup = [
  35. w_frame,
  36. w(self.running),
  37. ]
  38. return space.newtuple([new_inst, space.newtuple([]),
  39. space.newtuple(tup)])
  40. def descr__setstate__(self, space, w_args):
  41. from rpython.rlib.objectmodel import instantiate
  42. args_w = space.unpackiterable(w_args)
  43. w_framestate, w_running = args_w
  44. if space.is_w(w_framestate, space.w_None):
  45. self.frame = None
  46. self.space = space
  47. self.pycode = None
  48. else:
  49. frame = instantiate(space.FrameClass) # XXX fish
  50. frame.descr__setstate__(space, w_framestate)
  51. GeneratorIterator.__init__(self, frame)
  52. self.running = self.space.is_true(w_running)
  53. def descr__iter__(self):
  54. """x.__iter__() <==> iter(x)"""
  55. return self.space.wrap(self)
  56. def descr_send(self, w_arg=None):
  57. """send(arg) -> send 'arg' into generator,
  58. return next yielded value or raise StopIteration."""
  59. return self.send_ex(w_arg)
  60. def send_ex(self, w_arg, operr=None):
  61. pycode = self.pycode
  62. if pycode is not None:
  63. if jit.we_are_jitted() and should_not_inline(pycode):
  64. generatorentry_driver.jit_merge_point(gen=self, w_arg=w_arg,
  65. operr=operr, pycode=pycode)
  66. return self._send_ex(w_arg, operr)
  67. def _send_ex(self, w_arg, operr):
  68. space = self.space
  69. if self.running:
  70. raise oefmt(space.w_ValueError, "generator already executing")
  71. frame = self.frame
  72. if frame is None:
  73. # xxx a bit ad-hoc, but we don't want to go inside
  74. # execute_frame() if the frame is actually finished
  75. if operr is None:
  76. operr = OperationError(space.w_StopIteration, space.w_None)
  77. raise operr
  78. last_instr = jit.promote(frame.last_instr)
  79. if last_instr == -1:
  80. if w_arg and not space.is_w(w_arg, space.w_None):
  81. raise oefmt(space.w_TypeError,
  82. "can't send non-None value to a just-started "
  83. "generator")
  84. else:
  85. if not w_arg:
  86. w_arg = space.w_None
  87. self.running = True
  88. try:
  89. try:
  90. w_result = frame.execute_frame(w_arg, operr)
  91. except OperationError:
  92. # errors finish a frame
  93. self.frame = None
  94. raise
  95. # if the frame is now marked as finished, it was RETURNed from
  96. if frame.frame_finished_execution:
  97. self.frame = None
  98. raise OperationError(space.w_StopIteration, space.w_None)
  99. else:
  100. return w_result # YIELDed
  101. finally:
  102. frame.f_backref = jit.vref_None
  103. self.running = False
  104. def descr_throw(self, w_type, w_val=None, w_tb=None):
  105. """x.throw(typ[,val[,tb]]) -> raise exception in generator,
  106. return next yielded value or raise StopIteration."""
  107. if w_val is None:
  108. w_val = self.space.w_None
  109. return self.throw(w_type, w_val, w_tb)
  110. def throw(self, w_type, w_val, w_tb):
  111. from pypy.interpreter.pytraceback import check_traceback
  112. space = self.space
  113. msg = "throw() third argument must be a traceback object"
  114. if space.is_none(w_tb):
  115. tb = None
  116. else:
  117. tb = check_traceback(space, w_tb, msg)
  118. operr = OperationError(w_type, w_val, tb)
  119. operr.normalize_exception(space)
  120. return self.send_ex(space.w_None, operr)
  121. def descr_next(self):
  122. """x.next() -> the next value, or raise StopIteration"""
  123. return self.send_ex(self.space.w_None)
  124. def descr_close(self):
  125. """x.close(arg) -> raise GeneratorExit inside generator."""
  126. space = self.space
  127. try:
  128. w_retval = self.throw(space.w_GeneratorExit, space.w_None,
  129. space.w_None)
  130. except OperationError as e:
  131. if e.match(space, space.w_StopIteration) or \
  132. e.match(space, space.w_GeneratorExit):
  133. return space.w_None
  134. raise
  135. if w_retval is not None:
  136. raise oefmt(space.w_RuntimeError,
  137. "generator ignored GeneratorExit")
  138. def descr_gi_frame(self, space):
  139. if self.frame is not None and not self.frame.frame_finished_execution:
  140. return self.frame
  141. else:
  142. return space.w_None
  143. def descr_gi_code(self, space):
  144. return self.pycode
  145. def descr__name__(self, space):
  146. if self.pycode is None:
  147. code_name = '<finished>'
  148. else:
  149. code_name = self.pycode.co_name
  150. return space.wrap(code_name)
  151. # Results can be either an RPython list of W_Root, or it can be an
  152. # app-level W_ListObject, which also has an append() method, that's why we
  153. # generate 2 versions of the function and 2 jit drivers.
  154. def _create_unpack_into():
  155. jitdriver = jit.JitDriver(greens=['pycode'],
  156. reds=['self', 'frame', 'results'],
  157. name='unpack_into')
  158. def unpack_into(self, results):
  159. """This is a hack for performance: runs the generator and collects
  160. all produced items in a list."""
  161. # XXX copied and simplified version of send_ex()
  162. space = self.space
  163. if self.running:
  164. raise oefmt(space.w_ValueError, "generator already executing")
  165. frame = self.frame
  166. if frame is None: # already finished
  167. return
  168. self.running = True
  169. try:
  170. pycode = self.pycode
  171. while True:
  172. jitdriver.jit_merge_point(self=self, frame=frame,
  173. results=results, pycode=pycode)
  174. try:
  175. w_result = frame.execute_frame(space.w_None)
  176. except OperationError as e:
  177. if not e.match(space, space.w_StopIteration):
  178. raise
  179. break
  180. # if the frame is now marked as finished, it was RETURNed from
  181. if frame.frame_finished_execution:
  182. break
  183. results.append(w_result) # YIELDed
  184. finally:
  185. frame.f_backref = jit.vref_None
  186. self.running = False
  187. self.frame = None
  188. return unpack_into
  189. unpack_into = _create_unpack_into()
  190. unpack_into_w = _create_unpack_into()
  191. def _finalize_(self):
  192. # This is only called if the CO_YIELD_INSIDE_TRY flag is set
  193. # on the code object. If the frame is still not finished and
  194. # finally or except blocks are present at the current
  195. # position, then raise a GeneratorExit. Otherwise, there is
  196. # no point.
  197. if self.frame is not None:
  198. block = self.frame.lastblock
  199. while block is not None:
  200. if not isinstance(block, LoopBlock):
  201. self.descr_close()
  202. break
  203. block = block.previous
  204. def get_printable_location_genentry(bytecode):
  205. return '%s <generator>' % (bytecode.get_repr(),)
  206. generatorentry_driver = jit.JitDriver(greens=['pycode'],
  207. reds=['gen', 'w_arg', 'operr'],
  208. get_printable_location =
  209. get_printable_location_genentry,
  210. name='generatorentry')
  211. from pypy.tool.stdlib_opcode import HAVE_ARGUMENT, opmap
  212. YIELD_VALUE = opmap['YIELD_VALUE']
  213. @jit.elidable_promote()
  214. def should_not_inline(pycode):
  215. # Should not inline generators with more than one "yield",
  216. # as an approximative fix (see issue #1782). There are cases
  217. # where it slows things down; for example calls to a simple
  218. # generator that just produces a few simple values with a few
  219. # consecutive "yield" statements. It fixes the near-infinite
  220. # slow-down in issue #1782, though...
  221. count_yields = 0
  222. code = pycode.co_code
  223. n = len(code)
  224. i = 0
  225. while i < n:
  226. c = code[i]
  227. op = ord(c)
  228. if op == YIELD_VALUE:
  229. count_yields += 1
  230. i += 1
  231. if op >= HAVE_ARGUMENT:
  232. i += 2
  233. return count_yields >= 2