(original) (raw)
changeset: 85046:862ab99ab570 user: Antoine Pitrou solipsis@pitrou.net date: Mon Aug 05 23:26:40 2013 +0200 files: Doc/library/inspect.rst Doc/reference/datamodel.rst Include/frameobject.h Include/genobject.h Lib/test/test_sys.py Lib/test/test_traceback.py Misc/NEWS Objects/frameobject.c Objects/genobject.c Python/ceval.c description: Issue #17934: Add a clear() method to frame objects, to help clean up expensive details (local variables) and break reference cycles. diff -r 1754b7900da1 -r 862ab99ab570 Doc/library/inspect.rst --- a/Doc/library/inspect.rst Mon Aug 05 23:17:30 2013 +0200 +++ b/Doc/library/inspect.rst Mon Aug 05 23:26:40 2013 +0200 @@ -846,6 +846,10 @@ 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. diff -r 1754b7900da1 -r 862ab99ab570 Doc/reference/datamodel.rst --- a/Doc/reference/datamodel.rst Mon Aug 05 23:17:30 2013 +0200 +++ b/Doc/reference/datamodel.rst Mon Aug 05 23:26:40 2013 +0200 @@ -934,6 +934,20 @@ 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 diff -r 1754b7900da1 -r 862ab99ab570 Include/frameobject.h --- a/Include/frameobject.h Mon Aug 05 23:17:30 2013 +0200 +++ b/Include/frameobject.h Mon Aug 05 23:26:40 2013 +0200 @@ -36,6 +36,8 @@ 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 @@ 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; diff -r 1754b7900da1 -r 862ab99ab570 Include/genobject.h --- a/Include/genobject.h Mon Aug 05 23:17:30 2013 +0200 +++ b/Include/genobject.h Mon Aug 05 23:26:40 2013 +0200 @@ -36,6 +36,8 @@ 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 } diff -r 1754b7900da1 -r 862ab99ab570 Lib/test/test_sys.py --- a/Lib/test/test_sys.py Mon Aug 05 23:17:30 2013 +0200 +++ b/Lib/test/test_sys.py Mon Aug 05 23:26:40 2013 +0200 @@ -764,7 +764,7 @@ 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')) diff -r 1754b7900da1 -r 862ab99ab570 Lib/test/test_traceback.py --- a/Lib/test/test_traceback.py Mon Aug 05 23:17:30 2013 +0200 +++ b/Lib/test/test_traceback.py Mon Aug 05 23:26:40 2013 +0200 @@ -150,11 +150,17 @@ 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 @@ # 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 diff -r 1754b7900da1 -r 862ab99ab570 Misc/NEWS --- a/Misc/NEWS Mon Aug 05 23:17:30 2013 +0200 +++ b/Misc/NEWS Mon Aug 05 23:26:40 2013 +0200 @@ -10,6 +10,9 @@ 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 ------- diff -r 1754b7900da1 -r 862ab99ab570 Objects/frameobject.c --- a/Objects/frameobject.c Mon Aug 05 23:17:30 2013 +0200 +++ b/Objects/frameobject.c Mon Aug 05 23:26:40 2013 +0200 @@ -488,7 +488,7 @@ } static void -frame_clear(PyFrameObject *f) +frame_tp_clear(PyFrameObject *f) { PyObject **fastlocals, **p, **oldtop; Py_ssize_t i, slots; @@ -500,6 +500,7 @@ */ oldtop = f->f_stacktop; f->f_stacktop = NULL; + f->f_executing = 0; Py_CLEAR(f->f_exc_type); Py_CLEAR(f->f_exc_value); @@ -520,6 +521,25 @@ } 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) { Py_ssize_t res, extras, ncells, nfrees; @@ -538,6 +558,8 @@ "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 @@ 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 @@ 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; diff -r 1754b7900da1 -r 862ab99ab570 Objects/genobject.c --- a/Objects/genobject.c Mon Aug 05 23:17:30 2013 +0200 +++ b/Objects/genobject.c Mon Aug 05 23:26:40 2013 +0200 @@ -15,8 +15,8 @@ return 0; } -static void -gen_finalize(PyObject *self) +void +_PyGen_Finalize(PyObject *self) { PyGenObject *gen = (PyGenObject *)self; PyObject *res; @@ -140,6 +140,7 @@ 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 @@ 0, /* tp_weaklist */ 0, /* tp_del */ 0, /* tp_version_tag */ - gen_finalize, /* tp_finalize */ + _PyGen_Finalize, /* tp_finalize */ }; PyObject * @@ -517,6 +518,7 @@ 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; diff -r 1754b7900da1 -r 862ab99ab570 Python/ceval.c --- a/Python/ceval.c Mon Aug 05 23:17:30 2013 +0200 +++ b/Python/ceval.c Mon Aug 05 23:26:40 2013 +0200 @@ -1182,6 +1182,7 @@ 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 @@ /* pop frame */ exit_eval_frame: Py_LeaveRecursiveCall(); + f->f_executing = 0; tstate->frame = f->f_back; return retval; /solipsis@pitrou.net