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:
|
||||
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 number of lines of context to return, which are centered around the current
|
||||
line.
|
||||
|
|
|
@ -934,6 +934,20 @@ Internal types
|
|||
frame). A debugger can implement a Jump command (aka Set Next Statement)
|
||||
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
|
||||
.. index::
|
||||
object: traceback
|
||||
|
|
|
@ -36,6 +36,8 @@ typedef struct _frame {
|
|||
non-generator frames. See the save_exc_state and swap_exc_state
|
||||
functions in ceval.c for details of their use. */
|
||||
PyObject *f_exc_type, *f_exc_value, *f_exc_traceback;
|
||||
/* Borrowed referenced to a generator, or NULL */
|
||||
PyObject *f_gen;
|
||||
|
||||
PyThreadState *f_tstate;
|
||||
int f_lasti; /* Last instruction if called */
|
||||
|
@ -46,6 +48,7 @@ typedef struct _frame {
|
|||
bytecode index. */
|
||||
int f_lineno; /* Current line number */
|
||||
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 */
|
||||
PyObject *f_localsplus[1]; /* locals+stack, dynamically sized */
|
||||
} PyFrameObject;
|
||||
|
|
|
@ -36,6 +36,8 @@ PyAPI_FUNC(PyObject *) PyGen_New(struct _frame *);
|
|||
PyAPI_FUNC(int) PyGen_NeedsFinalizing(PyGenObject *);
|
||||
PyAPI_FUNC(int) _PyGen_FetchStopIterationValue(PyObject **);
|
||||
PyObject *_PyGen_Send(PyGenObject *, PyObject *);
|
||||
PyAPI_FUNC(void) _PyGen_Finalize(PyObject *self);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
@ -764,7 +764,7 @@ class SizeofTest(unittest.TestCase):
|
|||
nfrees = len(x.f_code.co_freevars)
|
||||
extras = x.f_code.co_stacksize + x.f_code.co_nlocals +\
|
||||
ncells + nfrees - 1
|
||||
check(x, vsize('12P3i' + CO_MAXBLOCKS*'3i' + 'P' + extras*'P'))
|
||||
check(x, vsize('13P3ic' + CO_MAXBLOCKS*'3i' + 'P' + extras*'P'))
|
||||
# function
|
||||
def func(): pass
|
||||
check(func, size('12P'))
|
||||
|
|
|
@ -150,11 +150,17 @@ class SyntaxTracebackCases(unittest.TestCase):
|
|||
|
||||
class TracebackFormatTests(unittest.TestCase):
|
||||
|
||||
def test_traceback_format(self):
|
||||
def some_exception(self):
|
||||
raise KeyError('blah')
|
||||
|
||||
def check_traceback_format(self, cleanup_func=None):
|
||||
try:
|
||||
raise KeyError('blah')
|
||||
self.some_exception()
|
||||
except KeyError:
|
||||
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' + \
|
||||
''.join(traceback.format_tb(tb))
|
||||
file_ = StringIO()
|
||||
|
@ -183,12 +189,22 @@ class TracebackFormatTests(unittest.TestCase):
|
|||
|
||||
# Make sure that the traceback is properly indented.
|
||||
tb_lines = python_fmt.splitlines()
|
||||
self.assertEqual(len(tb_lines), 3)
|
||||
banner, location, source_line = tb_lines
|
||||
self.assertEqual(len(tb_lines), 5)
|
||||
banner = tb_lines[0]
|
||||
location, source_line = tb_lines[-2:]
|
||||
self.assertTrue(banner.startswith('Traceback'))
|
||||
self.assertTrue(location.startswith(' File'))
|
||||
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):
|
||||
# Verify _stack functions. Note we have to use _getframe(1) to
|
||||
# compare them without this frame appearing in the output
|
||||
|
|
|
@ -10,6 +10,9 @@ Projected Release date: 2013-09-08
|
|||
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
|
||||
-------
|
||||
|
||||
|
|
|
@ -488,7 +488,7 @@ frame_traverse(PyFrameObject *f, visitproc visit, void *arg)
|
|||
}
|
||||
|
||||
static void
|
||||
frame_clear(PyFrameObject *f)
|
||||
frame_tp_clear(PyFrameObject *f)
|
||||
{
|
||||
PyObject **fastlocals, **p, **oldtop;
|
||||
Py_ssize_t i, slots;
|
||||
|
@ -500,6 +500,7 @@ frame_clear(PyFrameObject *f)
|
|||
*/
|
||||
oldtop = f->f_stacktop;
|
||||
f->f_stacktop = NULL;
|
||||
f->f_executing = 0;
|
||||
|
||||
Py_CLEAR(f->f_exc_type);
|
||||
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 *
|
||||
frame_sizeof(PyFrameObject *f)
|
||||
{
|
||||
|
@ -538,6 +558,8 @@ PyDoc_STRVAR(sizeof__doc__,
|
|||
"F.__sizeof__() -> size of F in memory, in bytes");
|
||||
|
||||
static PyMethodDef frame_methods[] = {
|
||||
{"clear", (PyCFunction)frame_clear, METH_NOARGS,
|
||||
clear__doc__},
|
||||
{"__sizeof__", (PyCFunction)frame_sizeof, METH_NOARGS,
|
||||
sizeof__doc__},
|
||||
{NULL, NULL} /* sentinel */
|
||||
|
@ -566,7 +588,7 @@ PyTypeObject PyFrame_Type = {
|
|||
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */
|
||||
0, /* tp_doc */
|
||||
(traverseproc)frame_traverse, /* tp_traverse */
|
||||
(inquiry)frame_clear, /* tp_clear */
|
||||
(inquiry)frame_tp_clear, /* tp_clear */
|
||||
0, /* tp_richcompare */
|
||||
0, /* tp_weaklistoffset */
|
||||
0, /* tp_iter */
|
||||
|
@ -708,6 +730,8 @@ PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals,
|
|||
f->f_lasti = -1;
|
||||
f->f_lineno = code->co_firstlineno;
|
||||
f->f_iblock = 0;
|
||||
f->f_executing = 0;
|
||||
f->f_gen = NULL;
|
||||
|
||||
_PyObject_GC_TRACK(f);
|
||||
return f;
|
||||
|
|
|
@ -15,8 +15,8 @@ gen_traverse(PyGenObject *gen, visitproc visit, void *arg)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
gen_finalize(PyObject *self)
|
||||
void
|
||||
_PyGen_Finalize(PyObject *self)
|
||||
{
|
||||
PyGenObject *gen = (PyGenObject *)self;
|
||||
PyObject *res;
|
||||
|
@ -140,6 +140,7 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc)
|
|||
Py_XDECREF(t);
|
||||
Py_XDECREF(v);
|
||||
Py_XDECREF(tb);
|
||||
gen->gi_frame->f_gen = NULL;
|
||||
gen->gi_frame = NULL;
|
||||
Py_DECREF(f);
|
||||
}
|
||||
|
@ -505,7 +506,7 @@ PyTypeObject PyGen_Type = {
|
|||
0, /* tp_weaklist */
|
||||
0, /* tp_del */
|
||||
0, /* tp_version_tag */
|
||||
gen_finalize, /* tp_finalize */
|
||||
_PyGen_Finalize, /* tp_finalize */
|
||||
};
|
||||
|
||||
PyObject *
|
||||
|
@ -517,6 +518,7 @@ PyGen_New(PyFrameObject *f)
|
|||
return NULL;
|
||||
}
|
||||
gen->gi_frame = f;
|
||||
f->f_gen = (PyObject *) gen;
|
||||
Py_INCREF(f->f_code);
|
||||
gen->gi_code = (PyObject *)(f->f_code);
|
||||
gen->gi_running = 0;
|
||||
|
|
|
@ -1182,6 +1182,7 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
|
|||
stack_pointer = f->f_stacktop;
|
||||
assert(stack_pointer != NULL);
|
||||
f->f_stacktop = NULL; /* remains NULL unless yield suspends frame */
|
||||
f->f_executing = 1;
|
||||
|
||||
if (co->co_flags & CO_GENERATOR && !throwflag) {
|
||||
if (f->f_exc_type != NULL && f->f_exc_type != Py_None) {
|
||||
|
@ -3206,6 +3207,7 @@ fast_yield:
|
|||
/* pop frame */
|
||||
exit_eval_frame:
|
||||
Py_LeaveRecursiveCall();
|
||||
f->f_executing = 0;
|
||||
tstate->frame = f->f_back;
|
||||
|
||||
return retval;
|
||||
|
|
Loading…
Reference in New Issue