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 @@ -15,7 +15,8 @@ PyObject **args, int argc, PyObject **kwds, int kwdc, PyObject **defs, int defc, - PyObject *kwdefs, PyObject *closure); + PyObject *kwdefs, PyObject *closure, + PyObject *func); #ifndef Py_LIMITED_API PyAPI_FUNC(PyObject *) _PyEval_CallTracing(PyObject *func, PyObject *args); 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) */ @@ -58,7 +59,7 @@ #define PyFrame_Check(op) (Py_TYPE(op) == &PyFrame_Type) PyAPI_FUNC(PyFrameObject *) PyFrame_New(PyThreadState *, PyCodeObject *, - PyObject *, PyObject *); + PyObject *, PyObject *, PyObject *); /* The rest of the interface is specific for frame objects */ diff --git a/Lib/test/test_f_func.py b/Lib/test/test_f_func.py new file mode 100644 --- /dev/null +++ b/Lib/test/test_f_func.py @@ -0,0 +1,66 @@ +"""Unit tests for the f_func attribute of frame objects.""" + +import unittest +from test import support + +from inspect import currentframe + +def get_func(): + return 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(): + support.run_unittest(TestFrameFunc) + + +if __name__ == "__main__": + unittest.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/Modules/_ctypes/callbacks.c b/Modules/_ctypes/callbacks.c --- a/Modules/_ctypes/callbacks.c +++ b/Modules/_ctypes/callbacks.c @@ -106,7 +106,8 @@ PyThreadState_Get(), /*PyThreadState *tstate,*/ py_code, /*PyCodeObject *code,*/ py_globals, /*PyObject *globals,*/ - 0 /*PyObject *locals*/ + 0, /*PyObject *locals*/ + 0 /*PyObject *func*/ ); if (!py_frame) goto bad; py_frame->f_lineno = lineno; diff --git a/Modules/pyexpat.c b/Modules/pyexpat.c --- a/Modules/pyexpat.c +++ b/Modules/pyexpat.c @@ -288,7 +288,7 @@ if (c == NULL) return NULL; - f = PyFrame_New(tstate, c, PyEval_GetGlobals(), NULL); + f = PyFrame_New(tstate, c, PyEval_GetGlobals(), NULL, func); if (f == NULL) return NULL; tstate->frame = f; 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); @@ -593,7 +596,7 @@ PyFrameObject * PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals, - PyObject *locals) + PyObject *locals, PyObject *func) { PyFrameObject *back = tstate->frame; PyFrameObject *f; @@ -602,7 +605,8 @@ #ifdef Py_DEBUG if (code == NULL || globals == NULL || !PyDict_Check(globals) || - (locals != NULL && !PyMapping_Check(locals))) { + (locals != NULL && !PyMapping_Check(locals)) || + (func != NULL && !PyFunction_Check(func))) { PyErr_BadInternalCall(); return NULL; } @@ -688,6 +692,10 @@ Py_INCREF(code); Py_INCREF(globals); f->f_globals = globals; + if (!(code->co_flags & CO_OPTIMIZED)) + func = Py_None; + Py_XINCREF(func); + f->f_func = func; /* 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 @@ -632,7 +632,8 @@ &PyTuple_GET_ITEM(arg, 0), PyTuple_GET_SIZE(arg), k, nk, d, nd, PyFunction_GET_KW_DEFAULTS(func), - PyFunction_GET_CLOSURE(func)); + PyFunction_GET_CLOSURE(func), + func); Py_XDECREF(kwtuple); diff --git a/Python/ceval.c b/Python/ceval.c --- a/Python/ceval.c +++ b/Python/ceval.c @@ -772,7 +772,7 @@ (PyObject **)NULL, 0, (PyObject **)NULL, 0, (PyObject **)NULL, 0, - NULL, NULL); + NULL, NULL, NULL); } @@ -3182,7 +3182,8 @@ PyObject * PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals, PyObject **args, int argcount, PyObject **kws, int kwcount, - PyObject **defs, int defcount, PyObject *kwdefs, PyObject *closure) + PyObject **defs, int defcount, PyObject *kwdefs, PyObject *closure, + PyObject *func) { PyCodeObject* co = (PyCodeObject*)_co; register PyFrameObject *f; @@ -3203,7 +3204,7 @@ assert(tstate != NULL); assert(globals != NULL); - f = PyFrame_New(tstate, co, globals, locals); + f = PyFrame_New(tstate, co, globals, locals, func); if (f == NULL) return NULL; @@ -4074,7 +4075,7 @@ take builtins without sanity checking them. */ assert(tstate != NULL); - f = PyFrame_New(tstate, co, globals, NULL); + f = PyFrame_New(tstate, co, globals, NULL, func); if (f == NULL) return NULL; @@ -4098,7 +4099,7 @@ return PyEval_EvalCodeEx((PyObject*)co, globals, (PyObject *)NULL, (*pp_stack)-n, na, (*pp_stack)-2*nk, nk, d, nd, kwdefs, - PyFunction_GET_CLOSURE(func)); + PyFunction_GET_CLOSURE(func), func); } static PyObject *