mirror of https://github.com/python/cpython
Issue #17934: Add a clear() method to frame objects, to help clean up expensive details (local variables) and break reference cycles.
This commit is contained in:
parent
c53204b947
commit
58720d6145
|
@ -846,6 +846,10 @@ index of the current line within that list.
|
||||||
finally:
|
finally:
|
||||||
del frame
|
del frame
|
||||||
|
|
||||||
|
If you want to keep the frame around (for example to print a traceback
|
||||||
|
later), you can also break reference cycles by using the
|
||||||
|
:meth:`frame.clear` method.
|
||||||
|
|
||||||
The optional *context* argument supported by most of these functions specifies
|
The optional *context* argument supported by most of these functions specifies
|
||||||
the number of lines of context to return, which are centered around the current
|
the number of lines of context to return, which are centered around the current
|
||||||
line.
|
line.
|
||||||
|
|
|
@ -934,6 +934,20 @@ Internal types
|
||||||
frame). A debugger can implement a Jump command (aka Set Next Statement)
|
frame). A debugger can implement a Jump command (aka Set Next Statement)
|
||||||
by writing to f_lineno.
|
by writing to f_lineno.
|
||||||
|
|
||||||
|
Frame objects support one method:
|
||||||
|
|
||||||
|
.. method:: frame.clear()
|
||||||
|
|
||||||
|
This method clears all references to local variables held by the
|
||||||
|
frame. Also, if the frame belonged to a generator, the generator
|
||||||
|
is finalized. This helps break reference cycles involving frame
|
||||||
|
objects (for example when catching an exception and storing its
|
||||||
|
traceback for later use).
|
||||||
|
|
||||||
|
:exc:`RuntimeError` is raised if the frame is currently executing.
|
||||||
|
|
||||||
|
.. versionadded:: 3.4
|
||||||
|
|
||||||
Traceback objects
|
Traceback objects
|
||||||
.. index::
|
.. index::
|
||||||
object: traceback
|
object: traceback
|
||||||
|
|
|
@ -36,6 +36,8 @@ typedef struct _frame {
|
||||||
non-generator frames. See the save_exc_state and swap_exc_state
|
non-generator frames. See the save_exc_state and swap_exc_state
|
||||||
functions in ceval.c for details of their use. */
|
functions in ceval.c for details of their use. */
|
||||||
PyObject *f_exc_type, *f_exc_value, *f_exc_traceback;
|
PyObject *f_exc_type, *f_exc_value, *f_exc_traceback;
|
||||||
|
/* Borrowed referenced to a generator, or NULL */
|
||||||
|
PyObject *f_gen;
|
||||||
|
|
||||||
PyThreadState *f_tstate;
|
PyThreadState *f_tstate;
|
||||||
int f_lasti; /* Last instruction if called */
|
int f_lasti; /* Last instruction if called */
|
||||||
|
@ -46,6 +48,7 @@ typedef struct _frame {
|
||||||
bytecode index. */
|
bytecode index. */
|
||||||
int f_lineno; /* Current line number */
|
int f_lineno; /* Current line number */
|
||||||
int f_iblock; /* index in f_blockstack */
|
int f_iblock; /* index in f_blockstack */
|
||||||
|
char f_executing; /* whether the frame is still executing */
|
||||||
PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
|
PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
|
||||||
PyObject *f_localsplus[1]; /* locals+stack, dynamically sized */
|
PyObject *f_localsplus[1]; /* locals+stack, dynamically sized */
|
||||||
} PyFrameObject;
|
} PyFrameObject;
|
||||||
|
|
|
@ -36,6 +36,8 @@ PyAPI_FUNC(PyObject *) PyGen_New(struct _frame *);
|
||||||
PyAPI_FUNC(int) PyGen_NeedsFinalizing(PyGenObject *);
|
PyAPI_FUNC(int) PyGen_NeedsFinalizing(PyGenObject *);
|
||||||
PyAPI_FUNC(int) _PyGen_FetchStopIterationValue(PyObject **);
|
PyAPI_FUNC(int) _PyGen_FetchStopIterationValue(PyObject **);
|
||||||
PyObject *_PyGen_Send(PyGenObject *, PyObject *);
|
PyObject *_PyGen_Send(PyGenObject *, PyObject *);
|
||||||
|
PyAPI_FUNC(void) _PyGen_Finalize(PyObject *self);
|
||||||
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
@ -764,7 +764,7 @@ class SizeofTest(unittest.TestCase):
|
||||||
nfrees = len(x.f_code.co_freevars)
|
nfrees = len(x.f_code.co_freevars)
|
||||||
extras = x.f_code.co_stacksize + x.f_code.co_nlocals +\
|
extras = x.f_code.co_stacksize + x.f_code.co_nlocals +\
|
||||||
ncells + nfrees - 1
|
ncells + nfrees - 1
|
||||||
check(x, vsize('12P3i' + CO_MAXBLOCKS*'3i' + 'P' + extras*'P'))
|
check(x, vsize('13P3ic' + CO_MAXBLOCKS*'3i' + 'P' + extras*'P'))
|
||||||
# function
|
# function
|
||||||
def func(): pass
|
def func(): pass
|
||||||
check(func, size('12P'))
|
check(func, size('12P'))
|
||||||
|
|
|
@ -150,11 +150,17 @@ class SyntaxTracebackCases(unittest.TestCase):
|
||||||
|
|
||||||
class TracebackFormatTests(unittest.TestCase):
|
class TracebackFormatTests(unittest.TestCase):
|
||||||
|
|
||||||
def test_traceback_format(self):
|
def some_exception(self):
|
||||||
try:
|
|
||||||
raise KeyError('blah')
|
raise KeyError('blah')
|
||||||
|
|
||||||
|
def check_traceback_format(self, cleanup_func=None):
|
||||||
|
try:
|
||||||
|
self.some_exception()
|
||||||
except KeyError:
|
except KeyError:
|
||||||
type_, value, tb = sys.exc_info()
|
type_, value, tb = sys.exc_info()
|
||||||
|
if cleanup_func is not None:
|
||||||
|
# Clear the inner frames, not this one
|
||||||
|
cleanup_func(tb.tb_next)
|
||||||
traceback_fmt = 'Traceback (most recent call last):\n' + \
|
traceback_fmt = 'Traceback (most recent call last):\n' + \
|
||||||
''.join(traceback.format_tb(tb))
|
''.join(traceback.format_tb(tb))
|
||||||
file_ = StringIO()
|
file_ = StringIO()
|
||||||
|
@ -183,12 +189,22 @@ class TracebackFormatTests(unittest.TestCase):
|
||||||
|
|
||||||
# Make sure that the traceback is properly indented.
|
# Make sure that the traceback is properly indented.
|
||||||
tb_lines = python_fmt.splitlines()
|
tb_lines = python_fmt.splitlines()
|
||||||
self.assertEqual(len(tb_lines), 3)
|
self.assertEqual(len(tb_lines), 5)
|
||||||
banner, location, source_line = tb_lines
|
banner = tb_lines[0]
|
||||||
|
location, source_line = tb_lines[-2:]
|
||||||
self.assertTrue(banner.startswith('Traceback'))
|
self.assertTrue(banner.startswith('Traceback'))
|
||||||
self.assertTrue(location.startswith(' File'))
|
self.assertTrue(location.startswith(' File'))
|
||||||
self.assertTrue(source_line.startswith(' raise'))
|
self.assertTrue(source_line.startswith(' raise'))
|
||||||
|
|
||||||
|
def test_traceback_format(self):
|
||||||
|
self.check_traceback_format()
|
||||||
|
|
||||||
|
def test_traceback_format_with_cleared_frames(self):
|
||||||
|
# Check that traceback formatting also works with a clear()ed frame
|
||||||
|
def cleanup_tb(tb):
|
||||||
|
tb.tb_frame.clear()
|
||||||
|
self.check_traceback_format(cleanup_tb)
|
||||||
|
|
||||||
def test_stack_format(self):
|
def test_stack_format(self):
|
||||||
# Verify _stack functions. Note we have to use _getframe(1) to
|
# Verify _stack functions. Note we have to use _getframe(1) to
|
||||||
# compare them without this frame appearing in the output
|
# compare them without this frame appearing in the output
|
||||||
|
|
|
@ -10,6 +10,9 @@ Projected Release date: 2013-09-08
|
||||||
Core and Builtins
|
Core and Builtins
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
- Issue #17934: Add a clear() method to frame objects, to help clean up
|
||||||
|
expensive details (local variables) and break reference cycles.
|
||||||
|
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|
|
@ -488,7 +488,7 @@ frame_traverse(PyFrameObject *f, visitproc visit, void *arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
frame_clear(PyFrameObject *f)
|
frame_tp_clear(PyFrameObject *f)
|
||||||
{
|
{
|
||||||
PyObject **fastlocals, **p, **oldtop;
|
PyObject **fastlocals, **p, **oldtop;
|
||||||
Py_ssize_t i, slots;
|
Py_ssize_t i, slots;
|
||||||
|
@ -500,6 +500,7 @@ frame_clear(PyFrameObject *f)
|
||||||
*/
|
*/
|
||||||
oldtop = f->f_stacktop;
|
oldtop = f->f_stacktop;
|
||||||
f->f_stacktop = NULL;
|
f->f_stacktop = NULL;
|
||||||
|
f->f_executing = 0;
|
||||||
|
|
||||||
Py_CLEAR(f->f_exc_type);
|
Py_CLEAR(f->f_exc_type);
|
||||||
Py_CLEAR(f->f_exc_value);
|
Py_CLEAR(f->f_exc_value);
|
||||||
|
@ -519,6 +520,25 @@ frame_clear(PyFrameObject *f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
frame_clear(PyFrameObject *f)
|
||||||
|
{
|
||||||
|
if (f->f_executing) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError,
|
||||||
|
"cannot clear an executing frame");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (f->f_gen) {
|
||||||
|
_PyGen_Finalize(f->f_gen);
|
||||||
|
assert(f->f_gen == NULL);
|
||||||
|
}
|
||||||
|
frame_tp_clear(f);
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(clear__doc__,
|
||||||
|
"F.clear(): clear most references held by the frame");
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
frame_sizeof(PyFrameObject *f)
|
frame_sizeof(PyFrameObject *f)
|
||||||
{
|
{
|
||||||
|
@ -538,6 +558,8 @@ PyDoc_STRVAR(sizeof__doc__,
|
||||||
"F.__sizeof__() -> size of F in memory, in bytes");
|
"F.__sizeof__() -> size of F in memory, in bytes");
|
||||||
|
|
||||||
static PyMethodDef frame_methods[] = {
|
static PyMethodDef frame_methods[] = {
|
||||||
|
{"clear", (PyCFunction)frame_clear, METH_NOARGS,
|
||||||
|
clear__doc__},
|
||||||
{"__sizeof__", (PyCFunction)frame_sizeof, METH_NOARGS,
|
{"__sizeof__", (PyCFunction)frame_sizeof, METH_NOARGS,
|
||||||
sizeof__doc__},
|
sizeof__doc__},
|
||||||
{NULL, NULL} /* sentinel */
|
{NULL, NULL} /* sentinel */
|
||||||
|
@ -566,7 +588,7 @@ PyTypeObject PyFrame_Type = {
|
||||||
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */
|
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */
|
||||||
0, /* tp_doc */
|
0, /* tp_doc */
|
||||||
(traverseproc)frame_traverse, /* tp_traverse */
|
(traverseproc)frame_traverse, /* tp_traverse */
|
||||||
(inquiry)frame_clear, /* tp_clear */
|
(inquiry)frame_tp_clear, /* tp_clear */
|
||||||
0, /* tp_richcompare */
|
0, /* tp_richcompare */
|
||||||
0, /* tp_weaklistoffset */
|
0, /* tp_weaklistoffset */
|
||||||
0, /* tp_iter */
|
0, /* tp_iter */
|
||||||
|
@ -708,6 +730,8 @@ PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals,
|
||||||
f->f_lasti = -1;
|
f->f_lasti = -1;
|
||||||
f->f_lineno = code->co_firstlineno;
|
f->f_lineno = code->co_firstlineno;
|
||||||
f->f_iblock = 0;
|
f->f_iblock = 0;
|
||||||
|
f->f_executing = 0;
|
||||||
|
f->f_gen = NULL;
|
||||||
|
|
||||||
_PyObject_GC_TRACK(f);
|
_PyObject_GC_TRACK(f);
|
||||||
return f;
|
return f;
|
||||||
|
|
|
@ -15,8 +15,8 @@ gen_traverse(PyGenObject *gen, visitproc visit, void *arg)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
void
|
||||||
gen_finalize(PyObject *self)
|
_PyGen_Finalize(PyObject *self)
|
||||||
{
|
{
|
||||||
PyGenObject *gen = (PyGenObject *)self;
|
PyGenObject *gen = (PyGenObject *)self;
|
||||||
PyObject *res;
|
PyObject *res;
|
||||||
|
@ -140,6 +140,7 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc)
|
||||||
Py_XDECREF(t);
|
Py_XDECREF(t);
|
||||||
Py_XDECREF(v);
|
Py_XDECREF(v);
|
||||||
Py_XDECREF(tb);
|
Py_XDECREF(tb);
|
||||||
|
gen->gi_frame->f_gen = NULL;
|
||||||
gen->gi_frame = NULL;
|
gen->gi_frame = NULL;
|
||||||
Py_DECREF(f);
|
Py_DECREF(f);
|
||||||
}
|
}
|
||||||
|
@ -505,7 +506,7 @@ PyTypeObject PyGen_Type = {
|
||||||
0, /* tp_weaklist */
|
0, /* tp_weaklist */
|
||||||
0, /* tp_del */
|
0, /* tp_del */
|
||||||
0, /* tp_version_tag */
|
0, /* tp_version_tag */
|
||||||
gen_finalize, /* tp_finalize */
|
_PyGen_Finalize, /* tp_finalize */
|
||||||
};
|
};
|
||||||
|
|
||||||
PyObject *
|
PyObject *
|
||||||
|
@ -517,6 +518,7 @@ PyGen_New(PyFrameObject *f)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
gen->gi_frame = f;
|
gen->gi_frame = f;
|
||||||
|
f->f_gen = (PyObject *) gen;
|
||||||
Py_INCREF(f->f_code);
|
Py_INCREF(f->f_code);
|
||||||
gen->gi_code = (PyObject *)(f->f_code);
|
gen->gi_code = (PyObject *)(f->f_code);
|
||||||
gen->gi_running = 0;
|
gen->gi_running = 0;
|
||||||
|
|
|
@ -1182,6 +1182,7 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
|
||||||
stack_pointer = f->f_stacktop;
|
stack_pointer = f->f_stacktop;
|
||||||
assert(stack_pointer != NULL);
|
assert(stack_pointer != NULL);
|
||||||
f->f_stacktop = NULL; /* remains NULL unless yield suspends frame */
|
f->f_stacktop = NULL; /* remains NULL unless yield suspends frame */
|
||||||
|
f->f_executing = 1;
|
||||||
|
|
||||||
if (co->co_flags & CO_GENERATOR && !throwflag) {
|
if (co->co_flags & CO_GENERATOR && !throwflag) {
|
||||||
if (f->f_exc_type != NULL && f->f_exc_type != Py_None) {
|
if (f->f_exc_type != NULL && f->f_exc_type != Py_None) {
|
||||||
|
@ -3206,6 +3207,7 @@ fast_yield:
|
||||||
/* pop frame */
|
/* pop frame */
|
||||||
exit_eval_frame:
|
exit_eval_frame:
|
||||||
Py_LeaveRecursiveCall();
|
Py_LeaveRecursiveCall();
|
||||||
|
f->f_executing = 0;
|
||||||
tstate->frame = f->f_back;
|
tstate->frame = f->f_back;
|
||||||
|
|
||||||
return retval;
|
return retval;
|
||||||
|
|
Loading…
Reference in New Issue