]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-74929: Implement PEP 667 (GH-115153)
authorTian Gao <gaogaotiantian@hotmail.com>
Sat, 4 May 2024 11:12:10 +0000 (04:12 -0700)
committerGitHub <noreply@github.com>
Sat, 4 May 2024 11:12:10 +0000 (12:12 +0100)
19 files changed:
Doc/data/stable_abi.dat
Include/ceval.h
Include/cpython/frameobject.h
Include/cpython/pyframe.h
Include/internal/pycore_frame.h
Lib/test/test_frame.py
Lib/test/test_listcomps.py
Lib/test/test_peepholer.py
Lib/test/test_stable_abi_ctypes.py
Lib/test/test_sys.py
Misc/NEWS.d/next/Core and Builtins/2024-04-27-21-44-40.gh-issue-74929.C2nESp.rst [new file with mode: 0644]
Misc/stable_abi.toml
Objects/frameobject.c
Objects/object.c
PC/python3dll.c
Python/ceval.c
Python/intrinsics.c
Python/sysmodule.c
Tools/c-analyzer/cpython/globals-to-fix.tsv

index 8c8a378f52bd5d5e76f062bb269ff5e65f4fe1b7..76a035f194d9115c76f525d93ace64b70de77a58 100644 (file)
@@ -188,6 +188,9 @@ function,PyEval_EvalFrame,3.2,,
 function,PyEval_EvalFrameEx,3.2,,
 function,PyEval_GetBuiltins,3.2,,
 function,PyEval_GetFrame,3.2,,
+function,PyEval_GetFrameBuiltins,3.13,,
+function,PyEval_GetFrameGlobals,3.13,,
+function,PyEval_GetFrameLocals,3.13,,
 function,PyEval_GetFuncDesc,3.2,,
 function,PyEval_GetFuncName,3.2,,
 function,PyEval_GetGlobals,3.2,,
index 8ea9da8d134ee0dbd5779215ad3db272210ce63a..1ec746c3708220edddb42e00a55d3a04dbd0b465 100644 (file)
@@ -22,6 +22,10 @@ PyAPI_FUNC(PyObject *) PyEval_GetGlobals(void);
 PyAPI_FUNC(PyObject *) PyEval_GetLocals(void);
 PyAPI_FUNC(PyFrameObject *) PyEval_GetFrame(void);
 
+PyAPI_FUNC(PyObject *) PyEval_GetFrameBuiltins(void);
+PyAPI_FUNC(PyObject *) PyEval_GetFrameGlobals(void);
+PyAPI_FUNC(PyObject *) PyEval_GetFrameLocals(void);
+
 PyAPI_FUNC(int) Py_AddPendingCall(int (*func)(void *), void *arg);
 PyAPI_FUNC(int) Py_MakePendingCalls(void);
 
index 4e19535c656f2cbef34238143c61aa120d6c9c5e..dbbfbb5105ba7aa51840beff4a90b4e103d497da 100644 (file)
@@ -27,3 +27,9 @@ PyAPI_FUNC(int) _PyFrame_IsEntryFrame(PyFrameObject *frame);
 
 PyAPI_FUNC(int) PyFrame_FastToLocalsWithError(PyFrameObject *f);
 PyAPI_FUNC(void) PyFrame_FastToLocals(PyFrameObject *);
+
+
+typedef struct {
+    PyObject_HEAD
+    PyFrameObject* frame;
+} PyFrameLocalsProxyObject;
index c5adbbe4868f698387e2269f4c031b98b0f98853..eeafbb17a56badddb542d8d13cbcd799ea1403eb 100644 (file)
@@ -3,8 +3,10 @@
 #endif
 
 PyAPI_DATA(PyTypeObject) PyFrame_Type;
+PyAPI_DATA(PyTypeObject) PyFrameLocalsProxy_Type;
 
 #define PyFrame_Check(op) Py_IS_TYPE((op), &PyFrame_Type)
+#define PyFrameLocalsProxy_Check(op) Py_IS_TYPE((op), &PyFrameLocalsProxy_Type)
 
 PyAPI_FUNC(PyFrameObject *) PyFrame_GetBack(PyFrameObject *frame);
 PyAPI_FUNC(PyObject *) PyFrame_GetLocals(PyFrameObject *frame);
index e13fdd9bb2e01ccccbc3661ba790547de4f37ecc..994900c007f4bd014ab733e1490ba2b749fb0b04 100644 (file)
@@ -25,7 +25,7 @@ struct _frame {
     int f_lineno;               /* Current line number. Only valid if non-zero */
     char f_trace_lines;         /* Emit per-line trace events? */
     char f_trace_opcodes;       /* Emit per-opcode trace events? */
-    char f_fast_as_locals;      /* Have the fast locals of this frame been converted to a dict? */
+    PyObject *f_extra_locals;   /* Dict for locals set by users using f_locals, could be NULL */
     /* The frame data, if this frame object owns the frame */
     PyObject *_f_frame_data[1];
 };
@@ -245,14 +245,11 @@ _PyFrame_ClearExceptCode(_PyInterpreterFrame * frame);
 int
 _PyFrame_Traverse(_PyInterpreterFrame *frame, visitproc visit, void *arg);
 
-PyObject *
-_PyFrame_GetLocals(_PyInterpreterFrame *frame, int include_hidden);
-
-int
-_PyFrame_FastToLocalsWithError(_PyInterpreterFrame *frame);
+bool
+_PyFrame_HasHiddenLocals(_PyInterpreterFrame *frame);
 
-void
-_PyFrame_LocalsToFast(_PyInterpreterFrame *frame, int clear);
+PyObject *
+_PyFrame_GetLocals(_PyInterpreterFrame *frame);
 
 static inline bool
 _PyThreadState_HasStackSpace(PyThreadState *tstate, int size)
index 8e744a1223e86f81acac0064fcca18cb9435f1cc..93d0ea839d16eb1d6b45389e0d67438bc503e425 100644 (file)
@@ -1,3 +1,4 @@
+import copy
 import gc
 import operator
 import re
@@ -13,7 +14,7 @@ except ImportError:
     _testcapi = None
 
 from test import support
-from test.support import threading_helper, Py_GIL_DISABLED
+from test.support import import_helper, threading_helper, Py_GIL_DISABLED
 from test.support.script_helper import assert_python_ok
 
 
@@ -198,14 +199,6 @@ class FrameAttrsTest(unittest.TestCase):
                 tb = tb.tb_next
         return frames
 
-    def test_locals(self):
-        f, outer, inner = self.make_frames()
-        outer_locals = outer.f_locals
-        self.assertIsInstance(outer_locals.pop('inner'), types.FunctionType)
-        self.assertEqual(outer_locals, {'x': 5, 'y': 6})
-        inner_locals = inner.f_locals
-        self.assertEqual(inner_locals, {'x': 5, 'z': 7})
-
     def test_clear_locals(self):
         # Test f_locals after clear() (issue #21897)
         f, outer, inner = self.make_frames()
@@ -217,8 +210,8 @@ class FrameAttrsTest(unittest.TestCase):
     def test_locals_clear_locals(self):
         # Test f_locals before and after clear() (to exercise caching)
         f, outer, inner = self.make_frames()
-        outer.f_locals
-        inner.f_locals
+        self.assertNotEqual(outer.f_locals, {})
+        self.assertNotEqual(inner.f_locals, {})
         outer.clear()
         inner.clear()
         self.assertEqual(outer.f_locals, {})
@@ -269,6 +262,177 @@ class ReprTest(unittest.TestCase):
                          r"^<frame at 0x[0-9a-fA-F]+, file %s, line %d, code inner>$"
                          % (file_repr, offset + 5))
 
+class TestFrameLocals(unittest.TestCase):
+    def test_scope(self):
+        class A:
+            x = 1
+            sys._getframe().f_locals['x'] = 2
+            sys._getframe().f_locals['y'] = 2
+
+        self.assertEqual(A.x, 2)
+        self.assertEqual(A.y, 2)
+
+        def f():
+            x = 1
+            sys._getframe().f_locals['x'] = 2
+            sys._getframe().f_locals['y'] = 2
+            self.assertEqual(x, 2)
+            self.assertEqual(locals()['y'], 2)
+        f()
+
+    def test_closure(self):
+        x = 1
+        y = 2
+
+        def f():
+            z = x + y
+            d = sys._getframe().f_locals
+            self.assertEqual(d['x'], 1)
+            self.assertEqual(d['y'], 2)
+            d['x'] = 2
+            d['y'] = 3
+
+        f()
+        self.assertEqual(x, 2)
+        self.assertEqual(y, 3)
+
+    def test_as_dict(self):
+        x = 1
+        y = 2
+        d = sys._getframe().f_locals
+        # self, x, y, d
+        self.assertEqual(len(d), 4)
+        self.assertIs(d['d'], d)
+        self.assertEqual(set(d.keys()), set(['x', 'y', 'd', 'self']))
+        self.assertEqual(len(d.values()), 4)
+        self.assertIn(1, d.values())
+        self.assertEqual(len(d.items()), 4)
+        self.assertIn(('x', 1), d.items())
+        self.assertEqual(d.__getitem__('x'), 1)
+        d.__setitem__('x', 2)
+        self.assertEqual(d['x'], 2)
+        self.assertEqual(d.get('x'), 2)
+        self.assertIs(d.get('non_exist', None), None)
+        self.assertEqual(d.__len__(), 4)
+        self.assertEqual(set([key for key in d]), set(['x', 'y', 'd', 'self']))
+        self.assertIn('x', d)
+        self.assertTrue(d.__contains__('x'))
+
+        self.assertEqual(reversed(d), list(reversed(d.keys())))
+
+        d.update({'x': 3, 'z': 4})
+        self.assertEqual(d['x'], 3)
+        self.assertEqual(d['z'], 4)
+
+        with self.assertRaises(TypeError):
+            d.update([1, 2])
+
+        self.assertEqual(d.setdefault('x', 5), 3)
+        self.assertEqual(d.setdefault('new', 5), 5)
+        self.assertEqual(d['new'], 5)
+
+        with self.assertRaises(KeyError):
+            d['non_exist']
+
+    def test_as_number(self):
+        x = 1
+        y = 2
+        d = sys._getframe().f_locals
+        self.assertIn('z', d | {'z': 3})
+        d |= {'z': 3}
+        self.assertEqual(d['z'], 3)
+        d |= {'y': 3}
+        self.assertEqual(d['y'], 3)
+        with self.assertRaises(TypeError):
+            d |= 3
+        with self.assertRaises(TypeError):
+            _ = d | [3]
+
+    def test_non_string_key(self):
+        d = sys._getframe().f_locals
+        d[1] = 2
+        self.assertEqual(d[1], 2)
+
+    def test_write_with_hidden(self):
+        def f():
+            f_locals = [sys._getframe().f_locals for b in [0]][0]
+            f_locals['b'] = 2
+            f_locals['c'] = 3
+            self.assertEqual(b, 2)
+            self.assertEqual(c, 3)
+            b = 0
+            c = 0
+        f()
+
+    def test_repr(self):
+        x = 1
+        # Introduce a reference cycle
+        frame = sys._getframe()
+        self.assertEqual(repr(frame.f_locals), repr(dict(frame.f_locals)))
+
+    def test_delete(self):
+        x = 1
+        d = sys._getframe().f_locals
+        with self.assertRaises(TypeError):
+            del d['x']
+
+        with self.assertRaises(AttributeError):
+            d.clear()
+
+        with self.assertRaises(AttributeError):
+            d.pop('x')
+
+    @support.cpython_only
+    def test_sizeof(self):
+        proxy = sys._getframe().f_locals
+        support.check_sizeof(self, proxy, support.calcobjsize("P"))
+
+    def test_unsupport(self):
+        x = 1
+        d = sys._getframe().f_locals
+        with self.assertRaises(AttributeError):
+            d.copy()
+
+        with self.assertRaises(TypeError):
+            copy.copy(d)
+
+        with self.assertRaises(TypeError):
+            copy.deepcopy(d)
+
+
+class TestFrameCApi(unittest.TestCase):
+    def test_basic(self):
+        x = 1
+        ctypes = import_helper.import_module('ctypes')
+        PyEval_GetFrameLocals = ctypes.pythonapi.PyEval_GetFrameLocals
+        PyEval_GetFrameLocals.restype = ctypes.py_object
+        frame_locals = PyEval_GetFrameLocals()
+        self.assertTrue(type(frame_locals), dict)
+        self.assertEqual(frame_locals['x'], 1)
+        frame_locals['x'] = 2
+        self.assertEqual(x, 1)
+
+        PyEval_GetFrameGlobals = ctypes.pythonapi.PyEval_GetFrameGlobals
+        PyEval_GetFrameGlobals.restype = ctypes.py_object
+        frame_globals = PyEval_GetFrameGlobals()
+        self.assertTrue(type(frame_globals), dict)
+        self.assertIs(frame_globals, globals())
+
+        PyEval_GetFrameBuiltins = ctypes.pythonapi.PyEval_GetFrameBuiltins
+        PyEval_GetFrameBuiltins.restype = ctypes.py_object
+        frame_builtins = PyEval_GetFrameBuiltins()
+        self.assertEqual(frame_builtins, __builtins__)
+
+        PyFrame_GetLocals = ctypes.pythonapi.PyFrame_GetLocals
+        PyFrame_GetLocals.argtypes = [ctypes.py_object]
+        PyFrame_GetLocals.restype = ctypes.py_object
+        frame = sys._getframe()
+        f_locals = PyFrame_GetLocals(frame)
+        self.assertTrue(f_locals['x'], 1)
+        f_locals['x'] = 2
+        self.assertEqual(x, 2)
+
+
 class TestIncompleteFrameAreInvisible(unittest.TestCase):
 
     def test_issue95818(self):
index df1debf35210caf3f0533ef8f07d62bb98e31b60..ec2aac81682db8540b639a5c1b20e55f1557b8a5 100644 (file)
@@ -622,9 +622,14 @@ class ListComprehensionTest(unittest.TestCase):
 
     def test_frame_locals(self):
         code = """
-            val = [sys._getframe().f_locals for a in [0]][0]["a"]
+            val = "a" in [sys._getframe().f_locals for a in [0]][0]
         """
         import sys
+        self._check_in_scopes(code, {"val": False}, ns={"sys": sys})
+
+        code = """
+            val = [sys._getframe().f_locals["a"] for a in [0]][0]
+        """
         self._check_in_scopes(code, {"val": 0}, ns={"sys": sys})
 
     def _recursive_replace(self, maybe_code):
index 6989aafd293138f91980f9ffd844eee6aeec4714..6c27ee4db97af3285c2681fed8f7447a054122e3 100644 (file)
@@ -933,23 +933,6 @@ class TestMarkingVariablesAsUnKnown(BytecodeTestCase):
         self.assertNotInBytecode(f, "LOAD_FAST_CHECK")
         return f
 
-    def test_deleting_local_warns_and_assigns_none(self):
-        f = self.make_function_with_no_checks()
-        co_code = f.__code__.co_code
-        def trace(frame, event, arg):
-            if event == 'line' and frame.f_lineno == 4:
-                del frame.f_locals["x"]
-                sys.settrace(None)
-                return None
-            return trace
-        e = r"assigning None to unbound local 'x'"
-        with self.assertWarnsRegex(RuntimeWarning, e):
-            sys.settrace(trace)
-            f()
-        self.assertInBytecode(f, "LOAD_FAST")
-        self.assertNotInBytecode(f, "LOAD_FAST_CHECK")
-        self.assertEqual(f.__code__.co_code, co_code)
-
     def test_modifying_local_does_not_add_check(self):
         f = self.make_function_with_no_checks()
         def trace(frame, event, arg):
index d22698168615e288673f3c925a50c896f4595f66..c06c285c5013a61c512f1c6caeebedf41d9f3c3c 100644 (file)
@@ -227,6 +227,9 @@ SYMBOL_NAMES = (
     "PyEval_EvalFrameEx",
     "PyEval_GetBuiltins",
     "PyEval_GetFrame",
+    "PyEval_GetFrameBuiltins",
+    "PyEval_GetFrameGlobals",
+    "PyEval_GetFrameLocals",
     "PyEval_GetFuncDesc",
     "PyEval_GetFuncName",
     "PyEval_GetGlobals",
index 73912767ae25b74aab99dacf72ad8b5c6ae6af0f..944e84e88c8a35d4852c0f84d07de526f8dedfd6 100644 (file)
@@ -1561,7 +1561,7 @@ class SizeofTest(unittest.TestCase):
         def func():
             return sys._getframe()
         x = func()
-        check(x, size('3Pi3c7P2ic??2P'))
+        check(x, size('3Pi2cP7P2ic??2P'))
         # function
         def func(): pass
         check(func, size('15Pi'))
diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-04-27-21-44-40.gh-issue-74929.C2nESp.rst b/Misc/NEWS.d/next/Core and Builtins/2024-04-27-21-44-40.gh-issue-74929.C2nESp.rst
new file mode 100644 (file)
index 0000000..46e628f
--- /dev/null
@@ -0,0 +1 @@
+Implement PEP 667 - converted ``frame.f_locals`` to a write through proxy
index 5c29e98705aeaf08080665291fc6024f61523a68..77473662aaa76ca849065c4ce3599f9dca5a2c06 100644 (file)
     added = '3.13'
 [function.PyType_GetModuleByDef]
     added = '3.13'
+[function.PyEval_GetFrameBuiltins]
+    added = '3.13'
+[function.PyEval_GetFrameGlobals]
+    added = '3.13'
+[function.PyEval_GetFrameLocals]
+    added = '3.13'
index 36538b1f6d53fe1a4857c56f39944c8266e729ed..8030ecb685367475e9f7a057dde78e5d10ecb1a6 100644 (file)
 
 #define OFF(x) offsetof(PyFrameObject, x)
 
+
+// Returns borrowed reference or NULL
+static PyObject *
+framelocalsproxy_getval(_PyInterpreterFrame *frame, PyCodeObject *co, int i)
+{
+    PyObject **fast = _PyFrame_GetLocalsArray(frame);
+    _PyLocals_Kind kind = _PyLocals_GetKind(co->co_localspluskinds, i);
+
+    PyObject *value = fast[i];
+    PyObject *cell = NULL;
+
+    if (value == NULL) {
+        return NULL;
+    }
+
+    if (kind == CO_FAST_FREE || kind & CO_FAST_CELL) {
+        // The cell was set when the frame was created from
+        // the function's closure.
+        assert(PyCell_Check(value));
+        cell = value;
+    }
+
+    if (cell != NULL) {
+        value = PyCell_GET(cell);
+    }
+
+    if (value == NULL) {
+        return NULL;
+    }
+
+    return value;
+}
+
+static int
+framelocalsproxy_getkeyindex(PyFrameObject *frame, PyObject* key, bool read)
+{
+    /*
+     * Returns the fast locals index of the key
+     *   - if read == true, returns the index if the value is not NULL
+     *   - if read == false, returns the index if the value is not hidden
+     */
+
+    assert(PyUnicode_CheckExact(key));
+
+    PyCodeObject *co = _PyFrame_GetCode(frame->f_frame);
+    int found_key = false;
+
+    // We do 2 loops here because it's highly possible the key is interned
+    // and we can do a pointer comparison.
+    for (int i = 0; i < co->co_nlocalsplus; i++) {
+        PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
+        if (name == key) {
+            found_key = true;
+            if (read) {
+                if (framelocalsproxy_getval(frame->f_frame, co, i) != NULL) {
+                    return i;
+                }
+            } else {
+                if (!(_PyLocals_GetKind(co->co_localspluskinds, i) & CO_FAST_HIDDEN)) {
+                    return i;
+                }
+            }
+        }
+    }
+
+    if (!found_key) {
+        // This is unlikely, but we need to make sure. This means the key
+        // is not interned.
+        for (int i = 0; i < co->co_nlocalsplus; i++) {
+            PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
+            if (_PyUnicode_EQ(name, key)) {
+                if (read) {
+                    if (framelocalsproxy_getval(frame->f_frame, co, i) != NULL) {
+                        return i;
+                    }
+                } else {
+                    if (!(_PyLocals_GetKind(co->co_localspluskinds, i) & CO_FAST_HIDDEN)) {
+                        return i;
+                    }
+                }
+            }
+        }
+    }
+
+    return -1;
+}
+
+static PyObject *
+framelocalsproxy_getitem(PyObject *self, PyObject *key)
+{
+    PyFrameObject* frame = ((PyFrameLocalsProxyObject*)self)->frame;
+    PyCodeObject* co = _PyFrame_GetCode(frame->f_frame);
+
+    if (PyUnicode_CheckExact(key)) {
+        int i = framelocalsproxy_getkeyindex(frame, key, true);
+        if (i >= 0) {
+            PyObject *value = framelocalsproxy_getval(frame->f_frame, co, i);
+            assert(value != NULL);
+            return Py_NewRef(value);
+        }
+    }
+
+    // Okay not in the fast locals, try extra locals
+
+    PyObject *extra = frame->f_extra_locals;
+    if (extra != NULL) {
+        PyObject *value = PyDict_GetItem(extra, key);
+        if (value != NULL) {
+            return Py_NewRef(value);
+        }
+    }
+
+    PyErr_Format(PyExc_KeyError, "local variable '%R' is not defined", key);
+    return NULL;
+}
+
+static int
+framelocalsproxy_setitem(PyObject *self, PyObject *key, PyObject *value)
+{
+    /* Merge locals into fast locals */
+    PyFrameObject* frame = ((PyFrameLocalsProxyObject*)self)->frame;
+    PyObject** fast = _PyFrame_GetLocalsArray(frame->f_frame);
+    PyCodeObject* co = _PyFrame_GetCode(frame->f_frame);
+
+    if (value == NULL) {
+        PyErr_SetString(PyExc_TypeError, "cannot remove variables from FrameLocalsProxy");
+        return -1;
+    }
+
+    if (PyUnicode_CheckExact(key)) {
+        int i = framelocalsproxy_getkeyindex(frame, key, false);
+        if (i >= 0) {
+            _PyLocals_Kind kind = _PyLocals_GetKind(co->co_localspluskinds, i);
+
+            PyObject *oldvalue = fast[i];
+            PyObject *cell = NULL;
+            if (kind == CO_FAST_FREE) {
+                // The cell was set when the frame was created from
+                // the function's closure.
+                assert(oldvalue != NULL && PyCell_Check(oldvalue));
+                cell = oldvalue;
+            } else if (kind & CO_FAST_CELL && oldvalue != NULL) {
+                if (PyCell_Check(oldvalue)) {
+                    cell = oldvalue;
+                }
+            }
+            if (cell != NULL) {
+                oldvalue = PyCell_GET(cell);
+                if (value != oldvalue) {
+                    PyCell_SET(cell, Py_XNewRef(value));
+                    Py_XDECREF(oldvalue);
+                }
+            } else if (value != oldvalue) {
+                Py_XSETREF(fast[i], Py_NewRef(value));
+            }
+            Py_XDECREF(value);
+            return 0;
+        }
+    }
+
+    // Okay not in the fast locals, try extra locals
+
+    PyObject *extra = frame->f_extra_locals;
+
+    if (extra == NULL) {
+        extra = PyDict_New();
+        if (extra == NULL) {
+            return -1;
+        }
+        frame->f_extra_locals = extra;
+    }
+
+    assert(PyDict_Check(extra));
+
+    return PyDict_SetItem(extra, key, value) < 0;
+}
+
+static int
+framelocalsproxy_merge(PyObject* self, PyObject* other)
+{
+    if (!PyDict_Check(other) && !PyFrameLocalsProxy_Check(other)) {
+        return -1;
+    }
+
+    PyObject *keys = PyMapping_Keys(other);
+    PyObject *iter = NULL;
+    PyObject *key = NULL;
+    PyObject *value = NULL;
+
+    assert(keys != NULL);
+
+    iter = PyObject_GetIter(keys);
+    Py_DECREF(keys);
+
+    if (iter == NULL) {
+        return -1;
+    }
+
+    while ((key = PyIter_Next(iter)) != NULL) {
+        value = PyObject_GetItem(other, key);
+        if (value == NULL) {
+            Py_DECREF(key);
+            Py_DECREF(iter);
+            return -1;
+        }
+
+        if (framelocalsproxy_setitem(self, key, value) < 0) {
+            Py_DECREF(key);
+            Py_DECREF(value);
+            Py_DECREF(iter);
+            return -1;
+        }
+
+        Py_DECREF(key);
+        Py_DECREF(value);
+    }
+
+    return 0;
+}
+
+static PyObject *
+framelocalsproxy_keys(PyObject *self, PyObject *__unused)
+{
+    PyObject *names = PyList_New(0);
+    PyFrameObject *frame = ((PyFrameLocalsProxyObject*)self)->frame;
+    PyCodeObject *co = _PyFrame_GetCode(frame->f_frame);
+
+    for (int i = 0; i < co->co_nlocalsplus; i++) {
+        PyObject *val = framelocalsproxy_getval(frame->f_frame, co, i);
+        if (val) {
+            PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
+            PyList_Append(names, name);
+        }
+    }
+
+    // Iterate through the extra locals
+    Py_ssize_t i = 0;
+    PyObject *key = NULL;
+    PyObject *value = NULL;
+
+    if (frame->f_extra_locals) {
+        assert(PyDict_Check(frame->f_extra_locals));
+        while (PyDict_Next(frame->f_extra_locals, &i, &key, &value)) {
+            PyList_Append(names, key);
+        }
+    }
+
+    return names;
+}
+
+static void
+framelocalsproxy_dealloc(PyObject *self)
+{
+    PyObject_GC_UnTrack(self);
+    Py_CLEAR(((PyFrameLocalsProxyObject*)self)->frame);
+    Py_TYPE(self)->tp_free(self);
+}
+
+static PyObject *
+framelocalsproxy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+    PyFrameLocalsProxyObject *self = (PyFrameLocalsProxyObject *)type->tp_alloc(type, 0);
+    if (self == NULL) {
+        return NULL;
+    }
+
+    PyFrameObject *frame = (PyFrameObject*)PyTuple_GET_ITEM(args, 0);
+    assert(PyFrame_Check(frame));
+
+    ((PyFrameLocalsProxyObject*)self)->frame = (PyFrameObject*)Py_NewRef(frame);
+
+    return (PyObject *)self;
+}
+
+static int
+framelocalsproxy_tp_clear(PyObject *self)
+{
+    Py_CLEAR(((PyFrameLocalsProxyObject*)self)->frame);
+    return 0;
+}
+
+static int
+framelocalsproxy_visit(PyObject *self, visitproc visit, void *arg)
+{
+    Py_VISIT(((PyFrameLocalsProxyObject*)self)->frame);
+    return 0;
+}
+
+static PyObject *
+framelocalsproxy_iter(PyObject *self)
+{
+    return PyObject_GetIter(framelocalsproxy_keys(self, NULL));
+}
+
+static PyObject *
+framelocalsproxy_richcompare(PyObject *self, PyObject *other, int op)
+{
+    if (PyFrameLocalsProxy_Check(other)) {
+        bool result = ((PyFrameLocalsProxyObject*)self)->frame == ((PyFrameLocalsProxyObject*)other)->frame;
+        if (op == Py_EQ) {
+            return PyBool_FromLong(result);
+        } else if (op == Py_NE) {
+            return PyBool_FromLong(!result);
+        }
+    } else if (PyDict_Check(other)) {
+        PyObject *dct = PyDict_New();
+        PyObject *result = NULL;
+        PyDict_Update(dct, self);
+        result = PyObject_RichCompare(dct, other, op);
+        Py_DECREF(dct);
+        return result;
+    }
+
+    Py_RETURN_NOTIMPLEMENTED;
+}
+
+static PyObject *
+framelocalsproxy_repr(PyObject *self)
+{
+    int i = Py_ReprEnter(self);
+    if (i != 0) {
+        return i > 0 ? PyUnicode_FromString("{...}") : NULL;
+    }
+
+    PyObject *dct = PyDict_New();
+    PyObject *repr = NULL;
+
+    if (PyDict_Update(dct, self) == 0) {
+        repr = PyObject_Repr(dct);
+    }
+    Py_ReprLeave(self);
+
+    Py_DECREF(dct);
+    return repr;
+}
+
+static PyObject*
+framelocalsproxy_or(PyObject *self, PyObject *other)
+{
+    if (!PyDict_Check(other) && !PyFrameLocalsProxy_Check(other)) {
+        Py_RETURN_NOTIMPLEMENTED;
+    }
+
+    PyObject *result = PyDict_New();
+    if (PyDict_Update(result, self) < 0) {
+        Py_DECREF(result);
+        return NULL;
+    }
+
+    if (PyDict_Update(result, other) < 0) {
+        Py_DECREF(result);
+        return NULL;
+    }
+
+    return result;
+}
+
+static PyObject*
+framelocalsproxy_inplace_or(PyObject *self, PyObject *other)
+{
+    if (!PyDict_Check(other) && !PyFrameLocalsProxy_Check(other)) {
+        Py_RETURN_NOTIMPLEMENTED;
+    }
+
+    if (framelocalsproxy_merge(self, other) < 0) {
+        Py_RETURN_NOTIMPLEMENTED;
+    }
+
+    return Py_NewRef(self);
+}
+
+static PyObject*
+framelocalsproxy_values(PyObject *self, PyObject *__unused)
+{
+    PyObject *values = PyList_New(0);
+    PyFrameObject *frame = ((PyFrameLocalsProxyObject*)self)->frame;
+    PyCodeObject *co = _PyFrame_GetCode(frame->f_frame);
+
+    for (int i = 0; i < co->co_nlocalsplus; i++) {
+        PyObject *value = framelocalsproxy_getval(frame->f_frame, co, i);
+        if (value) {
+            PyList_Append(values, value);
+        }
+    }
+
+    // Iterate through the extra locals
+    Py_ssize_t j = 0;
+    PyObject *key = NULL;
+    PyObject *value = NULL;
+
+    if (frame->f_extra_locals) {
+        while (PyDict_Next(frame->f_extra_locals, &j, &key, &value)) {
+            PyList_Append(values, value);
+        }
+    }
+
+    return values;
+}
+
+static PyObject *
+framelocalsproxy_items(PyObject *self, PyObject *__unused)
+{
+    PyObject *items = PyList_New(0);
+    PyFrameObject *frame = ((PyFrameLocalsProxyObject*)self)->frame;
+    PyCodeObject *co = _PyFrame_GetCode(frame->f_frame);
+
+    for (int i = 0; i < co->co_nlocalsplus; i++) {
+        PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
+        PyObject *value = framelocalsproxy_getval(frame->f_frame, co, i);
+
+        if (value) {
+            PyObject *pair = PyTuple_Pack(2, name, value);
+            PyList_Append(items, pair);
+            Py_DECREF(pair);
+        }
+    }
+
+    // Iterate through the extra locals
+    Py_ssize_t j = 0;
+    PyObject *key = NULL;
+    PyObject *value = NULL;
+
+    if (frame->f_extra_locals) {
+        while (PyDict_Next(frame->f_extra_locals, &j, &key, &value)) {
+            PyObject *pair = PyTuple_Pack(2, key, value);
+            PyList_Append(items, pair);
+            Py_DECREF(pair);
+        }
+    }
+
+    return items;
+}
+
+static Py_ssize_t
+framelocalsproxy_length(PyObject *self)
+{
+    PyFrameObject *frame = ((PyFrameLocalsProxyObject*)self)->frame;
+    PyCodeObject *co = _PyFrame_GetCode(frame->f_frame);
+    Py_ssize_t size = 0;
+
+    if (frame->f_extra_locals != NULL) {
+        assert(PyDict_Check(frame->f_extra_locals));
+        size += PyDict_Size(frame->f_extra_locals);
+    }
+
+    for (int i = 0; i < co->co_nlocalsplus; i++) {
+        if (framelocalsproxy_getval(frame->f_frame, co, i) != NULL) {
+            size++;
+        }
+    }
+    return size;
+}
+
+static PyObject*
+framelocalsproxy_contains(PyObject *self, PyObject *key)
+{
+    PyFrameObject *frame = ((PyFrameLocalsProxyObject*)self)->frame;
+
+    if (PyUnicode_CheckExact(key)) {
+        int i = framelocalsproxy_getkeyindex(frame, key, true);
+        if (i >= 0) {
+            Py_RETURN_TRUE;
+        }
+    }
+
+    PyObject *extra = ((PyFrameObject*)frame)->f_extra_locals;
+    if (extra != NULL) {
+        int result = PyDict_Contains(extra, key);
+        if (result < 0) {
+            return NULL;
+        } else if (result > 0) {
+            Py_RETURN_TRUE;
+        }
+    }
+
+    Py_RETURN_FALSE;
+}
+
+static PyObject*
+framelocalsproxy_update(PyObject *self, PyObject *other)
+{
+    if (framelocalsproxy_merge(self, other) < 0) {
+        PyErr_SetString(PyExc_TypeError, "update() argument must be dict or another FrameLocalsProxy");
+        return NULL;
+    }
+
+    Py_RETURN_NONE;
+}
+
+static PyObject*
+framelocalsproxy_get(PyObject* self, PyObject *const *args, Py_ssize_t nargs)
+{
+    if (nargs < 1 || nargs > 2) {
+        PyErr_SetString(PyExc_TypeError, "get expected 1 or 2 arguments");
+        return NULL;
+    }
+
+    PyObject *key = args[0];
+    PyObject *default_value = Py_None;
+
+    if (nargs == 2) {
+        default_value = args[1];
+    }
+
+    PyObject *result = framelocalsproxy_getitem(self, key);
+
+    if (result == NULL) {
+        if (PyErr_ExceptionMatches(PyExc_KeyError)) {
+            PyErr_Clear();
+            return Py_XNewRef(default_value);
+        }
+        return NULL;
+    }
+
+    return result;
+}
+
+static PyObject*
+framelocalsproxy_setdefault(PyObject* self, PyObject *const *args, Py_ssize_t nargs)
+{
+    if (nargs < 1 || nargs > 2) {
+        PyErr_SetString(PyExc_TypeError, "setdefault expected 1 or 2 arguments");
+        return NULL;
+    }
+
+    PyObject *key = args[0];
+    PyObject *default_value = Py_None;
+
+    if (nargs == 2) {
+        default_value = args[1];
+    }
+
+    PyObject *result = framelocalsproxy_getitem(self, key);
+
+    if (result == NULL) {
+        if (PyErr_ExceptionMatches(PyExc_KeyError)) {
+            PyErr_Clear();
+            if (framelocalsproxy_setitem(self, key, default_value) < 0) {
+                return NULL;
+            }
+            return Py_XNewRef(default_value);
+        }
+        return NULL;
+    }
+
+    return result;
+}
+
+static PyObject*
+framelocalsproxy_reversed(PyObject *self, PyObject *__unused)
+{
+    PyObject *result = framelocalsproxy_keys(self, NULL);
+    if (PyList_Reverse(result) < 0) {
+        Py_DECREF(result);
+        return NULL;
+    }
+    return result;
+}
+
+static PyNumberMethods framelocalsproxy_as_number = {
+    .nb_or = framelocalsproxy_or,
+    .nb_inplace_or = framelocalsproxy_inplace_or,
+};
+
+static PyMappingMethods framelocalsproxy_as_mapping = {
+    framelocalsproxy_length, // mp_length
+    framelocalsproxy_getitem, // mp_subscript
+    framelocalsproxy_setitem, // mp_ass_subscript
+};
+
+static PyMethodDef framelocalsproxy_methods[] = {
+    {"__contains__",  framelocalsproxy_contains,          METH_O | METH_COEXIST,
+     NULL},
+    {"__getitem__",   framelocalsproxy_getitem,           METH_O | METH_COEXIST,
+     NULL},
+    {"__reversed__",  framelocalsproxy_reversed,          METH_NOARGS,
+     NULL},
+    {"keys",          framelocalsproxy_keys,              METH_NOARGS,
+     NULL},
+    {"values",        framelocalsproxy_values,            METH_NOARGS,
+     NULL},
+    {"items",         framelocalsproxy_items,             METH_NOARGS,
+     NULL},
+    {"update",        framelocalsproxy_update,            METH_O,
+     NULL},
+    {"get",        _PyCFunction_CAST(framelocalsproxy_get),             METH_FASTCALL,
+     NULL},
+    {"setdefault", _PyCFunction_CAST(framelocalsproxy_setdefault),      METH_FASTCALL,
+     NULL},
+    {NULL,            NULL}   /* sentinel */
+};
+
+PyTypeObject PyFrameLocalsProxy_Type = {
+    PyVarObject_HEAD_INIT(&PyType_Type, 0)
+    .tp_name = "FrameLocalsProxy",
+    .tp_basicsize = sizeof(PyFrameLocalsProxyObject),
+    .tp_dealloc = (destructor)framelocalsproxy_dealloc,
+    .tp_repr = &framelocalsproxy_repr,
+    .tp_as_number = &framelocalsproxy_as_number,
+    .tp_as_mapping = &framelocalsproxy_as_mapping,
+    .tp_getattro = PyObject_GenericGetAttr,
+    .tp_setattro = PyObject_GenericSetAttr,
+    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
+    .tp_traverse = framelocalsproxy_visit,
+    .tp_clear = framelocalsproxy_tp_clear,
+    .tp_richcompare = framelocalsproxy_richcompare,
+    .tp_iter = framelocalsproxy_iter,
+    .tp_methods = framelocalsproxy_methods,
+    .tp_alloc = PyType_GenericAlloc,
+    .tp_new = framelocalsproxy_new,
+    .tp_free = PyObject_GC_Del,
+};
+
+PyObject *
+_PyFrameLocalsProxy_New(PyFrameObject *frame)
+{
+    PyObject* args = PyTuple_Pack(1, frame);
+    PyObject* proxy = (PyObject*)framelocalsproxy_new(&PyFrameLocalsProxy_Type, args, NULL);
+    Py_DECREF(args);
+    return proxy;
+}
+
 static PyMemberDef frame_memberlist[] = {
     {"f_trace_lines",   Py_T_BOOL,         OFF(f_trace_lines), 0},
     {NULL}      /* Sentinel */
 };
 
-
 static PyObject *
 frame_getlocals(PyFrameObject *f, void *closure)
 {
@@ -30,11 +651,14 @@ frame_getlocals(PyFrameObject *f, void *closure)
         return NULL;
     }
     assert(!_PyFrame_IsIncomplete(f->f_frame));
-    PyObject *locals = _PyFrame_GetLocals(f->f_frame, 1);
-    if (locals) {
-        f->f_fast_as_locals = 1;
+
+    PyCodeObject *co = _PyFrame_GetCode(f->f_frame);
+
+    if (!(co->co_flags & CO_OPTIMIZED) && !_PyFrame_HasHiddenLocals(f->f_frame)) {
+        return Py_NewRef(f->f_frame->f_locals);
     }
-    return locals;
+
+    return _PyFrameLocalsProxy_New(f);
 }
 
 int
@@ -595,20 +1219,6 @@ first_line_not_before(int *lines, int len, int line)
     return result;
 }
 
-static bool
-frame_is_cleared(PyFrameObject *frame)
-{
-    assert(!_PyFrame_IsIncomplete(frame->f_frame));
-    if (frame->f_frame->stacktop == 0) {
-        return true;
-    }
-    if (frame->f_frame->owner == FRAME_OWNED_BY_GENERATOR) {
-        PyGenObject *gen = _PyFrame_GetGenerator(frame->f_frame);
-        return gen->gi_frame_state == FRAME_CLEARED;
-    }
-    return false;
-}
-
 static bool frame_is_suspended(PyFrameObject *frame)
 {
     assert(!_PyFrame_IsIncomplete(frame->f_frame));
@@ -900,6 +1510,7 @@ frame_dealloc(PyFrameObject *f)
     }
     Py_CLEAR(f->f_back);
     Py_CLEAR(f->f_trace);
+    Py_CLEAR(f->f_extra_locals);
     PyObject_GC_Del(f);
     Py_XDECREF(co);
     Py_TRASHCAN_END;
@@ -910,6 +1521,7 @@ frame_traverse(PyFrameObject *f, visitproc visit, void *arg)
 {
     Py_VISIT(f->f_back);
     Py_VISIT(f->f_trace);
+    Py_VISIT(f->f_extra_locals);
     if (f->f_frame->owner != FRAME_OWNED_BY_FRAME_OBJECT) {
         return 0;
     }
@@ -921,6 +1533,7 @@ static int
 frame_tp_clear(PyFrameObject *f)
 {
     Py_CLEAR(f->f_trace);
+    Py_CLEAR(f->f_extra_locals);
 
     /* locals and stack */
     PyObject **locals = _PyFrame_GetLocalsArray(f->f_frame);
@@ -1056,8 +1669,8 @@ _PyFrame_New_NoTrack(PyCodeObject *code)
     f->f_trace = NULL;
     f->f_trace_lines = 1;
     f->f_trace_opcodes = 0;
-    f->f_fast_as_locals = 0;
     f->f_lineno = 0;
+    f->f_extra_locals = NULL;
     return f;
 }
 
@@ -1204,103 +1817,45 @@ frame_get_var(_PyInterpreterFrame *frame, PyCodeObject *co, int i,
 }
 
 
-PyObject *
-_PyFrame_GetLocals(_PyInterpreterFrame *frame, int include_hidden)
+bool
+_PyFrame_HasHiddenLocals(_PyInterpreterFrame *frame)
 {
-    /* Merge fast locals into f->f_locals */
-    PyObject *locals = frame->f_locals;
-    if (locals == NULL) {
-        locals = frame->f_locals = PyDict_New();
-        if (locals == NULL) {
-            return NULL;
-        }
-    }
-    PyObject *hidden = NULL;
-
-    /* If include_hidden, "hidden" fast locals (from inlined comprehensions in
-       module/class scopes) will be included in the returned dict, but not in
-       frame->f_locals; the returned dict will be a modified copy. Non-hidden
-       locals will still be updated in frame->f_locals. */
-    if (include_hidden) {
-        hidden = PyDict_New();
-        if (hidden == NULL) {
-            return NULL;
-        }
-    }
-
-    frame_init_get_vars(frame);
+    /*
+     * This function returns if there are hidden locals introduced by PEP 709,
+     * which are the isolated fast locals for inline comprehensions
+     */
+    PyCodeObject* co = _PyFrame_GetCode(frame);
 
-    PyCodeObject *co = _PyFrame_GetCode(frame);
     for (int i = 0; i < co->co_nlocalsplus; i++) {
-        PyObject *value;  // borrowed reference
-        if (!frame_get_var(frame, co, i, &value)) {
-            continue;
-        }
-
-        PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
         _PyLocals_Kind kind = _PyLocals_GetKind(co->co_localspluskinds, i);
+
         if (kind & CO_FAST_HIDDEN) {
-            if (include_hidden && value != NULL) {
-                if (PyObject_SetItem(hidden, name, value) != 0) {
-                    goto error;
-                }
-            }
-            continue;
-        }
-        if (value == NULL) {
-            if (PyObject_DelItem(locals, name) != 0) {
-                if (PyErr_ExceptionMatches(PyExc_KeyError)) {
-                    PyErr_Clear();
-                }
-                else {
-                    goto error;
-                }
-            }
-        }
-        else {
-            if (PyObject_SetItem(locals, name, value) != 0) {
-                goto error;
-            }
-        }
-    }
+            PyObject* value = framelocalsproxy_getval(frame, co, i);
 
-    if (include_hidden && PyDict_Size(hidden)) {
-        PyObject *innerlocals = PyDict_New();
-        if (innerlocals == NULL) {
-            goto error;
-        }
-        if (PyDict_Merge(innerlocals, locals, 1) != 0) {
-            Py_DECREF(innerlocals);
-            goto error;
-        }
-        if (PyDict_Merge(innerlocals, hidden, 1) != 0) {
-            Py_DECREF(innerlocals);
-            goto error;
+            if (value != NULL) {
+                return true;
+            }
         }
-        locals = innerlocals;
-    }
-    else {
-        Py_INCREF(locals);
     }
-    Py_CLEAR(hidden);
 
-    return locals;
-
-  error:
-    Py_XDECREF(hidden);
-    return NULL;
+    return false;
 }
 
 
-int
-_PyFrame_FastToLocalsWithError(_PyInterpreterFrame *frame)
+PyObject *
+_PyFrame_GetLocals(_PyInterpreterFrame *frame)
 {
-    PyObject *locals = _PyFrame_GetLocals(frame, 0);
-    if (locals == NULL) {
-        return -1;
+    // We should try to avoid creating the FrameObject if possible.
+    // So we check if the frame is a module or class level scope
+    PyCodeObject *co = _PyFrame_GetCode(frame);
+
+    if (!(co->co_flags & CO_OPTIMIZED) && !_PyFrame_HasHiddenLocals(frame)) {
+        return Py_NewRef(frame->f_locals);
     }
-    Py_DECREF(locals);
-    return 0;
+
+    PyFrameObject* f = _PyFrame_GetFrameObject(frame);
+
+    return _PyFrameLocalsProxy_New(f);
 }
 
 
@@ -1354,112 +1909,19 @@ PyFrame_GetVarString(PyFrameObject *frame, const char *name)
 int
 PyFrame_FastToLocalsWithError(PyFrameObject *f)
 {
-    if (f == NULL) {
-        PyErr_BadInternalCall();
-        return -1;
-    }
-    assert(!_PyFrame_IsIncomplete(f->f_frame));
-    int err = _PyFrame_FastToLocalsWithError(f->f_frame);
-    if (err == 0) {
-        f->f_fast_as_locals = 1;
-    }
-    return err;
+    return 0;
 }
 
 void
 PyFrame_FastToLocals(PyFrameObject *f)
 {
-    int res;
-    assert(!_PyFrame_IsIncomplete(f->f_frame));
-    assert(!PyErr_Occurred());
-
-    res = PyFrame_FastToLocalsWithError(f);
-    if (res < 0)
-        PyErr_Clear();
-}
-
-void
-_PyFrame_LocalsToFast(_PyInterpreterFrame *frame, int clear)
-{
-    /* Merge locals into fast locals */
-    PyObject *locals;
-    PyObject **fast;
-    PyCodeObject *co;
-    locals = frame->f_locals;
-    if (locals == NULL) {
-        return;
-    }
-    fast = _PyFrame_GetLocalsArray(frame);
-    co = _PyFrame_GetCode(frame);
-
-    PyObject *exc = PyErr_GetRaisedException();
-    for (int i = 0; i < co->co_nlocalsplus; i++) {
-        _PyLocals_Kind kind = _PyLocals_GetKind(co->co_localspluskinds, i);
-
-        /* Same test as in PyFrame_FastToLocals() above. */
-        if (kind & CO_FAST_FREE && !(co->co_flags & CO_OPTIMIZED)) {
-            continue;
-        }
-        PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
-        PyObject *value = PyObject_GetItem(locals, name);
-        /* We only care about NULLs if clear is true. */
-        if (value == NULL) {
-            PyErr_Clear();
-            if (!clear) {
-                continue;
-            }
-        }
-        PyObject *oldvalue = fast[i];
-        PyObject *cell = NULL;
-        if (kind == CO_FAST_FREE) {
-            // The cell was set when the frame was created from
-            // the function's closure.
-            assert(oldvalue != NULL && PyCell_Check(oldvalue));
-            cell = oldvalue;
-        }
-        else if (kind & CO_FAST_CELL && oldvalue != NULL) {
-            /* Same test as in PyFrame_FastToLocals() above. */
-            if (PyCell_Check(oldvalue) &&
-                    _PyFrame_OpAlreadyRan(frame, MAKE_CELL, i)) {
-                // (likely) MAKE_CELL must have executed already.
-                cell = oldvalue;
-            }
-            // (unlikely) Otherwise, it must have been set to some
-            // initial value by an earlier call to PyFrame_LocalsToFast().
-        }
-        if (cell != NULL) {
-            oldvalue = PyCell_GET(cell);
-            if (value != oldvalue) {
-                PyCell_SET(cell, Py_XNewRef(value));
-                Py_XDECREF(oldvalue);
-            }
-        }
-        else if (value != oldvalue) {
-            if (value == NULL) {
-                // Probably can't delete this, since the compiler's flow
-                // analysis may have already "proven" that it exists here:
-                const char *e = "assigning None to unbound local %R";
-                if (PyErr_WarnFormat(PyExc_RuntimeWarning, 0, e, name)) {
-                    // It's okay if frame_obj is NULL, just try anyways:
-                    PyErr_WriteUnraisable((PyObject *)frame->frame_obj);
-                }
-                value = Py_NewRef(Py_None);
-            }
-            Py_XSETREF(fast[i], Py_NewRef(value));
-        }
-        Py_XDECREF(value);
-    }
-    PyErr_SetRaisedException(exc);
+    return;
 }
 
 void
 PyFrame_LocalsToFast(PyFrameObject *f, int clear)
 {
-    assert(!_PyFrame_IsIncomplete(f->f_frame));
-    if (f && f->f_fast_as_locals && !frame_is_cleared(f)) {
-        _PyFrame_LocalsToFast(f->f_frame, clear);
-        f->f_fast_as_locals = 0;
-    }
+    return;
 }
 
 int
index 79e4fb4dbbf7c69ac020f861041679e235a0f01d..1bf0e65ec60ce4b53d145be6709df468ac0ab91e 100644 (file)
@@ -2235,6 +2235,7 @@ static PyTypeObject* static_types[] = {
     &PyFilter_Type,
     &PyFloat_Type,
     &PyFrame_Type,
+    &PyFrameLocalsProxy_Type,
     &PyFrozenSet_Type,
     &PyFunction_Type,
     &PyGen_Type,
index c6fdc0bd73b9fe79cc6b7373efe0bd1a89515790..86c888430891c9aff9cea669952dbad917057289 100755 (executable)
@@ -253,6 +253,9 @@ EXPORT_FUNC(PyEval_EvalFrame)
 EXPORT_FUNC(PyEval_EvalFrameEx)
 EXPORT_FUNC(PyEval_GetBuiltins)
 EXPORT_FUNC(PyEval_GetFrame)
+EXPORT_FUNC(PyEval_GetFrameBuiltins)
+EXPORT_FUNC(PyEval_GetFrameGlobals)
+EXPORT_FUNC(PyEval_GetFrameLocals)
 EXPORT_FUNC(PyEval_GetFuncDesc)
 EXPORT_FUNC(PyEval_GetFuncName)
 EXPORT_FUNC(PyEval_GetGlobals)
index 3626ffbd02ff401c9115a132632dcef6716710a1..0d02a9887bef7ad78f353a1e5e42965c4acb4f9c 100644 (file)
@@ -2475,12 +2475,7 @@ PyEval_GetLocals(void)
         return NULL;
     }
 
-    if (_PyFrame_FastToLocalsWithError(current_frame) < 0) {
-        return NULL;
-    }
-
-    PyObject *locals = current_frame->f_locals;
-    assert(locals != NULL);
+    PyObject *locals = _PyEval_GetFrameLocals();
     return locals;
 }
 
@@ -2494,7 +2489,24 @@ _PyEval_GetFrameLocals(void)
         return NULL;
     }
 
-    return _PyFrame_GetLocals(current_frame, 1);
+    PyObject *locals = _PyFrame_GetLocals(current_frame);
+    if (locals == NULL) {
+        return NULL;
+    }
+
+    if (PyFrameLocalsProxy_Check(locals)) {
+        PyObject* ret = PyDict_New();
+        if (PyDict_Update(ret, locals)) {
+            Py_DECREF(ret);
+            return NULL;
+        }
+        Py_DECREF(locals);
+        return ret;
+    } else if (PyMapping_Check(locals)) {
+        return locals;
+    }
+
+    return NULL;
 }
 
 PyObject *
@@ -2508,6 +2520,28 @@ PyEval_GetGlobals(void)
     return current_frame->f_globals;
 }
 
+PyObject*
+PyEval_GetFrameLocals(void)
+{
+    return _PyEval_GetFrameLocals();
+}
+
+PyObject* PyEval_GetFrameGlobals(void)
+{
+    PyThreadState *tstate = _PyThreadState_GET();
+    _PyInterpreterFrame *current_frame = _PyThreadState_GetFrame(tstate);
+    if (current_frame == NULL) {
+        return NULL;
+    }
+    return Py_XNewRef(current_frame->f_globals);
+}
+
+PyObject* PyEval_GetFrameBuiltins(void)
+{
+    PyThreadState *tstate = _PyThreadState_GET();
+    return Py_XNewRef(_PyEval_GetBuiltins(tstate));
+}
+
 int
 PyEval_MergeCompilerFlags(PyCompilerFlags *cf)
 {
index 5b10c3ce7d5d775d1c26e154d7980497c33b3d90..a6b2c108b671756ba94f144aa8a077d81f9d3013 100644 (file)
@@ -123,18 +123,15 @@ static PyObject *
 import_star(PyThreadState* tstate, PyObject *from)
 {
     _PyInterpreterFrame *frame = tstate->current_frame;
-    if (_PyFrame_FastToLocalsWithError(frame) < 0) {
-        return NULL;
-    }
 
-    PyObject *locals = frame->f_locals;
+    PyObject *locals = _PyFrame_GetLocals(frame);
     if (locals == NULL) {
         _PyErr_SetString(tstate, PyExc_SystemError,
                             "no locals found during 'import *'");
         return NULL;
     }
     int err = import_all_from(tstate, locals, from);
-    _PyFrame_LocalsToFast(frame, 0);
+    Py_DECREF(locals);
     if (err < 0) {
         return NULL;
     }
index 645b76fccf602c5816d32ad53fafcb9cf6a80589..bd7f821931da42443f0dcb7bde50b721686f24af 100644 (file)
@@ -1022,13 +1022,6 @@ static PyObject *
 call_trampoline(PyThreadState *tstate, PyObject* callback,
                 PyFrameObject *frame, int what, PyObject *arg)
 {
-    /* Discard any previous modifications the frame's fast locals */
-    if (frame->f_fast_as_locals) {
-        if (PyFrame_FastToLocalsWithError(frame) < 0) {
-            return NULL;
-        }
-    }
-
     /* call the Python-level function */
     if (arg == NULL) {
         arg = Py_None;
@@ -1036,7 +1029,6 @@ call_trampoline(PyThreadState *tstate, PyObject* callback,
     PyObject *args[3] = {(PyObject *)frame, whatstrings[what], arg};
     PyObject *result = _PyObject_VectorcallTstate(tstate, callback, args, 3, NULL);
 
-    PyFrame_LocalsToFast(frame, 1);
     return result;
 }
 
index 285129fd361665f0db746a97be6cf87493480bde..1b8cccf80872c8cd9cc7f580f49370b06fae2ffb 100644 (file)
@@ -43,6 +43,7 @@ Objects/enumobject.c  -       PyReversed_Type -
 Objects/fileobject.c   -       PyStdPrinter_Type       -
 Objects/floatobject.c  -       PyFloat_Type    -
 Objects/frameobject.c  -       PyFrame_Type    -
+Objects/frameobject.c  -       PyFrameLocalsProxy_Type -
 Objects/funcobject.c   -       PyClassMethod_Type      -
 Objects/funcobject.c   -       PyFunction_Type -
 Objects/funcobject.c   -       PyStaticMethod_Type     -