Skip to content

Commit 5b7a872

Browse files
gh-125590: Allow FrameLocalsProxy to delete and pop keys from extra locals (#125616)
1 parent 3d1df3d commit 5b7a872

File tree

3 files changed

+99
-8
lines changed

3 files changed

+99
-8
lines changed

Lib/test/test_frame.py

+28-2
Original file line numberDiff line numberDiff line change
@@ -397,15 +397,41 @@ def test_repr(self):
397397
def test_delete(self):
398398
x = 1
399399
d = sys._getframe().f_locals
400-
with self.assertRaises(TypeError):
400+
401+
# This needs to be tested before f_extra_locals is created
402+
with self.assertRaisesRegex(KeyError, 'non_exist'):
403+
del d['non_exist']
404+
405+
with self.assertRaises(KeyError):
406+
d.pop('non_exist')
407+
408+
with self.assertRaisesRegex(ValueError, 'local variables'):
401409
del d['x']
402410

403411
with self.assertRaises(AttributeError):
404412
d.clear()
405413

406-
with self.assertRaises(AttributeError):
414+
with self.assertRaises(ValueError):
407415
d.pop('x')
408416

417+
with self.assertRaises(ValueError):
418+
d.pop('x', None)
419+
420+
# 'm', 'n' is stored in f_extra_locals
421+
d['m'] = 1
422+
d['n'] = 1
423+
424+
with self.assertRaises(KeyError):
425+
d.pop('non_exist')
426+
427+
del d['m']
428+
self.assertEqual(d.pop('n'), 1)
429+
430+
self.assertNotIn('m', d)
431+
self.assertNotIn('n', d)
432+
433+
self.assertEqual(d.pop('n', 2), 2)
434+
409435
@support.cpython_only
410436
def test_sizeof(self):
411437
proxy = sys._getframe().f_locals
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Allow ``FrameLocalsProxy`` to delete and pop if the key is not a fast variable.

Objects/frameobject.c

+70-6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "pycore_code.h" // CO_FAST_LOCAL, etc.
66
#include "pycore_function.h" // _PyFunction_FromConstructor()
77
#include "pycore_moduleobject.h" // _PyModule_GetDict()
8+
#include "pycore_modsupport.h" // _PyArg_CheckPositional()
89
#include "pycore_object.h" // _PyObject_GC_UNTRACK()
910
#include "pycore_opcode_metadata.h" // _PyOpcode_Deopt, _PyOpcode_Caches
1011

@@ -158,16 +159,16 @@ framelocalsproxy_setitem(PyObject *self, PyObject *key, PyObject *value)
158159
_PyStackRef *fast = _PyFrame_GetLocalsArray(frame->f_frame);
159160
PyCodeObject *co = _PyFrame_GetCode(frame->f_frame);
160161

161-
if (value == NULL) {
162-
PyErr_SetString(PyExc_TypeError, "cannot remove variables from FrameLocalsProxy");
163-
return -1;
164-
}
165-
166162
int i = framelocalsproxy_getkeyindex(frame, key, false);
167163
if (i == -2) {
168164
return -1;
169165
}
170166
if (i >= 0) {
167+
if (value == NULL) {
168+
PyErr_SetString(PyExc_ValueError, "cannot remove local variables from FrameLocalsProxy");
169+
return -1;
170+
}
171+
171172
_Py_Executors_InvalidateDependency(PyInterpreterState_Get(), co, 1);
172173

173174
_PyLocals_Kind kind = _PyLocals_GetKind(co->co_localspluskinds, i);
@@ -202,6 +203,10 @@ framelocalsproxy_setitem(PyObject *self, PyObject *key, PyObject *value)
202203
PyObject *extra = frame->f_extra_locals;
203204

204205
if (extra == NULL) {
206+
if (value == NULL) {
207+
_PyErr_SetKeyError(key);
208+
return -1;
209+
}
205210
extra = PyDict_New();
206211
if (extra == NULL) {
207212
return -1;
@@ -211,7 +216,11 @@ framelocalsproxy_setitem(PyObject *self, PyObject *key, PyObject *value)
211216

212217
assert(PyDict_Check(extra));
213218

214-
return PyDict_SetItem(extra, key, value);
219+
if (value == NULL) {
220+
return PyDict_DelItem(extra, key);
221+
} else {
222+
return PyDict_SetItem(extra, key, value);
223+
}
215224
}
216225

217226
static int
@@ -676,6 +685,59 @@ framelocalsproxy_setdefault(PyObject* self, PyObject *const *args, Py_ssize_t na
676685
return result;
677686
}
678687

688+
static PyObject*
689+
framelocalsproxy_pop(PyObject* self, PyObject *const *args, Py_ssize_t nargs)
690+
{
691+
if (!_PyArg_CheckPositional("pop", nargs, 1, 2)) {
692+
return NULL;
693+
}
694+
695+
PyObject *key = args[0];
696+
PyObject *default_value = NULL;
697+
698+
if (nargs == 2) {
699+
default_value = args[1];
700+
}
701+
702+
PyFrameObject *frame = ((PyFrameLocalsProxyObject*)self)->frame;
703+
704+
int i = framelocalsproxy_getkeyindex(frame, key, false);
705+
if (i == -2) {
706+
return NULL;
707+
}
708+
709+
if (i >= 0) {
710+
PyErr_SetString(PyExc_ValueError, "cannot remove local variables from FrameLocalsProxy");
711+
return NULL;
712+
}
713+
714+
PyObject *result = NULL;
715+
716+
if (frame->f_extra_locals == NULL) {
717+
if (default_value != NULL) {
718+
return Py_XNewRef(default_value);
719+
} else {
720+
_PyErr_SetKeyError(key);
721+
return NULL;
722+
}
723+
}
724+
725+
if (PyDict_Pop(frame->f_extra_locals, key, &result) < 0) {
726+
return NULL;
727+
}
728+
729+
if (result == NULL) {
730+
if (default_value != NULL) {
731+
return Py_XNewRef(default_value);
732+
} else {
733+
_PyErr_SetKeyError(key);
734+
return NULL;
735+
}
736+
}
737+
738+
return result;
739+
}
740+
679741
static PyObject*
680742
framelocalsproxy_copy(PyObject *self, PyObject *Py_UNUSED(ignored))
681743
{
@@ -743,6 +805,8 @@ static PyMethodDef framelocalsproxy_methods[] = {
743805
NULL},
744806
{"get", _PyCFunction_CAST(framelocalsproxy_get), METH_FASTCALL,
745807
NULL},
808+
{"pop", _PyCFunction_CAST(framelocalsproxy_pop), METH_FASTCALL,
809+
NULL},
746810
{"setdefault", _PyCFunction_CAST(framelocalsproxy_setdefault), METH_FASTCALL,
747811
NULL},
748812
{NULL, NULL} /* sentinel */

0 commit comments

Comments
 (0)