| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -846,6 +846,10 @@ index of the current line within that list. | |||
| 846 | 846 | finally: | |
| 847 | 847 | del frame | |
| 848 | 848 | ||
| 849 | + If you want to keep the frame around (for example to print a traceback | ||
| 850 | + later), you can also break reference cycles by using the | ||
| 851 | + :meth:`frame.clear` method. | ||
| 852 | + | ||
| 849 | 853 | The optional *context* argument supported by most of these functions specifies | |
| 850 | 854 | the number of lines of context to return, which are centered around the current | |
| 851 | 855 | line. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -934,6 +934,20 @@ Internal types | |||
| 934 | 934 | frame). A debugger can implement a Jump command (aka Set Next Statement) | |
| 935 | 935 | by writing to f_lineno. | |
| 936 | 936 | ||
| 937 | + Frame objects support one method: | ||
| 938 | + | ||
| 939 | + .. method:: frame.clear() | ||
| 940 | + | ||
| 941 | + This method clears all references to local variables held by the | ||
| 942 | + frame. Also, if the frame belonged to a generator, the generator | ||
| 943 | + is finalized. This helps break reference cycles involving frame | ||
| 944 | + objects (for example when catching an exception and storing its | ||
| 945 | + traceback for later use). | ||
| 946 | + | ||
| 947 | + :exc:`RuntimeError` is raised if the frame is currently executing. | ||
| 948 | + | ||
| 949 | + .. versionadded:: 3.4 | ||
| 950 | + | ||
| 937 | 951 | Traceback objects | |
| 938 | 952 | .. index:: | |
| 939 | 953 | object: traceback | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -36,6 +36,8 @@ typedef struct _frame { | |||
| 36 | 36 | non-generator frames. See the save_exc_state and swap_exc_state | |
| 37 | 37 | functions in ceval.c for details of their use. */ | |
| 38 | 38 | PyObject *f_exc_type, *f_exc_value, *f_exc_traceback; | |
| 39 | + /* Borrowed referenced to a generator, or NULL */ | ||
| 40 | + PyObject *f_gen; | ||
| 39 | 41 | ||
| 40 | 42 | PyThreadState *f_tstate; | |
| 41 | 43 | int f_lasti; /* Last instruction if called */ | |
@@ -46,6 +48,7 @@ typedef struct _frame { | |||
| 46 | 48 | bytecode index. */ | |
| 47 | 49 | int f_lineno; /* Current line number */ | |
| 48 | 50 | int f_iblock; /* index in f_blockstack */ | |
| 51 | + char f_executing; /* whether the frame is still executing */ | ||
| 49 | 52 | PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */ | |
| 50 | 53 | PyObject *f_localsplus[1]; /* locals+stack, dynamically sized */ | |
| 51 | 54 | } PyFrameObject; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -36,6 +36,8 @@ PyAPI_FUNC(PyObject *) PyGen_New(struct _frame *); | |||
| 36 | 36 | PyAPI_FUNC(int) PyGen_NeedsFinalizing(PyGenObject *); | |
| 37 | 37 | PyAPI_FUNC(int) _PyGen_FetchStopIterationValue(PyObject **); | |
| 38 | 38 | PyObject *_PyGen_Send(PyGenObject *, PyObject *); | |
| 39 | + PyAPI_FUNC(void) _PyGen_Finalize(PyObject *self); | ||
| 40 | + | ||
| 39 | 41 | ||
| 40 | 42 | #ifdef __cplusplus | |
| 41 | 43 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -764,7 +764,7 @@ class C(object): pass | |||
| 764 | 764 | nfrees = len(x.f_code.co_freevars) | |
| 765 | 765 | extras = x.f_code.co_stacksize + x.f_code.co_nlocals +\ | |
| 766 | 766 | ncells + nfrees - 1 | |
| 767 | - check(x, vsize('12P3i' + CO_MAXBLOCKS*'3i' + 'P' + extras*'P')) | ||
| 767 | + check(x, vsize('13P3ic' + CO_MAXBLOCKS*'3i' + 'P' + extras*'P')) | ||
| 768 | 768 | # function | |
| 769 | 769 | def func(): pass | |
| 770 | 770 | check(func, size('12P')) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -150,11 +150,17 @@ def do_test(firstlines, message, charset, lineno): | |||
| 150 | 150 | ||
| 151 | 151 | class TracebackFormatTests(unittest.TestCase): | |
| 152 | 152 | ||
| 153 | - def test_traceback_format(self): | ||
| 153 | + def some_exception(self): | ||
| 154 | + raise KeyError('blah') | ||
| 155 | + | ||
| 156 | + def check_traceback_format(self, cleanup_func=None): | ||
| 154 | 157 | try: | |
| 155 | - raise KeyError('blah') | ||
| 158 | + self.some_exception() | ||
| 156 | 159 | except KeyError: | |
| 157 | 160 | type_, value, tb = sys.exc_info() | |
| 161 | + if cleanup_func is not None: | ||
| 162 | + # Clear the inner frames, not this one | ||
| 163 | + cleanup_func(tb.tb_next) | ||
| 158 | 164 | traceback_fmt = 'Traceback (most recent call last):\n' + \ | |
| 159 | 165 | ''.join(traceback.format_tb(tb)) | |
| 160 | 166 | file_ = StringIO() | |
@@ -183,12 +189,22 @@ def test_traceback_format(self): | |||
| 183 | 189 | ||
| 184 | 190 | # Make sure that the traceback is properly indented. | |
| 185 | 191 | tb_lines = python_fmt.splitlines() | |
| 186 | - self.assertEqual(len(tb_lines), 3) | ||
| 187 | - banner, location, source_line = tb_lines | ||
| 192 | + self.assertEqual(len(tb_lines), 5) | ||
| 193 | + banner = tb_lines[0] | ||
| 194 | + location, source_line = tb_lines[-2:] | ||
| 188 | 195 | self.assertTrue(banner.startswith('Traceback')) | |
| 189 | 196 | self.assertTrue(location.startswith(' File')) | |
| 190 | 197 | self.assertTrue(source_line.startswith(' raise')) | |
| 191 | 198 | ||
| 199 | + def test_traceback_format(self): | ||
| 200 | + self.check_traceback_format() | ||
| 201 | + | ||
| 202 | + def test_traceback_format_with_cleared_frames(self): | ||
| 203 | + # Check that traceback formatting also works with a clear()ed frame | ||
| 204 | + def cleanup_tb(tb): | ||
| 205 | + tb.tb_frame.clear() | ||
| 206 | + self.check_traceback_format(cleanup_tb) | ||
| 207 | + | ||
| 192 | 208 | def test_stack_format(self): | |
| 193 | 209 | # Verify _stack functions. Note we have to use _getframe(1) to | |
| 194 | 210 | # compare them without this frame appearing in the output | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -10,6 +10,9 @@ Projected Release date: 2013-09-08 | |||
| 10 | 10 | Core and Builtins | |
| 11 | 11 | ----------------- | |
| 12 | 12 | ||
| 13 | + - Issue #17934: Add a clear() method to frame objects, to help clean up | ||
| 14 | + expensive details (local variables) and break reference cycles. | ||
| 15 | + | ||
| 13 | 16 | Library | |
| 14 | 17 | ------- | |
| 15 | 18 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -488,7 +488,7 @@ frame_traverse(PyFrameObject *f, visitproc visit, void *arg) | |||
| 488 | 488 | } | |
| 489 | 489 | ||
| 490 | 490 | static void | |
| 491 | - frame_clear(PyFrameObject *f) | ||
| 491 | + frame_tp_clear(PyFrameObject *f) | ||
| 492 | 492 | { | |
| 493 | 493 | PyObject **fastlocals, **p, **oldtop; | |
| 494 | 494 | Py_ssize_t i, slots; | |
@@ -500,6 +500,7 @@ frame_clear(PyFrameObject *f) | |||
| 500 | 500 | */ | |
| 501 | 501 | oldtop = f->f_stacktop; | |
| 502 | 502 | f->f_stacktop = NULL; | |
| 503 | + f->f_executing = 0; | ||
| 503 | 504 | ||
| 504 | 505 | Py_CLEAR(f->f_exc_type); | |
| 505 | 506 | Py_CLEAR(f->f_exc_value); | |
@@ -519,6 +520,25 @@ frame_clear(PyFrameObject *f) | |||
| 519 | 520 | } | |
| 520 | 521 | } | |
| 521 | 522 | ||
| 523 | + static PyObject * | ||
| 524 | + frame_clear(PyFrameObject *f) | ||
| 525 | + { | ||
| 526 | + if (f->f_executing) { | ||
| 527 | + PyErr_SetString(PyExc_RuntimeError, | ||
| 528 | + "cannot clear an executing frame"); | ||
| 529 | + return NULL; | ||
| 530 | + } | ||
| 531 | + if (f->f_gen) { | ||
| 532 | + _PyGen_Finalize(f->f_gen); | ||
| 533 | + assert(f->f_gen == NULL); | ||
| 534 | + } | ||
| 535 | + frame_tp_clear(f); | ||
| 536 | + Py_RETURN_NONE; | ||
| 537 | + } | ||
| 538 | + | ||
| 539 | + PyDoc_STRVAR(clear__doc__, | ||
| 540 | + "F.clear(): clear most references held by the frame"); | ||
| 541 | + | ||
| 522 | 542 | static PyObject * | |
| 523 | 543 | frame_sizeof(PyFrameObject *f) | |
| 524 | 544 | { | |
@@ -538,6 +558,8 @@ PyDoc_STRVAR(sizeof__doc__, | |||
| 538 | 558 | "F.__sizeof__() -> size of F in memory, in bytes"); | |
| 539 | 559 | ||
| 540 | 560 | static PyMethodDef frame_methods[] = { | |
| 561 | + {"clear", (PyCFunction)frame_clear, METH_NOARGS, | ||
| 562 | + clear__doc__}, | ||
| 541 | 563 | {"__sizeof__", (PyCFunction)frame_sizeof, METH_NOARGS, | |
| 542 | 564 | sizeof__doc__}, | |
| 543 | 565 | {NULL, NULL} /* sentinel */ | |
@@ -566,7 +588,7 @@ PyTypeObject PyFrame_Type = { | |||
| 566 | 588 | Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */ | |
| 567 | 589 | 0, /* tp_doc */ | |
| 568 | 590 | (traverseproc)frame_traverse, /* tp_traverse */ | |
| 569 | - (inquiry)frame_clear, /* tp_clear */ | ||
| 591 | + (inquiry)frame_tp_clear, /* tp_clear */ | ||
| 570 | 592 | 0, /* tp_richcompare */ | |
| 571 | 593 | 0, /* tp_weaklistoffset */ | |
| 572 | 594 | 0, /* tp_iter */ | |
@@ -708,6 +730,8 @@ PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals, | |||
| 708 | 730 | f->f_lasti = -1; | |
| 709 | 731 | f->f_lineno = code->co_firstlineno; | |
| 710 | 732 | f->f_iblock = 0; | |
| 733 | + f->f_executing = 0; | ||
| 734 | + f->f_gen = NULL; | ||
| 711 | 735 | ||
| 712 | 736 | _PyObject_GC_TRACK(f); | |
| 713 | 737 | return f; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -15,8 +15,8 @@ gen_traverse(PyGenObject *gen, visitproc visit, void *arg) | |||
| 15 | 15 | return 0; | |
| 16 | 16 | } | |
| 17 | 17 | ||
| 18 | - static void | ||
| 19 | - gen_finalize(PyObject *self) | ||
| 18 | + void | ||
| 19 | + _PyGen_Finalize(PyObject *self) | ||
| 20 | 20 | { | |
| 21 | 21 | PyGenObject *gen = (PyGenObject *)self; | |
| 22 | 22 | PyObject *res; | |
@@ -140,6 +140,7 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc) | |||
| 140 | 140 | Py_XDECREF(t); | |
| 141 | 141 | Py_XDECREF(v); | |
| 142 | 142 | Py_XDECREF(tb); | |
| 143 | + gen->gi_frame->f_gen = NULL; | ||
| 143 | 144 | gen->gi_frame = NULL; | |
| 144 | 145 | Py_DECREF(f); | |
| 145 | 146 | } | |
@@ -505,7 +506,7 @@ PyTypeObject PyGen_Type = { | |||
| 505 | 506 | 0, /* tp_weaklist */ | |
| 506 | 507 | 0, /* tp_del */ | |
| 507 | 508 | 0, /* tp_version_tag */ | |
| 508 | - gen_finalize, /* tp_finalize */ | ||
| 509 | + _PyGen_Finalize, /* tp_finalize */ | ||
| 509 | 510 | }; | |
| 510 | 511 | ||
| 511 | 512 | PyObject * | |
@@ -517,6 +518,7 @@ PyGen_New(PyFrameObject *f) | |||
| 517 | 518 | return NULL; | |
| 518 | 519 | } | |
| 519 | 520 | gen->gi_frame = f; | |
| 521 | + f->f_gen = (PyObject *) gen; | ||
| 520 | 522 | Py_INCREF(f->f_code); | |
| 521 | 523 | gen->gi_code = (PyObject *)(f->f_code); | |
| 522 | 524 | gen->gi_running = 0; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1182,6 +1182,7 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) | |||
| 1182 | 1182 | stack_pointer = f->f_stacktop; | |
| 1183 | 1183 | assert(stack_pointer != NULL); | |
| 1184 | 1184 | f->f_stacktop = NULL; /* remains NULL unless yield suspends frame */ | |
| 1185 | + f->f_executing = 1; | ||
| 1185 | 1186 | ||
| 1186 | 1187 | if (co->co_flags & CO_GENERATOR && !throwflag) { | |
| 1187 | 1188 | if (f->f_exc_type != NULL && f->f_exc_type != Py_None) { | |
@@ -3206,6 +3207,7 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) | |||
| 3206 | 3207 | /* pop frame */ | |
| 3207 | 3208 | exit_eval_frame: | |
| 3208 | 3209 | Py_LeaveRecursiveCall(); | |
| 3210 | + f->f_executing = 0; | ||
| 3209 | 3211 | tstate->frame = f->f_back; | |
| 3210 | 3212 | ||
| 3211 | 3213 | return retval; | |
| Back | FazBrowse Home | New Git URL |
0 commit comments