diff options
author | Victor Stinner <victor.stinner@gmail.com> | 2014-06-25 19:41:58 (GMT) |
---|---|---|
committer | Victor Stinner <victor.stinner@gmail.com> | 2014-06-25 19:41:58 (GMT) |
commit | 975735f729df6b7767556d2d389b560dbc7500ac (patch) | |
tree | 8579740ca947c8323088a452d659e4c0cb130a2e /Lib/asyncio | |
parent | 65c623de74cba3eff6d8f5f4c37cfec89c0fde43 (diff) | |
download | cpython-975735f729df6b7767556d2d389b560dbc7500ac.zip cpython-975735f729df6b7767556d2d389b560dbc7500ac.tar.gz cpython-975735f729df6b7767556d2d389b560dbc7500ac.tar.bz2 |
asyncio, Tulip issue 177: Rewite repr() of Future, Task, Handle and TimerHandle
- Uniformize repr() output to format "<Class ...>"
- On Python 3.5+, repr(Task) uses the qualified name instead of the short name
of the coroutine
Diffstat (limited to 'Lib/asyncio')
-rw-r--r-- | Lib/asyncio/events.py | 56 | ||||
-rw-r--r-- | Lib/asyncio/futures.py | 48 | ||||
-rw-r--r-- | Lib/asyncio/tasks.py | 51 |
3 files changed, 102 insertions, 53 deletions
diff --git a/Lib/asyncio/events.py b/Lib/asyncio/events.py index de161df..4054482 100644 --- a/Lib/asyncio/events.py +++ b/Lib/asyncio/events.py @@ -18,6 +18,7 @@ import sys _PY34 = sys.version_info >= (3, 4) + def _get_function_source(func): if _PY34: func = inspect.unwrap(func) @@ -33,6 +34,35 @@ def _get_function_source(func): return None +def _format_args(args): + # function formatting ('hello',) as ('hello') + args_repr = repr(args) + if len(args) == 1 and args_repr.endswith(',)'): + args_repr = args_repr[:-2] + ')' + return args_repr + + +def _format_callback(func, args, suffix=''): + if isinstance(func, functools.partial): + if args is not None: + suffix = _format_args(args) + suffix + return _format_callback(func.func, func.args, suffix) + + func_repr = getattr(func, '__qualname__', None) + if not func_repr: + func_repr = repr(func) + + if args is not None: + func_repr += _format_args(args) + if suffix: + func_repr += suffix + + source = _get_function_source(func) + if source: + func_repr += ' at %s:%s' % source + return func_repr + + class Handle: """Object returned by callback registration methods.""" @@ -46,18 +76,11 @@ class Handle: self._cancelled = False def __repr__(self): - cb_repr = getattr(self._callback, '__qualname__', None) - if not cb_repr: - cb_repr = str(self._callback) - - source = _get_function_source(self._callback) - if source: - cb_repr += ' at %s:%s' % source - - res = 'Handle({}, {})'.format(cb_repr, self._args) + info = [] if self._cancelled: - res += '<cancelled>' - return res + info.append('cancelled') + info.append(_format_callback(self._callback, self._args)) + return '<%s %s>' % (self.__class__.__name__, ' '.join(info)) def cancel(self): self._cancelled = True @@ -88,13 +111,12 @@ class TimerHandle(Handle): self._when = when def __repr__(self): - res = 'TimerHandle({}, {}, {})'.format(self._when, - self._callback, - self._args) + info = [] if self._cancelled: - res += '<cancelled>' - - return res + info.append('cancelled') + info.append('when=%s' % self._when) + info.append(_format_callback(self._callback, self._args)) + return '<%s %s>' % (self.__class__.__name__, ' '.join(info)) def __hash__(self): return hash(self._when) diff --git a/Lib/asyncio/futures.py b/Lib/asyncio/futures.py index 4edd2e5..3103fe1 100644 --- a/Lib/asyncio/futures.py +++ b/Lib/asyncio/futures.py @@ -150,24 +150,40 @@ class Future: self._loop = loop self._callbacks = [] + def _format_callbacks(self): + cb = self._callbacks + size = len(cb) + if not size: + cb = '' + + def format_cb(callback): + return events._format_callback(callback, ()) + + if size == 1: + cb = format_cb(cb[0]) + elif size == 2: + cb = '{}, {}'.format(format_cb(cb[0]), format_cb(cb[1])) + elif size > 2: + cb = '{}, <{} more>, {}'.format(format_cb(cb[0]), + size-2, + format_cb(cb[-1])) + return 'cb=[%s]' % cb + + def _format_result(self): + if self._state != _FINISHED: + return None + elif self._exception is not None: + return 'exception={!r}'.format(self._exception) + else: + return 'result={!r}'.format(self._result) + def __repr__(self): - res = self.__class__.__name__ + info = [self._state.lower()] if self._state == _FINISHED: - if self._exception is not None: - res += '<exception={!r}>'.format(self._exception) - else: - res += '<result={!r}>'.format(self._result) - elif self._callbacks: - size = len(self._callbacks) - if size > 2: - res += '<{}, [{}, <{} more>, {}]>'.format( - self._state, self._callbacks[0], - size-2, self._callbacks[-1]) - else: - res += '<{}, {}>'.format(self._state, self._callbacks) - else: - res += '<{}>'.format(self._state) - return res + info.append(self._format_result()) + if self._callbacks: + info.append(self._format_callbacks()) + return '<%s %s>' % (self.__class__.__name__, ' '.join(info)) # On Python 3.3 or older, objects with a destructor part of a reference # cycle are never destroyed. It's not more the case on Python 3.4 thanks to diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index 3b41a21..52ca33a 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -132,6 +132,22 @@ def iscoroutine(obj): return isinstance(obj, CoroWrapper) or inspect.isgenerator(obj) +def _format_coroutine(coro): + assert iscoroutine(coro) + if _PY35: + coro_name = coro.__qualname__ + else: + coro_name = coro.__name__ + + filename = coro.gi_code.co_filename + if coro.gi_frame is not None: + lineno = coro.gi_frame.f_lineno + return '%s() at %s:%s' % (coro_name, filename, lineno) + else: + lineno = coro.gi_code.co_firstlineno + return '%s() done at %s:%s' % (coro_name, filename, lineno) + + class Task(futures.Future): """A coroutine wrapped in a Future.""" @@ -195,26 +211,21 @@ class Task(futures.Future): futures.Future.__del__(self) def __repr__(self): - res = super().__repr__() - if (self._must_cancel and - self._state == futures._PENDING and - '<PENDING' in res): - res = res.replace('<PENDING', '<CANCELLING', 1) - i = res.find('<') - if i < 0: - i = len(res) - text = self._coro.__name__ - coro = self._coro - if iscoroutine(coro): - filename = coro.gi_code.co_filename - if coro.gi_frame is not None: - lineno = coro.gi_frame.f_lineno - text += ' at %s:%s' % (filename, lineno) - else: - lineno = coro.gi_code.co_firstlineno - text += ' done at %s:%s' % (filename, lineno) - res = res[:i] + '(<{}>)'.format(text) + res[i:] - return res + info = [] + if self._must_cancel: + info.append('cancelling') + else: + info.append(self._state.lower()) + + info.append(_format_coroutine(self._coro)) + + if self._state == futures._FINISHED: + info.append(self._format_result()) + + if self._callbacks: + info.append(self._format_callbacks()) + + return '<%s %s>' % (self.__class__.__name__, ' '.join(info)) def get_stack(self, *, limit=None): """Return the list of stack frames for this task's coroutine. |