Skip to content

Commit 80ff0f0

Browse files
gaogaotiantianmiss-islington
authored andcommitted
pythongh-125590: Allow FrameLocalsProxy to delete and pop keys from extra locals (pythonGH-125616)
(cherry picked from commit 5b7a872) Co-authored-by: Tian Gao <[email protected]>
1 parent 65e43ca commit 80ff0f0

File tree

3 files changed

+99
-8
lines changed

3 files changed

+99
-8
lines changed

Lib/test/test_frame.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -399,15 +399,41 @@ def test_repr(self):
399399
def test_delete(self):
400400
x = 1
401401
d = sys._getframe().f_locals
402-
with self.assertRaises(TypeError):
402+
403+
# This needs to be tested before f_extra_locals is created
404+
with self.assertRaisesRegex(KeyError, 'non_exist'):
405+
del d['non_exist']
406+
407+
with self.assertRaises(KeyError):
408+
d.pop('non_exist')
409+
410+
with self.assertRaisesRegex(ValueError, 'local variables'):
403411
del d['x']
404412

405413
with self.assertRaises(AttributeError):
406414
d.clear()
407415

408-
with self.assertRaises(AttributeError):
416+
with self.assertRaises(ValueError):
409417
d.pop('x')
410418

419+
with self.assertRaises(ValueError):
420+
d.pop('x', None)
421+
422+
# 'm', 'n' is stored in f_extra_locals
423+
d['m'] = 1
424+
d['n'] = 1
425+
426+
with self.assertRaises(KeyError):
427+
d.pop('non_exist')
428+
429+
del d['m']
430+
self.assertEqual(d.pop('n'), 1)
431+
432+
self.assertNotIn('m', d)
433+
self.assertNotIn('n', d)
434+
435+
self.assertEqual(d.pop('n', 2), 2)
436+
411437
@support.cpython_only
412438
def test_sizeof(self):
413439
proxy = sys._getframe().f_locals
Lines changed: 1 addition & 0 deletions
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

Lines changed: 70 additions & 6 deletions
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
PyObject** 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);
@@ -200,6 +201,10 @@ framelocalsproxy_setitem(PyObject *self, PyObject *key, PyObject *value)
200201
PyObject *extra = frame->f_extra_locals;
201202

202203
if (extra == NULL) {
204+
if (value == NULL) {
205+
_PyErr_SetKeyError(key);
206+
return -1;
207+
}
203208
extra = PyDict_New();
204209
if (extra == NULL) {
205210
return -1;
@@ -209,7 +214,11 @@ framelocalsproxy_setitem(PyObject *self, PyObject *key, PyObject *value)
209214

210215
assert(PyDict_Check(extra));
211216

212-
return PyDict_SetItem(extra, key, value);
217+
if (value == NULL) {
218+
return PyDict_DelItem(extra, key);
219+
} else {
220+
return PyDict_SetItem(extra, key, value);
221+
}
213222
}
214223

215224
static int
@@ -674,6 +683,59 @@ framelocalsproxy_setdefault(PyObject* self, PyObject *const *args, Py_ssize_t na
674683
return result;
675684
}
676685

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

0 commit comments

Comments
 (0)