diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -90,6 +90,10 @@ | frame | f_back | next outer frame object | | | | (this frame's caller) | +-----------+-----------------+---------------------------+ +| | f_func | called function for which | +| | | this frame is executing, | +| | | if any | ++-----------+-----------------+---------------------------+ | | f_builtins | builtins namespace seen | | | | by this frame | +-----------+-----------------+---------------------------+ diff --git a/Include/eval.h b/Include/eval.h --- a/Include/eval.h +++ b/Include/eval.h @@ -9,6 +9,7 @@ PyAPI_FUNC(PyObject *) PyEval_EvalCode(PyObject *, PyObject *, PyObject *); +/* This is now just a wrapper around _PyEval_EvalFunctionCode. */ PyAPI_FUNC(PyObject *) PyEval_EvalCodeEx(PyObject *co, PyObject *globals, PyObject *locals, @@ -17,6 +18,14 @@ PyObject **defs, int defc, PyObject *kwdefs, PyObject *closure); +PyAPI_FUNC(PyObject *) _PyEval_EvalFunctionCode(PyObject *func, PyObject *co, + PyObject *globals, + PyObject *locals, + PyObject **args, int argc, + PyObject **kwds, int kwdc, + PyObject **defs, int defc, + PyObject *kwdefs, PyObject *closure); + #ifndef Py_LIMITED_API PyAPI_FUNC(PyObject *) _PyEval_CallTracing(PyObject *func, PyObject *args); #endif diff --git a/Include/frameobject.h b/Include/frameobject.h --- a/Include/frameobject.h +++ b/Include/frameobject.h @@ -18,6 +18,7 @@ PyObject_VAR_HEAD struct _frame *f_back; /* previous frame, or NULL */ PyCodeObject *f_code; /* code segment */ + PyObject *f_func; /* corresponding called function, if any */ PyObject *f_builtins; /* builtin symbol table (PyDictObject) */ PyObject *f_globals; /* global symbol table (PyDictObject) */ PyObject *f_locals; /* local symbol table (any mapping) */ diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -1057,13 +1057,66 @@ self.assertIn(name, str(state)) +def get_func(): + return inspect.currentframe().f_back.f_func + +module_func = get_func() + +class TestFrameFunc(unittest.TestCase): + + def test_plain_function(self): + def f(): return get_func() + self.assertEqual(f, f()) + + def test_lambda(self): + f = lambda : get_func() + self.assertEqual(f, f()) + + def test_method(self): + class X: + def f(self): return get_func() + self.assertEqual(X.f, X().f()) + + def test_staticmethod(self): + class X: + @staticmethod + def f(): return get_func() + self.assertEqual(X.f, X.f()) + self.assertEqual(X.f, X().f()) + + def test_classmethod(self): + class X: + @classmethod + def f(cls): return get_func() + self.assertEqual(X.f.__func__, X.f()) + self.assertEqual(X.f.__func__, X().f()) + + def test_decorator(self): + def decorator(f): + def newfunc(): + return f() + return newfunc + def f(): return get_func() + g = decorator(f) + self.assertEqual(f, g()) + self.assertNotEqual(g, g()) + + def test_module(self): + self.assertTrue(module_func is None) + + def test_class(self): + class X: + f = get_func() + self.assertTrue(X.f is None) + + def test_main(): run_unittest( TestDecorators, TestRetrievingSourceCode, TestOneliners, TestBuggyCases, TestInterpreterStack, TestClassesAndFunctions, TestPredicates, TestGetcallargsFunctions, TestGetcallargsMethods, TestGetcallargsUnboundMethods, TestGetattrStatic, TestGetGeneratorState, - TestNoEOL + TestNoEOL, TestFrameFunc ) if __name__ == "__main__": diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -726,7 +726,7 @@ nfrees = len(x.f_code.co_freevars) extras = x.f_code.co_stacksize + x.f_code.co_nlocals +\ ncells + nfrees - 1 - check(x, size(vh + '12P3i' + CO_MAXBLOCKS*'3i' + 'P' + extras*'P')) + check(x, size(vh + '13P3i' + CO_MAXBLOCKS*'3i' + 'P' + extras*'P')) # function def func(): pass check(func, size(h + '11P')) diff --git a/Misc/gdbinit b/Misc/gdbinit --- a/Misc/gdbinit +++ b/Misc/gdbinit @@ -129,7 +129,7 @@ # print the entire Python call stack define pystack while $pc < Py_Main || $pc > Py_GetArgcArgv - if $pc > PyEval_EvalFrameEx && $pc < PyEval_EvalCodeEx + if $pc > PyEval_EvalFrameEx && $pc < _PyEval_EvalFunctionCode pyframe end up-silently 1 @@ -140,7 +140,7 @@ # print the entire Python call stack - verbose mode define pystackv while $pc < Py_Main || $pc > Py_GetArgcArgv - if $pc > PyEval_EvalFrameEx && $pc < PyEval_EvalCodeEx + if $pc > PyEval_EvalFrameEx && $pc < _PyEval_EvalFunctionCode pyframev end up-silently 1 diff --git a/Objects/frameobject.c b/Objects/frameobject.c --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -17,6 +17,7 @@ static PyMemberDef frame_memberlist[] = { {"f_back", T_OBJECT, OFF(f_back), READONLY}, {"f_code", T_OBJECT, OFF(f_code), READONLY}, + {"f_func", T_OBJECT, OFF(f_func), READONLY}, {"f_builtins", T_OBJECT, OFF(f_builtins),READONLY}, {"f_globals", T_OBJECT, OFF(f_globals), READONLY}, {"f_lasti", T_INT, OFF(f_lasti), READONLY}, @@ -437,6 +438,7 @@ } Py_XDECREF(f->f_back); + Py_XDECREF(f->f_func); Py_DECREF(f->f_builtins); Py_DECREF(f->f_globals); Py_CLEAR(f->f_locals); @@ -467,6 +469,7 @@ int i, slots; Py_VISIT(f->f_back); + Py_VISIT(f->f_func); Py_VISIT(f->f_code); Py_VISIT(f->f_builtins); Py_VISIT(f->f_globals); @@ -688,6 +691,7 @@ Py_INCREF(code); Py_INCREF(globals); f->f_globals = globals; + f->f_func = NULL; /* Most functions have CO_NEWLOCALS and CO_OPTIMIZED set. */ if ((code->co_flags & (CO_NEWLOCALS | CO_OPTIMIZED)) == (CO_NEWLOCALS | CO_OPTIMIZED)) diff --git a/Objects/funcobject.c b/Objects/funcobject.c --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -626,7 +626,8 @@ nk = 0; } - result = PyEval_EvalCodeEx( + result = _PyEval_EvalFunctionCode( + func, PyFunction_GET_CODE(func), PyFunction_GET_GLOBALS(func), (PyObject *)NULL, &PyTuple_GET_ITEM(arg, 0), PyTuple_GET_SIZE(arg), diff --git a/Python/ceval.c b/Python/ceval.c --- a/Python/ceval.c +++ b/Python/ceval.c @@ -767,7 +767,7 @@ PyObject * PyEval_EvalCode(PyObject *co, PyObject *globals, PyObject *locals) { - return PyEval_EvalCodeEx(co, + return _PyEval_EvalFunctionCode(NULL, co, globals, locals, (PyObject **)NULL, 0, (PyObject **)NULL, 0, @@ -3176,7 +3176,7 @@ } /* This is gonna seem *real weird*, but if you put some other code between - PyEval_EvalFrame() and PyEval_EvalCodeEx() you will need to adjust + PyEval_EvalFrame() and _PyEval_EvalFunctionCode() you will need to adjust the test in the if statements in Misc/gdbinit (pystack and pystackv). */ PyObject * @@ -3184,6 +3184,17 @@ PyObject **args, int argcount, PyObject **kws, int kwcount, PyObject **defs, int defcount, PyObject *kwdefs, PyObject *closure) { + return _PyEval_EvalFunctionCode((PyObject *)NULL, _co, globals, locals, + args, argcount, kws, kwcount, + defs, defcount, kwdefs, closure); +} + +PyObject * +_PyEval_EvalFunctionCode(PyObject *func, PyObject *_co, + PyObject *globals, PyObject *locals, + PyObject **args, int argcount, PyObject **kws, int kwcount, + PyObject **defs, int defcount, PyObject *kwdefs, PyObject *closure) +{ PyCodeObject* co = (PyCodeObject*)_co; register PyFrameObject *f; register PyObject *retval = NULL; @@ -3197,7 +3208,7 @@ if (globals == NULL) { PyErr_SetString(PyExc_SystemError, - "PyEval_EvalCodeEx: NULL globals"); + "_PyEval_EvalFunctionCode: NULL globals"); return NULL; } @@ -3206,6 +3217,10 @@ f = PyFrame_New(tstate, co, globals, locals); if (f == NULL) return NULL; + if (func == NULL || !(co->co_flags & CO_OPTIMIZED)) + func = Py_None; + Py_INCREF(func); + f->f_func = func; fastlocals = f->f_localsplus; freevars = f->f_localsplus + co->co_nlocals; @@ -3381,6 +3396,8 @@ current Python frame (f), the associated C stack is still in use, so recursion_depth must be boosted for the duration. */ + Py_DECREF(func); + f->f_func = NULL; assert(tstate != NULL); ++tstate->recursion_depth; Py_DECREF(f); @@ -4042,8 +4059,8 @@ For the simplest case -- a function that takes only positional arguments and is called with only positional arguments -- it inlines the most primitive frame setup code from - PyEval_EvalCodeEx(), which vastly reduces the checks that must be - done before evaluating the frame. + _PyEval_EvalFunctionCode(), which vastly reduces the checks + that must be done before evaluating the frame. */ static PyObject * @@ -4077,6 +4094,8 @@ f = PyFrame_New(tstate, co, globals, NULL); if (f == NULL) return NULL; + Py_XINCREF(func); + f->f_func = func; fastlocals = f->f_localsplus; stack = (*pp_stack) - n; @@ -4086,6 +4105,8 @@ fastlocals[i] = *stack++; } retval = PyEval_EvalFrameEx(f,0); + Py_XDECREF(func); + f->f_func = NULL; ++tstate->recursion_depth; Py_DECREF(f); --tstate->recursion_depth; @@ -4095,7 +4116,7 @@ d = &PyTuple_GET_ITEM(argdefs, 0); nd = Py_SIZE(argdefs); } - return PyEval_EvalCodeEx((PyObject*)co, globals, + return _PyEval_EvalFunctionCode(func, (PyObject*)co, globals, (PyObject *)NULL, (*pp_stack)-n, na, (*pp_stack)-2*nk, nk, d, nd, kwdefs, PyFunction_GET_CLOSURE(func));