Skip to content

Commit af0b965

Browse files
committed
pythongh-79932: deprecate clearing a suspended frame and add option to get exception.
1 parent 5e5762a commit af0b965

File tree

5 files changed

+51
-5
lines changed

5 files changed

+51
-5
lines changed

Doc/reference/datamodel.rst

+9-1
Original file line numberDiff line numberDiff line change
@@ -1206,7 +1206,7 @@ by writing to f_lineno.
12061206

12071207
Frame objects support one method:
12081208

1209-
.. method:: frame.clear()
1209+
.. method:: frame.clear([raise_if_suspended])
12101210

12111211
This method clears all references to local variables held by the
12121212
frame. Also, if the frame belonged to a generator, the generator
@@ -1216,8 +1216,16 @@ Frame objects support one method:
12161216

12171217
:exc:`RuntimeError` is raised if the frame is currently executing.
12181218

1219+
Clearing a suspended frame is deprecated.
1220+
The optional argument *raise_if_suspended* can be passed ``True`` to
1221+
make this function raise a :exc:`RuntimeError` instead of issuing a
1222+
deprecation warning if the frame is suspended.
1223+
12191224
.. versionadded:: 3.4
12201225

1226+
.. versionchanged:: 3.13
1227+
Clearing a suspended frame is deprecated. Added the *raise_if_suspended*
1228+
argument.
12211229

12221230
.. _traceback-objects:
12231231

Doc/whatsnew/3.13.rst

+3
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,9 @@ Deprecated
397397
and methods that consider plural forms even if the translation was not found.
398398
(Contributed by Serhiy Storchaka in :gh:`88434`.)
399399

400+
* Calling :meth:`frame.clear` on a suspended frame is deprecated.
401+
(Contributed by Irit Katriel in :gh:`79932`.)
402+
400403

401404
Pending Removal in Python 3.14
402405
------------------------------

Lib/test/test_frame.py

+17-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import threading
77
import types
88
import unittest
9+
import warnings
910
import weakref
1011
try:
1112
import _testcapi
@@ -80,8 +81,19 @@ def g():
8081
gen = g()
8182
next(gen)
8283
self.assertFalse(endly)
84+
85+
# Raise exception when attempting to clear a suspended frame
86+
with self.assertRaisesRegex(RuntimeError, r'suspended frame'):
87+
gen.gi_frame.clear(True)
88+
self.assertFalse(endly)
89+
8390
# Clearing the frame closes the generator
84-
gen.gi_frame.clear()
91+
try:
92+
with self.assertWarnsRegex(DeprecationWarning, r'suspended frame'):
93+
gen.gi_frame.clear()
94+
except DeprecationWarning:
95+
# Suppress the warning when running with -We
96+
pass
8597
self.assertTrue(endly)
8698

8799
def test_clear_executing(self):
@@ -115,7 +127,10 @@ def g():
115127
f = next(gen)
116128
self.assertFalse(endly)
117129
# Clearing the frame closes the generator
118-
f.clear()
130+
with warnings.catch_warnings():
131+
warnings.filterwarnings('ignore', category=DeprecationWarning)
132+
f.clear()
133+
119134
self.assertTrue(endly)
120135

121136
def test_lineno_with_tracing(self):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Deprecate clearing a suspended frame.

Objects/frameobject.c

+21-2
Original file line numberDiff line numberDiff line change
@@ -933,13 +933,32 @@ frame_tp_clear(PyFrameObject *f)
933933
}
934934

935935
static PyObject *
936-
frame_clear(PyFrameObject *f, PyObject *Py_UNUSED(ignored))
936+
frame_clear(PyFrameObject *f, PyObject *args, PyObject *kwds)
937937
{
938+
bool raise_if_suspended = false;
939+
PyObject *v = NULL;
940+
if (!PyArg_UnpackTuple(args, "clear", 0, 1, &v)) {
941+
return NULL;
942+
}
943+
if (v != NULL && PyObject_IsTrue(v)) {
944+
raise_if_suspended = true;
945+
}
946+
938947
if (f->f_frame->owner == FRAME_OWNED_BY_GENERATOR) {
939948
PyGenObject *gen = _PyFrame_GetGenerator(f->f_frame);
940949
if (gen->gi_frame_state == FRAME_EXECUTING) {
941950
goto running;
942951
}
952+
if (FRAME_STATE_SUSPENDED(gen->gi_frame_state)) {
953+
if (raise_if_suspended) {
954+
PyErr_SetString(PyExc_RuntimeError, "cannot clear a suspended frame");
955+
return NULL;
956+
}
957+
if (PyErr_WarnEx(PyExc_DeprecationWarning,
958+
"clearing a suspended frame is deprecated", 1) < 0) {
959+
return NULL;
960+
}
961+
}
943962
_PyGen_Finalize((PyObject *)gen);
944963
}
945964
else if (f->f_frame->owner == FRAME_OWNED_BY_THREAD) {
@@ -983,7 +1002,7 @@ frame_repr(PyFrameObject *f)
9831002
}
9841003

9851004
static PyMethodDef frame_methods[] = {
986-
{"clear", (PyCFunction)frame_clear, METH_NOARGS,
1005+
{"clear", (PyCFunction)frame_clear, METH_VARARGS,
9871006
clear__doc__},
9881007
{"__sizeof__", (PyCFunction)frame_sizeof, METH_NOARGS,
9891008
sizeof__doc__},

0 commit comments

Comments
 (0)