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

/Lib/asyncio/coroutines.py

https://bitbucket.org/jaraco/cpython-issue13540
Python | 195 lines | 156 code | 20 blank | 19 comment | 8 complexity | 695d439e8474c9ced3fc0320d68a1e3c MD5 | raw file
Possible License(s): BSD-3-Clause, 0BSD
  1. __all__ = ['coroutine',
  2. 'iscoroutinefunction', 'iscoroutine']
  3. import functools
  4. import inspect
  5. import opcode
  6. import os
  7. import sys
  8. import traceback
  9. import types
  10. from . import events
  11. from . import futures
  12. from .log import logger
  13. # Opcode of "yield from" instruction
  14. _YIELD_FROM = opcode.opmap['YIELD_FROM']
  15. # If you set _DEBUG to true, @coroutine will wrap the resulting
  16. # generator objects in a CoroWrapper instance (defined below). That
  17. # instance will log a message when the generator is never iterated
  18. # over, which may happen when you forget to use "yield from" with a
  19. # coroutine call. Note that the value of the _DEBUG flag is taken
  20. # when the decorator is used, so to be of any use it must be set
  21. # before you define your coroutines. A downside of using this feature
  22. # is that tracebacks show entries for the CoroWrapper.__next__ method
  23. # when _DEBUG is true.
  24. _DEBUG = (not sys.flags.ignore_environment
  25. and bool(os.environ.get('PYTHONASYNCIODEBUG')))
  26. # Check for CPython issue #21209
  27. def has_yield_from_bug():
  28. class MyGen:
  29. def __init__(self):
  30. self.send_args = None
  31. def __iter__(self):
  32. return self
  33. def __next__(self):
  34. return 42
  35. def send(self, *what):
  36. self.send_args = what
  37. return None
  38. def yield_from_gen(gen):
  39. yield from gen
  40. value = (1, 2, 3)
  41. gen = MyGen()
  42. coro = yield_from_gen(gen)
  43. next(coro)
  44. coro.send(value)
  45. return gen.send_args != (value,)
  46. _YIELD_FROM_BUG = has_yield_from_bug()
  47. del has_yield_from_bug
  48. class CoroWrapper:
  49. # Wrapper for coroutine object in _DEBUG mode.
  50. def __init__(self, gen, func):
  51. assert inspect.isgenerator(gen), gen
  52. self.gen = gen
  53. self.func = func
  54. self._source_traceback = traceback.extract_stack(sys._getframe(1))
  55. # __name__, __qualname__, __doc__ attributes are set by the coroutine()
  56. # decorator
  57. def __repr__(self):
  58. coro_repr = _format_coroutine(self)
  59. if self._source_traceback:
  60. frame = self._source_traceback[-1]
  61. coro_repr += ', created at %s:%s' % (frame[0], frame[1])
  62. return '<%s %s>' % (self.__class__.__name__, coro_repr)
  63. def __iter__(self):
  64. return self
  65. def __next__(self):
  66. return next(self.gen)
  67. if _YIELD_FROM_BUG:
  68. # For for CPython issue #21209: using "yield from" and a custom
  69. # generator, generator.send(tuple) unpacks the tuple instead of passing
  70. # the tuple unchanged. Check if the caller is a generator using "yield
  71. # from" to decide if the parameter should be unpacked or not.
  72. def send(self, *value):
  73. frame = sys._getframe()
  74. caller = frame.f_back
  75. assert caller.f_lasti >= 0
  76. if caller.f_code.co_code[caller.f_lasti] != _YIELD_FROM:
  77. value = value[0]
  78. return self.gen.send(value)
  79. else:
  80. def send(self, value):
  81. return self.gen.send(value)
  82. def throw(self, exc):
  83. return self.gen.throw(exc)
  84. def close(self):
  85. return self.gen.close()
  86. @property
  87. def gi_frame(self):
  88. return self.gen.gi_frame
  89. @property
  90. def gi_running(self):
  91. return self.gen.gi_running
  92. @property
  93. def gi_code(self):
  94. return self.gen.gi_code
  95. def __del__(self):
  96. # Be careful accessing self.gen.frame -- self.gen might not exist.
  97. gen = getattr(self, 'gen', None)
  98. frame = getattr(gen, 'gi_frame', None)
  99. if frame is not None and frame.f_lasti == -1:
  100. msg = '%r was never yielded from' % self
  101. tb = getattr(self, '_source_traceback', ())
  102. if tb:
  103. tb = ''.join(traceback.format_list(tb))
  104. msg += ('\nCoroutine object created at '
  105. '(most recent call last):\n')
  106. msg += tb.rstrip()
  107. logger.error(msg)
  108. def coroutine(func):
  109. """Decorator to mark coroutines.
  110. If the coroutine is not yielded from before it is destroyed,
  111. an error message is logged.
  112. """
  113. if inspect.isgeneratorfunction(func):
  114. coro = func
  115. else:
  116. @functools.wraps(func)
  117. def coro(*args, **kw):
  118. res = func(*args, **kw)
  119. if isinstance(res, futures.Future) or inspect.isgenerator(res):
  120. res = yield from res
  121. return res
  122. if not _DEBUG:
  123. wrapper = coro
  124. else:
  125. @functools.wraps(func)
  126. def wrapper(*args, **kwds):
  127. w = CoroWrapper(coro(*args, **kwds), func)
  128. if w._source_traceback:
  129. del w._source_traceback[-1]
  130. w.__name__ = func.__name__
  131. if hasattr(func, '__qualname__'):
  132. w.__qualname__ = func.__qualname__
  133. w.__doc__ = func.__doc__
  134. return w
  135. wrapper._is_coroutine = True # For iscoroutinefunction().
  136. return wrapper
  137. def iscoroutinefunction(func):
  138. """Return True if func is a decorated coroutine function."""
  139. return getattr(func, '_is_coroutine', False)
  140. _COROUTINE_TYPES = (types.GeneratorType, CoroWrapper)
  141. def iscoroutine(obj):
  142. """Return True if obj is a coroutine object."""
  143. return isinstance(obj, _COROUTINE_TYPES)
  144. def _format_coroutine(coro):
  145. assert iscoroutine(coro)
  146. coro_name = getattr(coro, '__qualname__', coro.__name__)
  147. filename = coro.gi_code.co_filename
  148. if (isinstance(coro, CoroWrapper)
  149. and not inspect.isgeneratorfunction(coro.func)):
  150. filename, lineno = events._get_function_source(coro.func)
  151. if coro.gi_frame is None:
  152. coro_repr = '%s() done, defined at %s:%s' % (coro_name, filename, lineno)
  153. else:
  154. coro_repr = '%s() running, defined at %s:%s' % (coro_name, filename, lineno)
  155. elif coro.gi_frame is not None:
  156. lineno = coro.gi_frame.f_lineno
  157. coro_repr = '%s() running at %s:%s' % (coro_name, filename, lineno)
  158. else:
  159. lineno = coro.gi_code.co_firstlineno
  160. coro_repr = '%s() done, defined at %s:%s' % (coro_name, filename, lineno)
  161. return coro_repr