Skip to content

Commit a9dc2d8

Browse files
committed
Fix .keys() and other views to support being iterated more than once
1 parent 666613d commit a9dc2d8

File tree

4 files changed

+194
-48
lines changed

4 files changed

+194
-48
lines changed

immutables/_map.c

Lines changed: 121 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2625,7 +2625,7 @@ map_dump(MapObject *self)
26252625
static int
26262626
map_baseiter_tp_clear(MapIterator *it)
26272627
{
2628-
Py_CLEAR(it->hi_obj);
2628+
Py_CLEAR(it->mi_obj);
26292629
return 0;
26302630
}
26312631

@@ -2640,7 +2640,7 @@ map_baseiter_tp_dealloc(MapIterator *it)
26402640
static int
26412641
map_baseiter_tp_traverse(MapIterator *it, visitproc visit, void *arg)
26422642
{
2643-
Py_VISIT(it->hi_obj);
2643+
Py_VISIT(it->mi_obj);
26442644
return 0;
26452645
}
26462646

@@ -2649,15 +2649,15 @@ map_baseiter_tp_iternext(MapIterator *it)
26492649
{
26502650
PyObject *key;
26512651
PyObject *val;
2652-
map_iter_t res = map_iterator_next(&it->hi_iter, &key, &val);
2652+
map_iter_t res = map_iterator_next(&it->mi_iter, &key, &val);
26532653

26542654
switch (res) {
26552655
case I_END:
26562656
PyErr_SetNone(PyExc_StopIteration);
26572657
return NULL;
26582658

26592659
case I_ITEM: {
2660-
return (*(it->hi_yield))(key, val);
2660+
return (*(it->mi_yield))(key, val);
26612661
}
26622662

26632663
default: {
@@ -2666,37 +2666,86 @@ map_baseiter_tp_iternext(MapIterator *it)
26662666
}
26672667
}
26682668

2669+
static int
2670+
map_baseview_tp_clear(MapView *view)
2671+
{
2672+
Py_CLEAR(view->mv_obj);
2673+
Py_CLEAR(view->mv_itertype);
2674+
return 0;
2675+
}
2676+
2677+
static void
2678+
map_baseview_tp_dealloc(MapView *view)
2679+
{
2680+
PyObject_GC_UnTrack(view);
2681+
(void)map_baseview_tp_clear(view);
2682+
PyObject_GC_Del(view);
2683+
}
2684+
2685+
static int
2686+
map_baseview_tp_traverse(MapView *view, visitproc visit, void *arg)
2687+
{
2688+
Py_VISIT(view->mv_obj);
2689+
return 0;
2690+
}
2691+
26692692
static Py_ssize_t
2670-
map_baseiter_tp_len(MapIterator *it)
2693+
map_baseview_tp_len(MapView *view)
26712694
{
2672-
return it->hi_obj->h_count;
2695+
return view->mv_obj->h_count;
26732696
}
26742697

2675-
static PyMappingMethods MapIterator_as_mapping = {
2676-
(lenfunc)map_baseiter_tp_len,
2698+
static PyMappingMethods MapView_as_mapping = {
2699+
(lenfunc)map_baseview_tp_len,
26772700
};
26782701

26792702
static PyObject *
2680-
map_baseiter_new(PyTypeObject *type, binaryfunc yield, MapObject *o)
2703+
map_baseview_newiter(PyTypeObject *type, binaryfunc yield, MapObject *map)
26812704
{
2682-
MapIterator *it = PyObject_GC_New(MapIterator, type);
2683-
if (it == NULL) {
2705+
MapIterator *iter = PyObject_GC_New(MapIterator, type);
2706+
if (iter == NULL) {
2707+
return NULL;
2708+
}
2709+
2710+
Py_INCREF(map);
2711+
iter->mi_obj = map;
2712+
iter->mi_yield = yield;
2713+
map_iterator_init(&iter->mi_iter, map->h_root);
2714+
2715+
PyObject_GC_Track(iter);
2716+
return (PyObject *)iter;
2717+
}
2718+
2719+
static PyObject *
2720+
map_baseview_iter(MapView *view)
2721+
{
2722+
return map_baseview_newiter(
2723+
view->mv_itertype, view->mv_yield, view->mv_obj);
2724+
}
2725+
2726+
static PyObject *
2727+
map_baseview_new(PyTypeObject *type, binaryfunc yield,
2728+
MapObject *o, PyTypeObject *itertype)
2729+
{
2730+
MapView *view = PyObject_GC_New(MapView, type);
2731+
if (view == NULL) {
26842732
return NULL;
26852733
}
26862734

26872735
Py_INCREF(o);
2688-
it->hi_obj = o;
2689-
it->hi_yield = yield;
2736+
view->mv_obj = o;
2737+
view->mv_yield = yield;
26902738

2691-
map_iterator_init(&it->hi_iter, o->h_root);
2739+
Py_INCREF(itertype);
2740+
view->mv_itertype = itertype;
26922741

2693-
return (PyObject*)it;
2742+
PyObject_GC_Track(view);
2743+
return (PyObject *)view;
26942744
}
26952745

26962746
#define ITERATOR_TYPE_SHARED_SLOTS \
26972747
.tp_basicsize = sizeof(MapIterator), \
26982748
.tp_itemsize = 0, \
2699-
.tp_as_mapping = &MapIterator_as_mapping, \
27002749
.tp_dealloc = (destructor)map_baseiter_tp_dealloc, \
27012750
.tp_getattro = PyObject_GenericGetAttr, \
27022751
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, \
@@ -2706,12 +2755,30 @@ map_baseiter_new(PyTypeObject *type, binaryfunc yield, MapObject *o)
27062755
.tp_iternext = (iternextfunc)map_baseiter_tp_iternext,
27072756

27082757

2758+
#define VIEW_TYPE_SHARED_SLOTS \
2759+
.tp_basicsize = sizeof(MapView), \
2760+
.tp_itemsize = 0, \
2761+
.tp_as_mapping = &MapView_as_mapping, \
2762+
.tp_dealloc = (destructor)map_baseview_tp_dealloc, \
2763+
.tp_getattro = PyObject_GenericGetAttr, \
2764+
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, \
2765+
.tp_traverse = (traverseproc)map_baseview_tp_traverse, \
2766+
.tp_clear = (inquiry)map_baseview_tp_clear, \
2767+
.tp_iter = (getiterfunc)map_baseview_iter, \
2768+
2769+
27092770
/////////////////////////////////// _MapItems_Type
27102771

27112772

27122773
PyTypeObject _MapItems_Type = {
27132774
PyVarObject_HEAD_INIT(NULL, 0)
27142775
"items",
2776+
VIEW_TYPE_SHARED_SLOTS
2777+
};
2778+
2779+
PyTypeObject _MapItemsIter_Type = {
2780+
PyVarObject_HEAD_INIT(NULL, 0)
2781+
"items_iterator",
27152782
ITERATOR_TYPE_SHARED_SLOTS
27162783
};
27172784

@@ -2722,10 +2789,11 @@ map_iter_yield_items(PyObject *key, PyObject *val)
27222789
}
27232790

27242791
static PyObject *
2725-
map_new_iteritems(MapObject *o)
2792+
map_new_items_view(MapObject *o)
27262793
{
2727-
return map_baseiter_new(
2728-
&_MapItems_Type, map_iter_yield_items, o);
2794+
return map_baseview_new(
2795+
&_MapItems_Type, map_iter_yield_items, o,
2796+
&_MapItemsIter_Type);
27292797
}
27302798

27312799

@@ -2735,6 +2803,12 @@ map_new_iteritems(MapObject *o)
27352803
PyTypeObject _MapKeys_Type = {
27362804
PyVarObject_HEAD_INIT(NULL, 0)
27372805
"keys",
2806+
VIEW_TYPE_SHARED_SLOTS
2807+
};
2808+
2809+
PyTypeObject _MapKeysIter_Type = {
2810+
PyVarObject_HEAD_INIT(NULL, 0)
2811+
"keys_iterator",
27382812
ITERATOR_TYPE_SHARED_SLOTS
27392813
};
27402814

@@ -2746,19 +2820,32 @@ map_iter_yield_keys(PyObject *key, PyObject *val)
27462820
}
27472821

27482822
static PyObject *
2749-
map_new_iterkeys(MapObject *o)
2823+
map_new_keys_iter(MapObject *o)
27502824
{
2751-
return map_baseiter_new(
2752-
&_MapKeys_Type, map_iter_yield_keys, o);
2825+
return map_baseview_newiter(
2826+
&_MapKeysIter_Type, map_iter_yield_keys, o);
27532827
}
27542828

2829+
static PyObject *
2830+
map_new_keys_view(MapObject *o)
2831+
{
2832+
return map_baseview_new(
2833+
&_MapKeys_Type, map_iter_yield_keys, o,
2834+
&_MapKeysIter_Type);
2835+
}
27552836

27562837
/////////////////////////////////// _MapValues_Type
27572838

27582839

27592840
PyTypeObject _MapValues_Type = {
27602841
PyVarObject_HEAD_INIT(NULL, 0)
27612842
"values",
2843+
VIEW_TYPE_SHARED_SLOTS
2844+
};
2845+
2846+
PyTypeObject _MapValuesIter_Type = {
2847+
PyVarObject_HEAD_INIT(NULL, 0)
2848+
"values_iterator",
27622849
ITERATOR_TYPE_SHARED_SLOTS
27632850
};
27642851

@@ -2770,10 +2857,11 @@ map_iter_yield_values(PyObject *key, PyObject *val)
27702857
}
27712858

27722859
static PyObject *
2773-
map_new_itervalues(MapObject *o)
2860+
map_new_values_view(MapObject *o)
27742861
{
2775-
return map_baseiter_new(
2776-
&_MapValues_Type, map_iter_yield_values, o);
2862+
return map_baseview_new(
2863+
&_MapValues_Type, map_iter_yield_values, o,
2864+
&_MapValuesIter_Type);
27772865
}
27782866

27792867

@@ -2922,7 +3010,7 @@ map_tp_len(BaseMapObject *self)
29223010
static PyObject *
29233011
map_tp_iter(MapObject *self)
29243012
{
2925-
return map_new_iterkeys(self);
3013+
return map_new_keys_iter(self);
29263014
}
29273015

29283016
static PyObject *
@@ -3041,19 +3129,19 @@ map_py_update(MapObject *self, PyObject *args, PyObject *kwds)
30413129
static PyObject *
30423130
map_py_items(MapObject *self, PyObject *args)
30433131
{
3044-
return map_new_iteritems(self);
3132+
return map_new_items_view(self);
30453133
}
30463134

30473135
static PyObject *
30483136
map_py_values(MapObject *self, PyObject *args)
30493137
{
3050-
return map_new_itervalues(self);
3138+
return map_new_values_view(self);
30513139
}
30523140

30533141
static PyObject *
30543142
map_py_keys(MapObject *self, PyObject *args)
30553143
{
3056-
return map_new_iterkeys(self);
3144+
return map_new_keys_view(self);
30573145
}
30583146

30593147
static PyObject *
@@ -3844,7 +3932,10 @@ PyInit__map(void)
38443932
(PyType_Ready(&_Map_CollisionNode_Type) < 0) ||
38453933
(PyType_Ready(&_MapKeys_Type) < 0) ||
38463934
(PyType_Ready(&_MapValues_Type) < 0) ||
3847-
(PyType_Ready(&_MapItems_Type) < 0))
3935+
(PyType_Ready(&_MapItems_Type) < 0) ||
3936+
(PyType_Ready(&_MapKeysIter_Type) < 0) ||
3937+
(PyType_Ready(&_MapValuesIter_Type) < 0) ||
3938+
(PyType_Ready(&_MapItemsIter_Type) < 0))
38483939
{
38493940
return 0;
38503941
}

immutables/_map.h

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,25 @@ typedef struct {
7272
just a key for the 'Keys' iterator, and a value for the 'Values'
7373
iterator.
7474
*/
75+
76+
typedef struct {
77+
PyObject_HEAD
78+
MapObject *mv_obj;
79+
binaryfunc mv_yield;
80+
PyTypeObject *mv_itertype;
81+
} MapView;
82+
7583
typedef struct {
7684
PyObject_HEAD
77-
MapObject *hi_obj;
78-
MapIteratorState hi_iter;
79-
binaryfunc hi_yield;
85+
MapObject *mi_obj;
86+
binaryfunc mi_yield;
87+
MapIteratorState mi_iter;
8088
} MapIterator;
8189

8290

91+
/* PyTypes */
92+
93+
8394
PyTypeObject _Map_Type;
8495
PyTypeObject _MapMutation_Type;
8596
PyTypeObject _Map_ArrayNode_Type;
@@ -88,6 +99,9 @@ PyTypeObject _Map_CollisionNode_Type;
8899
PyTypeObject _MapKeys_Type;
89100
PyTypeObject _MapValues_Type;
90101
PyTypeObject _MapItems_Type;
102+
PyTypeObject _MapKeysIter_Type;
103+
PyTypeObject _MapValuesIter_Type;
104+
PyTypeObject _MapItemsIter_Type;
91105

92106

93107
#endif

immutables/map.py

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -391,20 +391,43 @@ def dump(self, buf, level): # pragma: no cover
391391
buf.append('{}{!r}: {!r}'.format(pad, key, val))
392392

393393

394-
class GenWrapper:
394+
class MapKeys:
395395

396-
def __init__(self, count, gen):
397-
self.__count = count
398-
self.__gen = gen
396+
def __init__(self, c, m):
397+
self.__count = c
398+
self.__root = m
399+
400+
def __len__(self):
401+
return self.__count
402+
403+
def __iter__(self):
404+
return iter(self.__root.keys())
405+
406+
407+
class MapValues:
408+
409+
def __init__(self, c, m):
410+
self.__count = c
411+
self.__root = m
399412

400413
def __len__(self):
401414
return self.__count
402415

403416
def __iter__(self):
404-
return self
417+
return iter(self.__root.values())
418+
419+
420+
class MapItems:
421+
422+
def __init__(self, c, m):
423+
self.__count = c
424+
self.__root = m
405425

406-
def __next__(self):
407-
return next(self.__gen)
426+
def __len__(self):
427+
return self.__count
428+
429+
def __iter__(self):
430+
return iter(self.__root.items())
408431

409432

410433
class Map:
@@ -544,13 +567,13 @@ def __iter__(self):
544567
yield from self.__root.keys()
545568

546569
def keys(self):
547-
return GenWrapper(self.__count, self.__root.keys())
570+
return MapKeys(self.__count, self.__root)
548571

549572
def values(self):
550-
return GenWrapper(self.__count, self.__root.values())
573+
return MapValues(self.__count, self.__root)
551574

552575
def items(self):
553-
return GenWrapper(self.__count, self.__root.items())
576+
return MapItems(self.__count, self.__root)
554577

555578
def __hash__(self):
556579
if self.__hash != -1:

0 commit comments

Comments
 (0)