summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew Svetlov <andrew.svetlov@gmail.com>2022-03-17 01:03:09 (GMT)
committerGitHub <noreply@github.com>2022-03-17 01:03:09 (GMT)
commit30b5d41fabad04f9f34d603f1ce2249452c18c71 (patch)
tree7eed9b2cc60f96768193498b051583dd733e2a40
parenta7c54148322781cb0f332d440a3454d550ef6414 (diff)
downloadcpython-30b5d41fabad04f9f34d603f1ce2249452c18c71.zip
cpython-30b5d41fabad04f9f34d603f1ce2249452c18c71.tar.gz
cpython-30b5d41fabad04f9f34d603f1ce2249452c18c71.tar.bz2
bpo-47039: Normalize repr() of asyncio future and task objects (GH-31950)
-rw-r--r--Lib/asyncio/base_futures.py30
-rw-r--r--Lib/asyncio/base_tasks.py7
-rw-r--r--Lib/asyncio/futures.py5
-rw-r--r--Lib/asyncio/tasks.py4
-rw-r--r--Misc/NEWS.d/next/Library/2022-03-17-01-54-13.bpo-47039.0Yxv0K.rst1
-rw-r--r--Modules/_asynciomodule.c77
-rw-r--r--Modules/clinic/_asynciomodule.c.h36
7 files changed, 42 insertions, 118 deletions
diff --git a/Lib/asyncio/base_futures.py b/Lib/asyncio/base_futures.py
index 2c01ac9..cd811a7 100644
--- a/Lib/asyncio/base_futures.py
+++ b/Lib/asyncio/base_futures.py
@@ -42,16 +42,6 @@ def _format_callbacks(cb):
return f'cb=[{cb}]'
-# bpo-42183: _repr_running is needed for repr protection
-# when a Future or Task result contains itself directly or indirectly.
-# The logic is borrowed from @reprlib.recursive_repr decorator.
-# Unfortunately, the direct decorator usage is impossible because of
-# AttributeError: '_asyncio.Task' object has no attribute '__module__' error.
-#
-# After fixing this thing we can return to the decorator based approach.
-_repr_running = set()
-
-
def _future_repr_info(future):
# (Future) -> str
"""helper function for Future.__repr__"""
@@ -60,17 +50,9 @@ def _future_repr_info(future):
if future._exception is not None:
info.append(f'exception={future._exception!r}')
else:
- key = id(future), get_ident()
- if key in _repr_running:
- result = '...'
- else:
- _repr_running.add(key)
- try:
- # use reprlib to limit the length of the output, especially
- # for very long strings
- result = reprlib.repr(future._result)
- finally:
- _repr_running.discard(key)
+ # use reprlib to limit the length of the output, especially
+ # for very long strings
+ result = reprlib.repr(future._result)
info.append(f'result={result}')
if future._callbacks:
info.append(_format_callbacks(future._callbacks))
@@ -78,3 +60,9 @@ def _future_repr_info(future):
frame = future._source_traceback[-1]
info.append(f'created at {frame[0]}:{frame[1]}')
return info
+
+
+@reprlib.recursive_repr()
+def _future_repr(future):
+ info = ' '.join(_future_repr_info(future))
+ return f'<{future.__class__.__name__} {info}>'
diff --git a/Lib/asyncio/base_tasks.py b/Lib/asyncio/base_tasks.py
index 1d62389..26298e6 100644
--- a/Lib/asyncio/base_tasks.py
+++ b/Lib/asyncio/base_tasks.py
@@ -1,4 +1,5 @@
import linecache
+import reprlib
import traceback
from . import base_futures
@@ -22,6 +23,12 @@ def _task_repr_info(task):
return info
+@reprlib.recursive_repr()
+def _task_repr(task):
+ info = ' '.join(_task_repr_info(task))
+ return f'<{task.__class__.__name__} {info}>'
+
+
def _task_get_stack(task, limit):
frames = []
if hasattr(task._coro, 'cr_frame'):
diff --git a/Lib/asyncio/futures.py b/Lib/asyncio/futures.py
index 5a92f17..bfd4ee9 100644
--- a/Lib/asyncio/futures.py
+++ b/Lib/asyncio/futures.py
@@ -85,11 +85,8 @@ class Future:
self._source_traceback = format_helpers.extract_stack(
sys._getframe(1))
- _repr_info = base_futures._future_repr_info
-
def __repr__(self):
- return '<{} {}>'.format(self.__class__.__name__,
- ' '.join(self._repr_info()))
+ return base_futures._future_repr(self)
def __del__(self):
if not self.__log_traceback:
diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py
index b4f1eed..0b5f322 100644
--- a/Lib/asyncio/tasks.py
+++ b/Lib/asyncio/tasks.py
@@ -133,8 +133,8 @@ class Task(futures._PyFuture): # Inherit Python Task implementation
__class_getitem__ = classmethod(GenericAlias)
- def _repr_info(self):
- return base_tasks._task_repr_info(self)
+ def __repr__(self):
+ return base_tasks._task_repr(self)
def get_coro(self):
return self._coro
diff --git a/Misc/NEWS.d/next/Library/2022-03-17-01-54-13.bpo-47039.0Yxv0K.rst b/Misc/NEWS.d/next/Library/2022-03-17-01-54-13.bpo-47039.0Yxv0K.rst
new file mode 100644
index 0000000..66785e1
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-03-17-01-54-13.bpo-47039.0Yxv0K.rst
@@ -0,0 +1 @@
+Normalize ``repr()`` of asyncio future and task objects.
diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c
index 4b12744..2411978 100644
--- a/Modules/_asynciomodule.c
+++ b/Modules/_asynciomodule.c
@@ -29,11 +29,11 @@ _Py_IDENTIFIER(throw);
static PyObject *asyncio_mod;
static PyObject *traceback_extract_stack;
static PyObject *asyncio_get_event_loop_policy;
-static PyObject *asyncio_future_repr_info_func;
+static PyObject *asyncio_future_repr_func;
static PyObject *asyncio_iscoroutine_func;
static PyObject *asyncio_task_get_stack_func;
static PyObject *asyncio_task_print_stack_func;
-static PyObject *asyncio_task_repr_info_func;
+static PyObject *asyncio_task_repr_func;
static PyObject *asyncio_InvalidStateError;
static PyObject *asyncio_CancelledError;
static PyObject *context_kwname;
@@ -1360,6 +1360,13 @@ FutureObj_get_state(FutureObj *fut, void *Py_UNUSED(ignored))
return ret;
}
+static PyObject *
+FutureObj_repr(FutureObj *fut)
+{
+ ENSURE_FUTURE_ALIVE(fut)
+ return PyObject_CallOneArg(asyncio_future_repr_func, (PyObject *)fut);
+}
+
/*[clinic input]
_asyncio.Future._make_cancelled_error
@@ -1376,42 +1383,6 @@ _asyncio_Future__make_cancelled_error_impl(FutureObj *self)
return create_cancelled_error(self);
}
-/*[clinic input]
-_asyncio.Future._repr_info
-[clinic start generated code]*/
-
-static PyObject *
-_asyncio_Future__repr_info_impl(FutureObj *self)
-/*[clinic end generated code: output=fa69e901bd176cfb input=f21504d8e2ae1ca2]*/
-{
- return PyObject_CallOneArg(asyncio_future_repr_info_func, (PyObject *)self);
-}
-
-static PyObject *
-FutureObj_repr(FutureObj *fut)
-{
- _Py_IDENTIFIER(_repr_info);
-
- ENSURE_FUTURE_ALIVE(fut)
-
- PyObject *rinfo = _PyObject_CallMethodIdNoArgs((PyObject*)fut,
- &PyId__repr_info);
- if (rinfo == NULL) {
- return NULL;
- }
-
- PyObject *rinfo_s = PyUnicode_Join(NULL, rinfo);
- Py_DECREF(rinfo);
- if (rinfo_s == NULL) {
- return NULL;
- }
-
- PyObject *rstr = PyUnicode_FromFormat("<%s %U>",
- _PyType_Name(Py_TYPE(fut)), rinfo_s);
- Py_DECREF(rinfo_s);
- return rstr;
-}
-
static void
FutureObj_finalize(FutureObj *fut)
{
@@ -1497,7 +1468,6 @@ static PyMethodDef FutureType_methods[] = {
_ASYNCIO_FUTURE_DONE_METHODDEF
_ASYNCIO_FUTURE_GET_LOOP_METHODDEF
_ASYNCIO_FUTURE__MAKE_CANCELLED_ERROR_METHODDEF
- _ASYNCIO_FUTURE__REPR_INFO_METHODDEF
{"__class_getitem__", Py_GenericAlias, METH_O|METH_CLASS, PyDoc_STR("See PEP 585")},
{NULL, NULL} /* Sentinel */
};
@@ -2145,6 +2115,13 @@ TaskObj_get_fut_waiter(TaskObj *task, void *Py_UNUSED(ignored))
Py_RETURN_NONE;
}
+static PyObject *
+TaskObj_repr(TaskObj *task)
+{
+ return PyObject_CallOneArg(asyncio_task_repr_func, (PyObject *)task);
+}
+
+
/*[clinic input]
_asyncio.Task._make_cancelled_error
@@ -2164,17 +2141,6 @@ _asyncio_Task__make_cancelled_error_impl(TaskObj *self)
/*[clinic input]
-_asyncio.Task._repr_info
-[clinic start generated code]*/
-
-static PyObject *
-_asyncio_Task__repr_info_impl(TaskObj *self)
-/*[clinic end generated code: output=6a490eb66d5ba34b input=3c6d051ed3ddec8b]*/
-{
- return PyObject_CallOneArg(asyncio_task_repr_info_func, (PyObject *)self);
-}
-
-/*[clinic input]
_asyncio.Task.cancel
msg: object = None
@@ -2514,7 +2480,6 @@ static PyMethodDef TaskType_methods[] = {
_ASYNCIO_TASK_GET_STACK_METHODDEF
_ASYNCIO_TASK_PRINT_STACK_METHODDEF
_ASYNCIO_TASK__MAKE_CANCELLED_ERROR_METHODDEF
- _ASYNCIO_TASK__REPR_INFO_METHODDEF
_ASYNCIO_TASK_GET_NAME_METHODDEF
_ASYNCIO_TASK_SET_NAME_METHODDEF
_ASYNCIO_TASK_GET_CORO_METHODDEF
@@ -2539,7 +2504,7 @@ static PyTypeObject TaskType = {
.tp_base = &FutureType,
.tp_dealloc = TaskObj_dealloc,
.tp_as_async = &FutureType_as_async,
- .tp_repr = (reprfunc)FutureObj_repr,
+ .tp_repr = (reprfunc)TaskObj_repr,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE,
.tp_doc = _asyncio_Task___init____doc__,
.tp_traverse = (traverseproc)TaskObj_traverse,
@@ -3337,12 +3302,12 @@ module_free(void *m)
{
Py_CLEAR(asyncio_mod);
Py_CLEAR(traceback_extract_stack);
- Py_CLEAR(asyncio_future_repr_info_func);
+ Py_CLEAR(asyncio_future_repr_func);
Py_CLEAR(asyncio_get_event_loop_policy);
Py_CLEAR(asyncio_iscoroutine_func);
Py_CLEAR(asyncio_task_get_stack_func);
Py_CLEAR(asyncio_task_print_stack_func);
- Py_CLEAR(asyncio_task_repr_info_func);
+ Py_CLEAR(asyncio_task_repr_func);
Py_CLEAR(asyncio_InvalidStateError);
Py_CLEAR(asyncio_CancelledError);
@@ -3403,14 +3368,14 @@ module_init(void)
GET_MOD_ATTR(asyncio_get_event_loop_policy, "get_event_loop_policy")
WITH_MOD("asyncio.base_futures")
- GET_MOD_ATTR(asyncio_future_repr_info_func, "_future_repr_info")
+ GET_MOD_ATTR(asyncio_future_repr_func, "_future_repr")
WITH_MOD("asyncio.exceptions")
GET_MOD_ATTR(asyncio_InvalidStateError, "InvalidStateError")
GET_MOD_ATTR(asyncio_CancelledError, "CancelledError")
WITH_MOD("asyncio.base_tasks")
- GET_MOD_ATTR(asyncio_task_repr_info_func, "_task_repr_info")
+ GET_MOD_ATTR(asyncio_task_repr_func, "_task_repr")
GET_MOD_ATTR(asyncio_task_get_stack_func, "_task_get_stack")
GET_MOD_ATTR(asyncio_task_print_stack_func, "_task_print_stack")
diff --git a/Modules/clinic/_asynciomodule.c.h b/Modules/clinic/_asynciomodule.c.h
index 4a90dfa..4b64367 100644
--- a/Modules/clinic/_asynciomodule.c.h
+++ b/Modules/clinic/_asynciomodule.c.h
@@ -292,23 +292,6 @@ _asyncio_Future__make_cancelled_error(FutureObj *self, PyObject *Py_UNUSED(ignor
return _asyncio_Future__make_cancelled_error_impl(self);
}
-PyDoc_STRVAR(_asyncio_Future__repr_info__doc__,
-"_repr_info($self, /)\n"
-"--\n"
-"\n");
-
-#define _ASYNCIO_FUTURE__REPR_INFO_METHODDEF \
- {"_repr_info", (PyCFunction)_asyncio_Future__repr_info, METH_NOARGS, _asyncio_Future__repr_info__doc__},
-
-static PyObject *
-_asyncio_Future__repr_info_impl(FutureObj *self);
-
-static PyObject *
-_asyncio_Future__repr_info(FutureObj *self, PyObject *Py_UNUSED(ignored))
-{
- return _asyncio_Future__repr_info_impl(self);
-}
-
PyDoc_STRVAR(_asyncio_Task___init____doc__,
"Task(coro, *, loop=None, name=None, context=None)\n"
"--\n"
@@ -383,23 +366,6 @@ _asyncio_Task__make_cancelled_error(TaskObj *self, PyObject *Py_UNUSED(ignored))
return _asyncio_Task__make_cancelled_error_impl(self);
}
-PyDoc_STRVAR(_asyncio_Task__repr_info__doc__,
-"_repr_info($self, /)\n"
-"--\n"
-"\n");
-
-#define _ASYNCIO_TASK__REPR_INFO_METHODDEF \
- {"_repr_info", (PyCFunction)_asyncio_Task__repr_info, METH_NOARGS, _asyncio_Task__repr_info__doc__},
-
-static PyObject *
-_asyncio_Task__repr_info_impl(TaskObj *self);
-
-static PyObject *
-_asyncio_Task__repr_info(TaskObj *self, PyObject *Py_UNUSED(ignored))
-{
- return _asyncio_Task__repr_info_impl(self);
-}
-
PyDoc_STRVAR(_asyncio_Task_cancel__doc__,
"cancel($self, /, msg=None)\n"
"--\n"
@@ -924,4 +890,4 @@ _asyncio__leave_task(PyObject *module, PyObject *const *args, Py_ssize_t nargs,
exit:
return return_value;
}
-/*[clinic end generated code: output=540ed3caf5a4d57d input=a9049054013a1b77]*/
+/*[clinic end generated code: output=64b3836574e8a18c input=a9049054013a1b77]*/