Skip to content

Commit 90b6e28

Browse files
committed
dedup keys(), values(), items(), and len() on frame
1 parent 5661919 commit 90b6e28

2 files changed

Lines changed: 100 additions & 26 deletions

File tree

‎Lib/test/test_listcomps.py‎

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -811,19 +811,35 @@ def test_frame_locals(self):
811811
def test_frame_locals_comp_cell_and_enclosing_free(self):
812812
# The inlined listcomp cell and the enclosing free share a name.
813813
# f_locals keys must still be unique so dict(**f_locals) works.
814+
# keys(), values(), items(), and len() must agree (first slot wins).
814815
code = """
815816
def outer(x):
816817
def inner():
817-
return [(lambda: x, dict(**sys._getframe().f_locals))
818+
return [(lambda: x,
819+
dict(**sys._getframe().f_locals),
820+
len(sys._getframe().f_locals),
821+
list(sys._getframe().f_locals.keys()),
822+
list(sys._getframe().f_locals.values()),
823+
list(sys._getframe().f_locals.items()),
824+
dict(sys._getframe().f_locals.items()))
818825
for x in x]
819826
return inner()
820827
result = outer([1, 2])
821-
snaps = [d['x'] for _, d in result]
822-
vals = [fn() for fn, _ in result]
828+
snaps = [d['x'] for _, d, *_ in result]
829+
vals = [fn() for fn, *_ in result]
830+
consistent = []
831+
for _, d, n, ks, vs, it, d_items in result:
832+
consistent.append(
833+
n == len(ks) == len(vs) == len(it)
834+
and ks.count('x') == 1
835+
and d == d_items == dict(zip(ks, vs))
836+
)
823837
"""
824838
import sys
825-
self._check_in_scopes(code, {"snaps": [1, 2], "vals": [2, 2]},
826-
ns={"sys": sys}, scopes=["module", "function"])
839+
self._check_in_scopes(
840+
code,
841+
{"snaps": [1, 2], "vals": [2, 2], "consistent": [True, True]},
842+
ns={"sys": sys}, scopes=["module", "function"])
827843

828844
def _recursive_replace(self, maybe_code):
829845
if not isinstance(maybe_code, types.CodeType):

‎Objects/frameobject.c‎

Lines changed: 79 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,22 @@ framelocalsproxy_hasval(_PyInterpreterFrame *frame, PyCodeObject *co, int i)
9494
return true;
9595
}
9696

97+
static int
98+
framelocalsproxy_is_first_occurrence(PyObject *seen, PyObject *name)
99+
{
100+
int found = PySet_Contains(seen, name);
101+
if (found < 0) {
102+
return -1;
103+
}
104+
if (found) {
105+
return 0;
106+
}
107+
if (PySet_Add(seen, name) < 0) {
108+
return -1;
109+
}
110+
return 1;
111+
}
112+
97113
static int
98114
framelocalsproxy_getkeyindex(PyFrameObject *frame, PyObject *key, bool read, PyObject **value_ptr)
99115
{
@@ -390,18 +406,14 @@ framelocalsproxy_keys(PyObject *self, PyObject *Py_UNUSED(ignored))
390406
for (int i = 0; i < co->co_nlocalsplus; i++) {
391407
if (framelocalsproxy_hasval(frame->f_frame, co, i)) {
392408
PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
393-
int found = PySet_Contains(seen, name);
394-
if (found < 0) {
409+
int first = framelocalsproxy_is_first_occurrence(seen, name);
410+
if (first < 0) {
395411
goto error;
396412
}
397-
if (found) {
398-
continue;
399-
}
400-
if (PySet_Add(seen, name) < 0) {
401-
goto error;
402-
}
403-
if (PyList_Append(names, name) < 0) {
404-
goto error;
413+
if (first) {
414+
if (PyList_Append(names, name) < 0) {
415+
goto error;
416+
}
405417
}
406418
}
407419
}
@@ -610,18 +622,30 @@ framelocalsproxy_values(PyObject *self, PyObject *Py_UNUSED(ignored))
610622
if (values == NULL) {
611623
return NULL;
612624
}
625+
PyObject *seen = PySet_New(NULL);
626+
if (seen == NULL) {
627+
Py_DECREF(values);
628+
return NULL;
629+
}
613630

614631
for (int i = 0; i < co->co_nlocalsplus; i++) {
615632
PyObject *value = framelocalsproxy_getval(frame->f_frame, co, i);
616633
if (value) {
617-
if (PyList_Append(values, value) < 0) {
618-
Py_DECREF(values);
619-
Py_DECREF(value);
620-
return NULL;
634+
PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
635+
int first = framelocalsproxy_is_first_occurrence(seen, name);
636+
if (first == 1) {
637+
if (PyList_Append(values, value) < 0) {
638+
Py_DECREF(value);
639+
goto error;
640+
}
621641
}
622642
Py_DECREF(value);
643+
if (first < 0) {
644+
goto error;
645+
}
623646
}
624647
}
648+
Py_DECREF(seen);
625649

626650
// Iterate through the extra locals
627651
if (frame->f_extra_locals) {
@@ -637,6 +661,11 @@ framelocalsproxy_values(PyObject *self, PyObject *Py_UNUSED(ignored))
637661
}
638662

639663
return values;
664+
665+
error:
666+
Py_DECREF(seen);
667+
Py_DECREF(values);
668+
return NULL;
640669
}
641670

642671
static PyObject *
@@ -648,22 +677,37 @@ framelocalsproxy_items(PyObject *self, PyObject *Py_UNUSED(ignored))
648677
if (items == NULL) {
649678
return NULL;
650679
}
680+
PyObject *seen = PySet_New(NULL);
681+
if (seen == NULL) {
682+
Py_DECREF(items);
683+
return NULL;
684+
}
651685

652686
for (int i = 0; i < co->co_nlocalsplus; i++) {
653687
PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
654688
PyObject *value = framelocalsproxy_getval(frame->f_frame, co, i);
655689

656690
if (value) {
657-
PyObject *pair = _PyTuple_FromPairSteal(Py_NewRef(name), value);
658-
if (pair == NULL) {
659-
goto error;
691+
int first = framelocalsproxy_is_first_occurrence(seen, name);
692+
if (first == 1) {
693+
PyObject *pair = _PyTuple_FromPairSteal(Py_NewRef(name), value);
694+
if (pair == NULL) {
695+
goto error;
696+
}
697+
if (_PyList_AppendTakeRef((PyListObject *)items, pair) < 0) {
698+
goto error;
699+
}
660700
}
661-
662-
if (_PyList_AppendTakeRef((PyListObject *)items, pair) < 0) {
663-
goto error;
701+
else {
702+
Py_DECREF(value);
703+
if (first < 0) {
704+
goto error;
705+
}
664706
}
665707
}
666708
}
709+
Py_DECREF(seen);
710+
seen = NULL;
667711

668712
// Iterate through the extra locals
669713
if (frame->f_extra_locals) {
@@ -685,6 +729,7 @@ framelocalsproxy_items(PyObject *self, PyObject *Py_UNUSED(ignored))
685729
return items;
686730

687731
error:
732+
Py_XDECREF(seen);
688733
Py_DECREF(items);
689734
return NULL;
690735
}
@@ -701,11 +746,24 @@ framelocalsproxy_length(PyObject *self)
701746
size += PyDict_Size(frame->f_extra_locals);
702747
}
703748

749+
PyObject *seen = PySet_New(NULL);
750+
if (seen == NULL) {
751+
return -1;
752+
}
704753
for (int i = 0; i < co->co_nlocalsplus; i++) {
705754
if (framelocalsproxy_hasval(frame->f_frame, co, i)) {
706-
size++;
755+
PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
756+
int first = framelocalsproxy_is_first_occurrence(seen, name);
757+
if (first < 0) {
758+
Py_DECREF(seen);
759+
return -1;
760+
}
761+
else if (first) {
762+
size++;
763+
}
707764
}
708765
}
766+
Py_DECREF(seen);
709767
return size;
710768
}
711769

0 commit comments

Comments
 (0)